summaryrefslogtreecommitdiff
path: root/src/mongo/util
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@mongodb.com>2016-05-04 19:20:14 -0400
committerAndy Schwerin <schwerin@mongodb.com>2016-05-17 11:11:57 -0400
commit11e82fe19c81bd1a5d350a219c3a000e959b6dd6 (patch)
treecd7798a660b4c8791caad44d0b25e27bceb03938 /src/mongo/util
parentd22193f28e3113d42b4b7248b25f476dc75a7da7 (diff)
downloadmongo-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/SConscript9
-rw-r--r--src/mongo/util/duration.cpp146
-rw-r--r--src/mongo/util/duration.h410
-rw-r--r--src/mongo/util/duration_test.cpp219
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