diff options
author | Andy Schwerin <schwerin@mongodb.com> | 2016-05-04 19:20:14 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@mongodb.com> | 2016-05-17 11:11:57 -0400 |
commit | 11e82fe19c81bd1a5d350a219c3a000e959b6dd6 (patch) | |
tree | cd7798a660b4c8791caad44d0b25e27bceb03938 /src/mongo/util | |
parent | d22193f28e3113d42b4b7248b25f476dc75a7da7 (diff) | |
download | mongo-11e82fe19c81bd1a5d350a219c3a000e959b6dd6.tar.gz |
Introduce a replacement for std::chrono::duration without undefined behavior.
Diffstat (limited to 'src/mongo/util')
-rw-r--r-- | src/mongo/util/SConscript | 9 | ||||
-rw-r--r-- | src/mongo/util/duration.cpp | 146 | ||||
-rw-r--r-- | src/mongo/util/duration.h | 410 | ||||
-rw-r--r-- | src/mongo/util/duration_test.cpp | 219 |
4 files changed, 784 insertions, 0 deletions
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index e03426cfdb3..f2d3763c858 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -489,3 +489,12 @@ env.CppUnitTest( '$BUILD_DIR/mongo/base', ] ) + +env.CppUnitTest( + target='duration_test', +source=[ + 'duration_test.cpp', + 'duration.cpp', +], +LIBDEPS=[ +]) diff --git a/src/mongo/util/duration.cpp b/src/mongo/util/duration.cpp new file mode 100644 index 00000000000..ac0faf19394 --- /dev/null +++ b/src/mongo/util/duration.cpp @@ -0,0 +1,146 @@ +/* Copyright 2016 MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/util/duration.h" + +#include <iostream> + +#include "mongo/bson/util/builder.h" + +namespace mongo { +namespace x { +namespace { +template <typename Stream> +Stream& streamPut(Stream& os, Nanoseconds ns) { + return os << ns.count() << "ns"; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Microseconds us) { + return os << us.count() << "\xce\xbcs"; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Milliseconds ms) { + return os << ms.count() << "ms"; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Seconds s) { + return os << s.count() << 's'; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Minutes min) { + return os << min.count() << "min"; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Hours hrs) { + return os << hrs.count() << "hr"; +} + +} // namespace + +std::ostream& operator<<(std::ostream& os, Nanoseconds ns) { + return streamPut(os, ns); +} + +std::ostream& operator<<(std::ostream& os, Microseconds us) { + return streamPut(os, us); +} + +std::ostream& operator<<(std::ostream& os, Milliseconds ms) { + return streamPut(os, ms); +} +std::ostream& operator<<(std::ostream& os, Seconds s) { + return streamPut(os, s); +} + +std::ostream& operator<<(std::ostream& os, Minutes m) { + return streamPut(os, m); +} + +std::ostream& operator<<(std::ostream& os, Hours h) { + return streamPut(os, h); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Nanoseconds ns) { + return streamPut(os, ns); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Microseconds us) { + return streamPut(os, us); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Milliseconds ms) { + return streamPut(os, ms); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Seconds s) { + return streamPut(os, s); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Minutes m) { + return streamPut(os, m); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Hours h) { + return streamPut(os, h); +} + +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, + Nanoseconds); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, + Microseconds); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, + Milliseconds); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, Seconds); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, Minutes); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, Hours); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Nanoseconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Microseconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Milliseconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Seconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Minutes); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Hours); +} // namespace x +} // namespace mongo diff --git a/src/mongo/util/duration.h b/src/mongo/util/duration.h new file mode 100644 index 00000000000..ccde7fd57a3 --- /dev/null +++ b/src/mongo/util/duration.h @@ -0,0 +1,410 @@ +/* Copyright 2016 MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include <cstdint> +#include <iosfwd> +#include <limits> +#include <ratio> + +#include "mongo/platform/overflow_arithmetic.h" +#include "mongo/stdx/type_traits.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + +template <typename Allocator> +class StringBuilderImpl; + +namespace x { + +template <typename Period> +class Duration; + +using Nanoseconds = Duration<std::nano>; +using Microseconds = Duration<std::micro>; +using Milliseconds = Duration<std::milli>; +using Seconds = Duration<std::ratio<1>>; +using Minutes = Duration<std::ratio<60>>; +using Hours = Duration<std::ratio<3600>>; + +// +// Streaming output operators for common duration types. Writes the numerical value followed by +// an abbreviated unit, without a space. +// +// E.g., std::cout << Minutes{5} << std::endl; should produce the following: +// 5min +// + +std::ostream& operator<<(std::ostream& os, Nanoseconds ns); +std::ostream& operator<<(std::ostream& os, Microseconds us); +std::ostream& operator<<(std::ostream& os, Milliseconds ms); +std::ostream& operator<<(std::ostream& os, Seconds s); +std::ostream& operator<<(std::ostream& os, Minutes m); +std::ostream& operator<<(std::ostream& os, Hours h); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Nanoseconds ns); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Microseconds us); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Milliseconds ms); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Seconds s); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Minutes m); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Hours h); + + +template <typename Duration1, typename Duration2> +using HigherPrecisionDuration = + typename std::conditional<!Duration1::template IsLowerPrecisionThan<Duration2>::value, + Duration1, + Duration2>::type; + +/** + * Casts from one Duration precision to another. + * + * May throw a UserException if "from" is of lower-precision type and is outside the range of the + * ToDuration. For example, Seconds::max() cannot be represented as a Milliseconds, and + * so attempting to cast that value to Milliseconds will throw an exception. + */ +template <typename ToDuration, typename FromPeriod> +ToDuration durationCast(const Duration<FromPeriod>& from) { + using FromOverTo = std::ratio_divide<FromPeriod, typename ToDuration::period>; + if (ToDuration::template isHigherPrecisionThan<Duration<FromPeriod>>()) { + typename ToDuration::rep toCount; + uassert(ErrorCodes::DurationOverflow, + "Overflow casting from a lower-precision duration to a higher-precision duration", + !mongoSignedMultiplyOverflow64(from.count(), FromOverTo::num, &toCount)); + return ToDuration{toCount}; + } + return ToDuration{from.count() / FromOverTo::den}; +} + +/** + * Type representing a duration using a 64-bit counter. + * + * The Period template argument is a std::ratio describing the units of the duration type. + * + * This type's behavior is similar to std::chrono::duration, but instead of undefined behavior on + * overflows and other conversions, throws exceptions. + */ +template <typename Period> +class Duration { +public: + static_assert(Period::num > 0, "Duration::period's numerator must be positive"); + static_assert(Period::den > 0, "Duration::period's denominator must be positive"); + + using rep = int64_t; + using period = Period; + + /** + * Type with static bool "value" set to true if this Duration type is higher precision than + * OtherDuration. That is, if OtherDuration::period > period. + */ + template <typename OtherDuration> + struct IsHigherPrecisionThan { + using OtherOverThis = std::ratio_divide<typename OtherDuration::period, period>; + static_assert(OtherOverThis::den == 1 || OtherOverThis::num == 1, + "Mongo duration types are only compatible with each other when one's period " + "is an even multiple of the other's."); + static constexpr bool value = OtherOverThis::den == 1 && OtherOverThis::num != 1; + }; + + /** + * Type with static bool "value" set to true if this Duration type is lower precision than + * OtherDuration. That is, if OtherDuration::period > period. + */ + template <typename OtherDuration> + struct IsLowerPrecisionThan { + using OtherOverThis = std::ratio_divide<typename OtherDuration::period, period>; + static_assert(OtherOverThis::den == 1 || OtherOverThis::num == 1, + "Mongo duration types are only compatible with each other when one's period " + "is an even multiple of the other's."); + static constexpr bool value = OtherOverThis::num == 1 && OtherOverThis::den != 1; + }; + + /** + * Function that returns true if period > OtherDuration::period. + */ + template <typename OtherDuration> + constexpr static bool isHigherPrecisionThan() { + return IsHigherPrecisionThan<OtherDuration>::value; + } + + /** + * Function that returns true if period < OtherDuration::period. + */ + template <typename OtherDuration> + constexpr static bool isLowerPrecisionThan() { + return IsLowerPrecisionThan<OtherDuration>::value; + } + + static constexpr Duration zero() { + return Duration{}; + } + + static constexpr Duration min() { + return Duration{std::numeric_limits<rep>::min()}; + } + + static constexpr Duration max() { + return Duration{std::numeric_limits<rep>::max()}; + } + + /** + * Constructs the zero duration. + */ + constexpr Duration() = default; + + /** + * Constructs a duration representing "r" periods. + */ + template <typename Rep2> + constexpr explicit Duration(const Rep2& r) + : _count(r) { + static_assert(std::is_integral<Rep2>::value && std::is_signed<Rep2>::value, + "Durations must be constructed from values of signed integral type"); + } + + /** + * Constructs a higher-precision duration from a lower-precision one, as by durationCast. + * + * Throws a UserException if "from" is out of the range of this duration type. + * + * It is a compilation error to attempt a conversion from higher-precision to lower-precision by + * this constructor. + */ + template <typename FromPeriod> + /*implicit*/ Duration(const Duration<FromPeriod>& from) : Duration(durationCast<Duration>(from)) { + static_assert(!isLowerPrecisionThan<Duration<FromPeriod>>(), + "Use duration_cast to convert from higher precision Duration types to lower " + "precision ones"); + } + + /** + * Returns the number of periods represented by this duration. + */ + constexpr rep count() const { + return _count; + } + + /** + * Compares this duration to another duration of the same type. + * + * Returns 1, -1 or 0 depending on whether this duration is greater than, less than or equal to + * the other duration, respectively. + */ + constexpr int compare(const Duration& other) const { + return (count() > other.count()) ? 1 : (count() < other.count()) ? -1 : 0; + } + + /** + * Compares this duration to a lower-precision duration, "other". + */ + template <typename OtherPeriod> + int compare(const Duration<OtherPeriod>& other) const { + if (isLowerPrecisionThan<Duration<OtherPeriod>>()) { + return -other.compare(*this); + } + using OtherOverThis = std::ratio_divide<OtherPeriod, period>; + rep otherCount; + if (mongoSignedMultiplyOverflow64(other.count(), OtherOverThis::num, &otherCount)) { + return other.count() < 0 ? 1 : -1; + } + if (count() < otherCount) { + return -1; + } + if (count() > otherCount) { + return 1; + } + return 0; + } + + constexpr Duration operator+() const { + return *this; + } + + Duration operator-() const { + uassert(ErrorCodes::DurationOverflow, "Cannot negate the minimum duration", *this != min()); + return Duration(-count()); + } + + // + // In-place arithmetic operators + // + + Duration& operator++() { + return (*this) += Duration{1}; + } + + Duration operator++(int) { + auto result = *this; + *this += Duration{1}; + return result; + } + + Duration operator--() { + return (*this) -= Duration{1}; + } + + Duration operator--(int) { + auto result = *this; + *this -= Duration{1}; + return result; + } + + Duration& operator+=(const Duration& other) { + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while adding " << other << " to " << *this, + !mongoSignedAddOverflow64(count(), other.count(), &_count)); + return *this; + } + + Duration& operator-=(const Duration& other) { + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while subtracting " << other << " from " << *this, + !mongoSignedSubtractOverflow64(count(), other.count(), &_count)); + return *this; + } + + template <typename Rep2> + Duration& operator*=(const Rep2& scale) { + static_assert(std::is_integral<Rep2>::value && std::is_signed<Rep2>::value, + "Durations may only be multiplied by values of signed integral type"); + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while multiplying " << *this << " by " << scale, + !mongoSignedMultiplyOverflow64(count(), scale, &_count)); + return *this; + } + + template <typename Rep2> + Duration& operator/=(const Rep2& scale) { + static_assert(std::is_integral<Rep2>::value && std::is_signed<Rep2>::value, + "Durations may only be divided by values of signed integral type"); + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while dividing " << *this << " by -1", + (count() != min().count() || scale != -1)); + _count /= scale; + return *this; + } + +private: + rep _count = {}; +}; + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator==(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) == 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator!=(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) != 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator<(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) < 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator<=(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) <= 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator>(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) > 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator>=(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) >= 0; +} + +/** + * Returns the sum of two durations, "lhs" and "rhs". + */ +template < + typename LhsPeriod, + typename RhsPeriod, + typename ReturnDuration = HigherPrecisionDuration<Duration<LhsPeriod>, Duration<RhsPeriod>>> +ReturnDuration operator+(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + ReturnDuration result = lhs; + result += rhs; + return result; +} + +/** + * Returns the result of subtracting "rhs" from "lhs". + */ +template < + typename LhsPeriod, + typename RhsPeriod, + typename ReturnDuration = HigherPrecisionDuration<Duration<LhsPeriod>, Duration<RhsPeriod>>> +ReturnDuration operator-(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + ReturnDuration result = lhs; + result -= rhs; + return result; +} + +/** + * Returns the product of a duration "d" and a unitless integer, "scale". + */ +template <typename Period, typename Rep2> +Duration<Period> operator*(Duration<Period> d, const Rep2& scale) { + d *= scale; + return d; +} + +template <typename Period, typename Rep2> +Duration<Period> operator*(const Rep2& scale, Duration<Period> d) { + d *= scale; + return d; +} + +/** + * Returns duration "d" divided by unitless integer "scale". + */ +template <typename Period, typename Rep2> +Duration<Period> operator/(Duration<Period> d, const Rep2& scale) { + d /= scale; + return d; +} + +} // namespace x +} // namespace mongo diff --git a/src/mongo/util/duration_test.cpp b/src/mongo/util/duration_test.cpp new file mode 100644 index 00000000000..8e463bcee76 --- /dev/null +++ b/src/mongo/util/duration_test.cpp @@ -0,0 +1,219 @@ +/* Copyright 2016 MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/util/duration.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace x { +namespace { + +// The DurationTestSameType Compare* tests server to check the implementation of the comparison +// operators as well as the compare() method, and so sometimes must explicitly check ASSERT_FALSE(v1 +// OP v2). The DurationTestDifferentTypes Compare* tests rely on the fact that the operators all +// just call compare(), so if the operators worked in the SameType tests, they can be trusted in the +// DifferentType tests. As such, the DifferentType tests only use ASSERT_{GT,LT,LTE,GTE,EQ,NE} and +// never do an ASSERT_FALSE. + +TEST(DurationComparisonSameType, CompareEqual) { + ASSERT_EQ(Microseconds::zero(), Microseconds::zero()); + ASSERT_EQ(Microseconds::max(), Microseconds::max()); + ASSERT_EQ(Microseconds::min(), Microseconds::min()); + ASSERT_FALSE(Microseconds::zero() == Microseconds{-1}); +} + +TEST(DurationComparisonSameType, CompareNotEqual) { + ASSERT_NE(Microseconds{1}, Microseconds::zero()); + ASSERT_NE(Microseconds{-1}, Microseconds{1}); + ASSERT_FALSE(Microseconds::zero() != Microseconds{0}); +} +TEST(DurationComparisonSameType, SameTypeCompareGreaterThan) { + ASSERT_GT(Microseconds::zero(), Microseconds::min()); + ASSERT_GT(Microseconds{Microseconds::min().count() + 1}, Microseconds::min()); + ASSERT_FALSE(Microseconds{-10} > Microseconds{103}); + ASSERT_FALSE(Microseconds{1} > Microseconds{1}); +} + +TEST(DurationComparisonSameType, CompareLessThan) { + ASSERT_LT(Microseconds::zero(), Microseconds::max()); + ASSERT_LT(Microseconds{Microseconds::max().count() - 1}, Microseconds::max()); + ASSERT_LT(Microseconds{1}, Microseconds{10}); + ASSERT_FALSE(Microseconds{1} < Microseconds{1}); + ASSERT_FALSE(Microseconds{-3} < Microseconds{-1200}); +} + +TEST(DurationComparisonSameType, CompareGreaterThanOrEqual) { + ASSERT_GTE(Microseconds::zero(), Microseconds::min()); + ASSERT_GTE(Microseconds{Microseconds::min().count() + 1}, Microseconds::min()); + ASSERT_GTE(Microseconds::max(), Microseconds::max()); + ASSERT_GTE(Microseconds::min(), Microseconds::min()); + ASSERT_GTE(Microseconds{5}, Microseconds{5}); + ASSERT_FALSE(Microseconds{-10} > Microseconds{103}); +} + +TEST(DurationComparisonSameType, CompareLessThanOrEqual) { + ASSERT_LTE(Microseconds::zero(), Microseconds::max()); + ASSERT_LTE(Microseconds{Microseconds::max().count() - 1}, Microseconds::max()); + ASSERT_LTE(Microseconds{1}, Microseconds{10}); + ASSERT_LTE(Microseconds{1}, Microseconds{1}); + ASSERT_FALSE(Microseconds{-3} < Microseconds{-1200}); +} + +// Since comparison operators are implemented in terms of Duration::compare, we do not need to +// re-test all of the operators when the duration types are different. It suffices to know that +// compare works, which can be accomplished with EQ, NE and LT alone. + +TEST(DurationComparisonDifferentTypes, CompareEqual) { + ASSERT_EQ(Seconds::zero(), Milliseconds::zero()); + ASSERT_EQ(Seconds{16}, Milliseconds{16000}); + ASSERT_EQ(Minutes{60}, Hours{1}); +} +TEST(DurationComparisonDifferentTypes, CompareNotEqual) { + ASSERT_NE(Milliseconds::max(), Seconds::max()); + ASSERT_NE(Milliseconds::min(), Seconds::min()); + ASSERT_NE(Seconds::max(), Milliseconds::max()); + ASSERT_NE(Seconds::min(), Milliseconds::min()); + ASSERT_NE(Seconds{1}, Milliseconds{1}); +} + +TEST(DurationComparisonDifferentTypes, CompareLessThan) { + ASSERT_LT(Milliseconds{1}, Seconds{1}); + ASSERT_LT(Milliseconds{999}, Seconds{1}); + ASSERT_LT(Seconds{1}, Milliseconds{1001}); + ASSERT_LT(Milliseconds{-1001}, Seconds{-1}); + ASSERT_LT(Seconds{-1}, Milliseconds{-1}); + ASSERT_LT(Seconds{-1}, Milliseconds{-999}); +} + +TEST(DurationComparisonDifferentTypes, CompareAtLimits) { + ASSERT_LT(Milliseconds::max(), Seconds::max()); + ASSERT_LT(Seconds::min(), Milliseconds::min()); + + ASSERT_LT(Milliseconds::min(), + durationCast<Milliseconds>(durationCast<Seconds>(Milliseconds::min()))); + ASSERT_GT(Milliseconds::max(), + durationCast<Milliseconds>(durationCast<Seconds>(Milliseconds::max()))); +} + +TEST(DurationCast, NonTruncatingDurationCasts) { + ASSERT_EQ(1, durationCast<Seconds>(Milliseconds{1000}).count()); + ASSERT_EQ(1000, durationCast<Milliseconds>(Seconds{1}).count()); + ASSERT_EQ(1000, Milliseconds{Seconds{1}}.count()); + ASSERT_EQ(1053, durationCast<Milliseconds>(Milliseconds{1053}).count()); +} + +TEST(DurationCast, TruncatingDurationCasts) { + ASSERT_EQ(1, durationCast<Seconds>(Milliseconds{1600}).count()); + ASSERT_EQ(0, durationCast<Seconds>(Milliseconds{999}).count()); + ASSERT_EQ(-1, durationCast<Seconds>(Milliseconds{-1600}).count()); + ASSERT_EQ(0, durationCast<Seconds>(Milliseconds{-999}).count()); +} + +TEST(DurationCast, OverflowingCastsThrow) { + ASSERT_THROWS_CODE( + durationCast<Milliseconds>(Seconds::max()), UserException, ErrorCodes::DurationOverflow); + ASSERT_THROWS_CODE( + durationCast<Milliseconds>(Seconds::min()), UserException, ErrorCodes::DurationOverflow); +} + +TEST(DurationAssignment, DurationAssignment) { + Milliseconds ms = Milliseconds{15}; + Milliseconds ms2 = ms; + Milliseconds ms3 = Milliseconds{30}; + ms3 = ms; + ASSERT_EQ(ms, ms3); + ASSERT_EQ(ms2, ms3); +} + +TEST(DurationArithmetic, AddNoOverflowSucceeds) { + ASSERT_EQ(Milliseconds{1001}, Milliseconds{1} + Seconds{1}); + ASSERT_EQ(Milliseconds{1001}, Seconds{1} + Milliseconds{1}); + ASSERT_EQ(Milliseconds{1001}, Milliseconds{1} + Milliseconds{1000}); +} + +TEST(DurationArithmetic, AddOverflowThrows) { + // Max + 1 should throw + ASSERT_THROWS_CODE( + Milliseconds::max() + Milliseconds{1}, UserException, ErrorCodes::DurationOverflow); + + // Min + -1 should throw + ASSERT_THROWS_CODE( + Milliseconds::min() + Milliseconds{-1}, UserException, ErrorCodes::DurationOverflow); + + // Conversion of Seconds::min() to Milliseconds should throw + ASSERT_THROWS_CODE( + Seconds::min() + Milliseconds{1}, UserException, ErrorCodes::DurationOverflow); + ASSERT_THROWS_CODE( + Milliseconds{1} + Seconds::min(), UserException, ErrorCodes::DurationOverflow); +} + +TEST(DurationArithmetic, SubtractNoOverflowSucceeds) { + ASSERT_EQ(Milliseconds{-999}, Milliseconds{1} - Seconds{1}); + ASSERT_EQ(Milliseconds{999}, Seconds{1} - Milliseconds{1}); + ASSERT_EQ(Milliseconds{-999}, Milliseconds{1} - Milliseconds{1000}); + ASSERT_EQ(Milliseconds::zero() - Milliseconds{1}, -Milliseconds{1}); +} + +TEST(DurationArithmetic, SubtractOverflowThrows) { + // Min - 1 should throw + ASSERT_THROWS_CODE( + Milliseconds::min() - Milliseconds{1}, UserException, ErrorCodes::DurationOverflow); + + // Max + -1 should throw + ASSERT_THROWS_CODE( + Milliseconds::max() - Milliseconds{-1}, UserException, ErrorCodes::DurationOverflow); + + // Conversion of Seconds::min() to Milliseconds should throw + ASSERT_THROWS_CODE( + Seconds::min() - Milliseconds{1}, UserException, ErrorCodes::DurationOverflow); + ASSERT_THROWS_CODE( + Milliseconds{1} - Seconds::min(), UserException, ErrorCodes::DurationOverflow); +} + +TEST(DurationArithmetic, MultiplyNoOverflowSucceds) { + ASSERT_EQ(Milliseconds{150}, 15 * Milliseconds{10}); + ASSERT_EQ(Milliseconds{150}, Milliseconds{15} * 10); +} + +TEST(DurationArithmetic, MultilpyOverflowThrows) { + ASSERT_THROWS_CODE(Milliseconds::max() * 2, UserException, ErrorCodes::DurationOverflow); + ASSERT_THROWS_CODE(2 * Milliseconds::max(), UserException, ErrorCodes::DurationOverflow); + ASSERT_THROWS_CODE(Milliseconds::max() * -2, UserException, ErrorCodes::DurationOverflow); + ASSERT_THROWS_CODE(-2 * Milliseconds::max(), UserException, ErrorCodes::DurationOverflow); +} + +TEST(DurationArithmetic, DivideNoOverflowSucceeds) { + ASSERT_EQ(Milliseconds{-1}, Milliseconds{2} / -2); +} + +TEST(DurationArithmetic, DivideOverflowThrows) { + ASSERT_THROWS_CODE(Milliseconds::min() / -1, UserException, ErrorCodes::DurationOverflow); +} + +} // namespace +} // namespace x +} // namespace mongo |