diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index 9e1772bb5..b114b297c 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake @@ -74,12 +74,13 @@ function(absl_header_library) if(NOT EXISTS ${__dummy_header_only_lib_file}) file(WRITE ${__dummy_header_only_lib_file} - "\ - /* generated file for header-only cmake target */ \ - \ - // single meaningless symbol \ - void __${_NAME}__header_fakesym() {} \ - \ + "/* generated file for header-only cmake target */ + + namespace absl { + + // single meaningless symbol + void ${_NAME}__header_fakesym() {} + } // namespace absl " ) endif() diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 64057f6c9..e52846334 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -238,6 +238,7 @@ cc_library( deps = [ ":config", ":pretty_function", + "//absl/memory", "//absl/meta:type_traits", "//absl/strings", "@com_google_googletest//:gtest", diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc index 365732c67..c1b5df265 100644 --- a/absl/base/exception_safety_testing_test.cc +++ b/absl/base/exception_safety_testing_test.cc @@ -14,10 +14,6 @@ namespace absl { namespace { using ::absl::exceptions_internal::TestException; -void SetCountdown() { exceptions_internal::countdown = 0; } - -void UnsetCountdown() { exceptions_internal::countdown = -1; } - // EXPECT_NO_THROW can't inspect the thrown inspection in general. template void ExpectNoThrow(const F& f) { @@ -395,12 +391,15 @@ struct FailsBasicGuarantee { ++i; } - bool operator!=(const FailsBasicGuarantee& other) const { + bool operator==(const FailsBasicGuarantee& other) const { return i != other.i; } - friend bool AbslCheckInvariants(const FailsBasicGuarantee& g) { - return g.i >= 0; + friend testing::AssertionResult AbslCheckInvariants( + const FailsBasicGuarantee& g) { + if (g.i >= 0) return testing::AssertionSuccess(); + return testing::AssertionFailure() + << "i should be non-negative but is " << g.i; } int i = 0; @@ -408,7 +407,7 @@ struct FailsBasicGuarantee { TEST(ExceptionCheckTest, BasicGuaranteeFailure) { FailsBasicGuarantee g; - EXPECT_FALSE(TestBasicGuarantee(&g, CallOperator{})); + EXPECT_FALSE(TestExceptionSafety(&g, CallOperator{})); } struct FollowsBasicGuarantee { @@ -417,12 +416,15 @@ struct FollowsBasicGuarantee { ThrowingValue<> bomb; } - bool operator!=(const FollowsBasicGuarantee& other) const { - return i != other.i; + bool operator==(const FollowsBasicGuarantee& other) const { + return i == other.i; } - friend bool AbslCheckInvariants(const FollowsBasicGuarantee& g) { - return g.i >= 0; + friend testing::AssertionResult AbslCheckInvariants( + const FollowsBasicGuarantee& g) { + if (g.i >= 0) return testing::AssertionSuccess(); + return testing::AssertionFailure() + << "i should be non-negative but is " << g.i; } int i = 0; @@ -430,30 +432,79 @@ struct FollowsBasicGuarantee { TEST(ExceptionCheckTest, BasicGuarantee) { FollowsBasicGuarantee g; - EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety(&g, CallOperator{})); } TEST(ExceptionCheckTest, StrongGuaranteeFailure) { { FailsBasicGuarantee g; - EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{})); + EXPECT_FALSE(TestExceptionSafety(&g, CallOperator{}, StrongGuarantee(g))); } { FollowsBasicGuarantee g; - EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{})); + EXPECT_FALSE(TestExceptionSafety(&g, CallOperator{}, StrongGuarantee(g))); + } +} + +struct BasicGuaranteeWithExtraInvariants { + // After operator(), i is incremented. If operator() throws, i is set to 9999 + void operator()() { + int old_i = i; + i = kExceptionSentinel; + ThrowingValue<> bomb; + i = ++old_i; + } + + bool operator==(const FollowsBasicGuarantee& other) const { + return i == other.i; + } + + friend testing::AssertionResult AbslCheckInvariants( + const BasicGuaranteeWithExtraInvariants& g) { + if (g.i >= 0) return testing::AssertionSuccess(); + return testing::AssertionFailure() + << "i should be non-negative but is " << g.i; + } + + int i = 0; + static constexpr int kExceptionSentinel = 9999; +}; +constexpr int BasicGuaranteeWithExtraInvariants::kExceptionSentinel; + +TEST(ExceptionCheckTest, BasicGuaranteeWithInvariants) { + { + BasicGuaranteeWithExtraInvariants g; + EXPECT_TRUE(TestExceptionSafety(&g, CallOperator{})); + } + + { + BasicGuaranteeWithExtraInvariants g; + EXPECT_TRUE(TestExceptionSafety( + &g, CallOperator{}, [](const BasicGuaranteeWithExtraInvariants& w) { + if (w.i == BasicGuaranteeWithExtraInvariants::kExceptionSentinel) { + return testing::AssertionSuccess(); + } + return testing::AssertionFailure() + << "i should be " + << BasicGuaranteeWithExtraInvariants::kExceptionSentinel + << ", but is " << w.i; + })); } } struct FollowsStrongGuarantee { void operator()() { ThrowingValue<> bomb; } - bool operator!=(const FollowsStrongGuarantee& other) const { - return i != other.i; + bool operator==(const FollowsStrongGuarantee& other) const { + return i == other.i; } - friend bool AbslCheckInvariants(const FollowsStrongGuarantee& g) { - return g.i >= 0; + friend testing::AssertionResult AbslCheckInvariants( + const FollowsStrongGuarantee& g) { + if (g.i >= 0) return testing::AssertionSuccess(); + return testing::AssertionFailure() + << "i should be non-negative but is " << g.i; } int i = 0; @@ -461,8 +512,65 @@ struct FollowsStrongGuarantee { TEST(ExceptionCheckTest, StrongGuarantee) { FollowsStrongGuarantee g; - EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{})); - EXPECT_TRUE(TestStrongGuarantee(&g, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety(&g, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety(&g, CallOperator{}, StrongGuarantee(g))); +} + +struct NonCopyable { + NonCopyable(const NonCopyable&) = delete; + explicit NonCopyable(int ii) : i(ii) {} + + void operator()() { ThrowingValue<> bomb; } + + bool operator==(const NonCopyable& other) const { return i == other.i; } + + friend testing::AssertionResult AbslCheckInvariants(const NonCopyable& g) { + if (g.i >= 0) return testing::AssertionSuccess(); + return testing::AssertionFailure() + << "i should be non-negative but is " << g.i; + } + + int i; +}; + +TEST(ExceptionCheckTest, NonCopyable) { + NonCopyable g(0); + EXPECT_TRUE(TestExceptionSafety(&g, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety( + &g, CallOperator{}, + PointeeStrongGuarantee(absl::make_unique(g.i)))); +} + +struct NonEqualityComparable { + void operator()() { ThrowingValue<> bomb; } + + void ModifyOnThrow() { + ++i; + ThrowingValue<> bomb; + static_cast(bomb); + --i; + } + + friend testing::AssertionResult AbslCheckInvariants( + const NonEqualityComparable& g) { + if (g.i >= 0) return testing::AssertionSuccess(); + return testing::AssertionFailure() + << "i should be non-negative but is " << g.i; + } + + int i = 0; +}; + +TEST(ExceptionCheckTest, NonEqualityComparable) { + NonEqualityComparable g; + auto comp = [](const NonEqualityComparable& a, + const NonEqualityComparable& b) { return a.i == b.i; }; + EXPECT_TRUE(TestExceptionSafety(&g, CallOperator{})); + EXPECT_TRUE( + TestExceptionSafety(&g, CallOperator{}, absl::StrongGuarantee(g, comp))); + EXPECT_FALSE(TestExceptionSafety( + &g, [&](NonEqualityComparable* n) { n->ModifyOnThrow(); }, + absl::StrongGuarantee(g, comp))); } template @@ -480,11 +588,14 @@ struct InstructionCounter { ++counter; } - bool operator!=(const InstructionCounter>& other) const { - return false; + bool operator==(const InstructionCounter>&) const { + return true; } - friend bool AbslCheckInvariants(const InstructionCounter&) { return true; } + friend testing::AssertionResult AbslCheckInvariants( + const InstructionCounter&) { + return testing::AssertionSuccess(); + } static int counter; }; @@ -493,18 +604,38 @@ int InstructionCounter::counter = 0; TEST(ExceptionCheckTest, Exhaustiveness) { InstructionCounter int_factory; - EXPECT_TRUE(TestBasicGuarantee(&int_factory, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety(&int_factory, CallOperator{})); EXPECT_EQ(InstructionCounter::counter, 4); InstructionCounter> bomb_factory; - EXPECT_TRUE(TestBasicGuarantee(&bomb_factory, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety(&bomb_factory, CallOperator{})); EXPECT_EQ(InstructionCounter>::counter, 10); InstructionCounter>::counter = 0; - EXPECT_TRUE(TestStrongGuarantee(&bomb_factory, CallOperator{})); + EXPECT_TRUE(TestExceptionSafety(&bomb_factory, CallOperator{}, + StrongGuarantee(bomb_factory))); EXPECT_EQ(InstructionCounter>::counter, 10); } +struct LeaksIfCtorThrows : private exceptions_internal::TrackedObject { + LeaksIfCtorThrows() : TrackedObject(ABSL_PRETTY_FUNCTION) { + ++counter; + ThrowingValue<> v; + static_cast(v); + --counter; + } + LeaksIfCtorThrows(const LeaksIfCtorThrows&) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION) {} + static int counter; +}; +int LeaksIfCtorThrows::counter = 0; + +TEST(ExceptionCheckTest, TestLeakyCtor) { + absl::TestThrowingCtor(); + EXPECT_EQ(LeaksIfCtorThrows::counter, 1); + LeaksIfCtorThrows::counter = 0; +} + struct Tracked : private exceptions_internal::TrackedObject { Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {} }; diff --git a/absl/base/internal/exception_safety_testing.cc b/absl/base/internal/exception_safety_testing.cc index 383c9c595..32d904ed2 100644 --- a/absl/base/internal/exception_safety_testing.cc +++ b/absl/base/internal/exception_safety_testing.cc @@ -4,6 +4,7 @@ #include "absl/meta/type_traits.h" namespace absl { +exceptions_internal::NoThrowTag no_throw_ctor; namespace exceptions_internal { int countdown = -1; @@ -14,8 +15,7 @@ void MaybeThrow(absl::string_view msg) { testing::AssertionResult FailureMessage(const TestException& e, int countdown) noexcept { - return testing::AssertionFailure() - << "Exception number " << countdown + 1 << " thrown from " << e.what(); + return testing::AssertionFailure() << "Exception thrown from " << e.what(); } } // namespace exceptions_internal } // namespace absl diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h index d2742456f..a0a70d91d 100644 --- a/absl/base/internal/exception_safety_testing.h +++ b/absl/base/internal/exception_safety_testing.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/base/internal/pretty_function.h" +#include "absl/memory/memory.h" #include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" @@ -43,6 +45,8 @@ constexpr NoThrow operator&(NoThrow a, NoThrow b) { } namespace exceptions_internal { +struct NoThrowTag {}; + constexpr bool ThrowingAllowed(NoThrow flags, NoThrow flag) { return !static_cast(flags & flag); } @@ -92,8 +96,46 @@ class TrackedObject { friend struct ::absl::AllocInspector; }; + +template +testing::AssertionResult TestInvariants(const T& t, const TestException& e, + int count, + const Checkers&... checkers) { + auto out = AbslCheckInvariants(t); + // Don't bother with the checkers if the class invariants are already broken. + bool dummy[] = {true, + (out && (out = testing::AssertionResult(checkers(t))))...}; + static_cast(dummy); + + return out ? out + : out << " Caused by exception " << count << "thrown by " + << e.what(); +} + +template +class StrongGuaranteeTester { + public: + explicit StrongGuaranteeTester(std::unique_ptr t_ptr, EqualTo eq) noexcept + : val_(std::move(t_ptr)), eq_(eq) {} + + testing::AssertionResult operator()(const T& other) const { + return eq_(*val_, other) ? testing::AssertionSuccess() + : testing::AssertionFailure() << "State changed"; + } + + private: + std::unique_ptr val_; + EqualTo eq_; +}; } // namespace exceptions_internal +extern exceptions_internal::NoThrowTag no_throw_ctor; + +// These are useful for tests which just construct objects and make sure there +// are no leaks. +inline void SetCountdown() { exceptions_internal::countdown = 0; } +inline void UnsetCountdown() { exceptions_internal::countdown = -1; } + // A test class which is contextually convertible to bool. The conversion can // be instrumented to throw at a controlled time. class ThrowingBool { @@ -152,6 +194,9 @@ class ThrowingValue : private exceptions_internal::TrackedObject { dummy_ = i; } + ThrowingValue(int i, exceptions_internal::NoThrowTag) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(i) {} + // absl expects nothrow destructors ~ThrowingValue() noexcept = default; @@ -173,22 +218,22 @@ class ThrowingValue : private exceptions_internal::TrackedObject { // Arithmetic Operators ThrowingValue operator+(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ + other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ + other.dummy_, no_throw_ctor); } ThrowingValue operator+() const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_, NoThrowTag{}); + return ThrowingValue(dummy_, no_throw_ctor); } ThrowingValue operator-(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ - other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ - other.dummy_, no_throw_ctor); } ThrowingValue operator-() const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(-dummy_, NoThrowTag{}); + return ThrowingValue(-dummy_, no_throw_ctor); } ThrowingValue& operator++() { @@ -199,7 +244,7 @@ class ThrowingValue : private exceptions_internal::TrackedObject { ThrowingValue operator++(int) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - auto out = ThrowingValue(dummy_, NoThrowTag{}); + auto out = ThrowingValue(dummy_, no_throw_ctor); ++dummy_; return out; } @@ -212,34 +257,34 @@ class ThrowingValue : private exceptions_internal::TrackedObject { ThrowingValue operator--(int) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - auto out = ThrowingValue(dummy_, NoThrowTag{}); + auto out = ThrowingValue(dummy_, no_throw_ctor); --dummy_; return out; } ThrowingValue operator*(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ * other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ * other.dummy_, no_throw_ctor); } ThrowingValue operator/(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ / other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ / other.dummy_, no_throw_ctor); } ThrowingValue operator%(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ % other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ % other.dummy_, no_throw_ctor); } ThrowingValue operator<<(int shift) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ << shift, NoThrowTag{}); + return ThrowingValue(dummy_ << shift, no_throw_ctor); } ThrowingValue operator>>(int shift) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ >> shift, NoThrowTag{}); + return ThrowingValue(dummy_ >> shift, no_throw_ctor); } // Comparison Operators @@ -293,22 +338,22 @@ class ThrowingValue : private exceptions_internal::TrackedObject { // Bitwise Logical Operators ThrowingValue operator~() const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(~dummy_, NoThrowTag{}); + return ThrowingValue(~dummy_, no_throw_ctor); } ThrowingValue operator&(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ & other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ & other.dummy_, no_throw_ctor); } ThrowingValue operator|(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ | other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ | other.dummy_, no_throw_ctor); } ThrowingValue operator^(const ThrowingValue& other) const { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return ThrowingValue(dummy_ ^ other.dummy_, NoThrowTag{}); + return ThrowingValue(dummy_ ^ other.dummy_, no_throw_ctor); } // Compound Assignment operators @@ -434,10 +479,6 @@ class ThrowingValue : private exceptions_internal::TrackedObject { const int& Get() const noexcept { return dummy_; } private: - struct NoThrowTag {}; - ThrowingValue(int i, NoThrowTag) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(i) {} - int dummy_; }; // While not having to do with exceptions, explicitly delete comma operator, to @@ -596,7 +637,9 @@ int ThrowingAllocator::next_id_ = 0; // Inspects the constructions and destructions of anything inheriting from // TrackedObject. Place this as a member variable in a test fixture to ensure -// that every ThrowingValue was constructed and destroyed correctly. +// that every ThrowingValue was constructed and destroyed correctly. This also +// allows us to safely "leak" TrackedObjects, as AllocInspector will destroy +// everything left over in its destructor. struct AllocInspector { AllocInspector() = default; ~AllocInspector() { @@ -609,69 +652,79 @@ struct AllocInspector { } }; -// Tests that performing operation Op on a T follows the basic exception safety -// guarantee. +// Tests for resource leaks by attempting to construct a T using args repeatedly +// until successful, using the countdown method. Side effects can then be +// tested for resource leaks. If an AllocInspector is present in the test +// fixture, then this will also test that memory resources are not leaked as +// long as T allocates TrackedObjects. +template +T TestThrowingCtor(Args&&... args) { + struct Cleanup { + ~Cleanup() { UnsetCountdown(); } + }; + Cleanup c; + for (int countdown = 0;; ++countdown) { + exceptions_internal::countdown = countdown; + try { + return T(std::forward(args)...); + } catch (const exceptions_internal::TestException&) { + } + } +} + +// Tests that performing operation Op on a T follows exception safety +// guarantees. By default only tests the basic guarantee. // // Parameters: // * T: the type under test. // * FunctionFromTPtrToVoid: A functor exercising the function under test. It -// should take a T* and return void. -// -// There must also be a function named `AbslCheckInvariants` in an associated -// namespace of T which takes a const T& and returns true if the T's class -// invariants hold, and false if they don't. -template -testing::AssertionResult TestBasicGuarantee(T* t, FunctionFromTPtrToVoid&& op) { +// should take a T* and return void. +// * Checkers: Any number of functions taking a const T& and returning +// anything contextually convertible to bool. If a testing::AssertionResult +// is used then the error message is kept. These test invariants related to +// the operation. To test the strong guarantee, pass +// absl::StrongGuarantee(...) as one of these arguments if T has operator==. +// Some types for which the strong guarantee makes sense don't have operator== +// (eg std::any). A function capturing *t or a T equal to it, taking a const +// T&, and returning contextually-convertible-to-bool may be passed instead. +template +testing::AssertionResult TestExceptionSafety(T* t, FunctionFromTPtrToVoid&& op, + const Checkers&... checkers) { + auto out = testing::AssertionSuccess(); for (int countdown = 0;; ++countdown) { exceptions_internal::countdown = countdown; try { op(t); break; } catch (const exceptions_internal::TestException& e) { - if (!AbslCheckInvariants(*t)) { - return exceptions_internal::FailureMessage(e, countdown) - << " broke invariants."; - } + out = exceptions_internal::TestInvariants(*t, e, countdown, checkers...); + if (!out) return out; } } - exceptions_internal::countdown = -1; - return testing::AssertionSuccess(); + UnsetCountdown(); + return out; } -// Tests that performing operation Op on a T follows the strong exception safety -// guarantee. +// Returns a functor to test for the strong exception-safety guarantee. If T is +// copyable, use the const T& overload, otherwise pass a unique_ptr. +// Equality comparisons are made against the T provided and default to using +// operator==. See the documentation for TestExceptionSafety if T doesn't have +// operator== but the strong guarantee still makes sense for it. // // Parameters: -// * T: the type under test. T must be copy-constructable and -// equality-comparible. -// * FunctionFromTPtrToVoid: A functor exercising the function under test. It -// should take a T* and return void. -// -// There must also be a function named `AbslCheckInvariants` in an associated -// namespace of T which takes a const T& and returns true if the T's class -// invariants hold, and false if they don't. -template -testing::AssertionResult TestStrongGuarantee(T* t, - FunctionFromTPtrToVoid&& op) { - exceptions_internal::countdown = -1; - for (auto countdown = 0;; ++countdown) { - T dup = *t; - exceptions_internal::countdown = countdown; - try { - op(t); - break; - } catch (const exceptions_internal::TestException& e) { - if (!AbslCheckInvariants(*t)) { - return exceptions_internal::FailureMessage(e, countdown) - << " broke invariants."; - } - if (dup != *t) - return exceptions_internal::FailureMessage(e, countdown) - << " changed state."; - } - } - exceptions_internal::countdown = -1; - return testing::AssertionSuccess(); +// * T: The type under test. +template > +exceptions_internal::StrongGuaranteeTester StrongGuarantee( + const T& t, EqualTo eq = EqualTo()) { + return exceptions_internal::StrongGuaranteeTester( + absl::make_unique(t), eq); +} + +template > +exceptions_internal::StrongGuaranteeTester PointeeStrongGuarantee( + std::unique_ptr t_ptr, EqualTo eq = EqualTo()) { + return exceptions_internal::StrongGuaranteeTester( + std::move(t_ptr), eq); } } // namespace absl diff --git a/absl/strings/strip_test.cc b/absl/strings/strip_test.cc index 3c9e726ee..ff0e7f1c2 100644 --- a/absl/strings/strip_test.cc +++ b/absl/strings/strip_test.cc @@ -116,4 +116,66 @@ TEST(Strip, StripSuffix) { EXPECT_EQ(absl::StripSuffix("", ""), ""); } +TEST(Strip, RemoveExtraAsciiWhitespace) { + const char* inputs[] = { + "No extra space", + " Leading whitespace", + "Trailing whitespace ", + " Leading and trailing ", + " Whitespace \t in\v middle ", + "'Eeeeep! \n Newlines!\n", + "nospaces", + }; + const char* outputs[] = { + "No extra space", + "Leading whitespace", + "Trailing whitespace", + "Leading and trailing", + "Whitespace in middle", + "'Eeeeep! Newlines!", + "nospaces", + }; + int NUM_TESTS = 7; + + for (int i = 0; i < NUM_TESTS; i++) { + std::string s(inputs[i]); + absl::RemoveExtraAsciiWhitespace(&s); + EXPECT_STREQ(outputs[i], s.c_str()); + } + + // Test that absl::RemoveExtraAsciiWhitespace returns immediately for empty + // strings (It was adding the \0 character to the C++ std::string, which broke + // tests involving empty()) + std::string zero_string = ""; + assert(zero_string.empty()); + absl::RemoveExtraAsciiWhitespace(&zero_string); + EXPECT_EQ(zero_string.size(), 0); + EXPECT_TRUE(zero_string.empty()); +} + +TEST(Strip, StripTrailingAsciiWhitespace) { + std::string test = "foo "; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, "foo"); + + test = " "; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, ""); + + test = ""; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, ""); + + test = " abc\t"; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, " abc"); +} + +TEST(String, StripLeadingAsciiWhitespace) { + absl::string_view orig = "\t \n\f\r\n\vfoo"; + EXPECT_EQ("foo", absl::StripLeadingAsciiWhitespace(orig)); + orig = "\t \n\f\r\v\n\t \n\f\r\v\n"; + EXPECT_EQ(absl::string_view(), absl::StripLeadingAsciiWhitespace(orig)); +} + } // namespace diff --git a/absl/time/duration.cc b/absl/time/duration.cc index f5081955d..5e4f93136 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -672,7 +672,7 @@ namespace { char* Format64(char* ep, int width, int64_t v) { do { --width; - *--ep = "0123456789"[v % 10]; + *--ep = '0' + (v % 10); // contiguous digits } while (v /= 10); while (--width >= 0) *--ep = '0'; // zero pad return ep; @@ -782,15 +782,32 @@ std::string FormatDuration(Duration d) { namespace { // A helper for ParseDuration() that parses a leading number from the given -// std::string and stores the result in *n. The given std::string pointer is modified -// to point to the first unconsumed char. -bool ConsumeDurationNumber(const char** start, double* n) { - const char* s = *start; - char* end = nullptr; - errno = 0; - *n = strtod(s, &end); - *start = end; - return !std::isspace(*s) && errno == 0 && end != s && *n >= 0; +// std::string and stores the result in *int_part/*frac_part/*frac_scale. The +// given std::string pointer is modified to point to the first unconsumed char. +bool ConsumeDurationNumber(const char** dpp, int64_t* int_part, + int64_t* frac_part, int64_t* frac_scale) { + *int_part = 0; + *frac_part = 0; + *frac_scale = 1; // invariant: *frac_part < *frac_scale + const char* start = *dpp; + for (; std::isdigit(**dpp); *dpp += 1) { + const int d = **dpp - '0'; // contiguous digits + if (*int_part > kint64max / 10) return false; + *int_part *= 10; + if (*int_part > kint64max - d) return false; + *int_part += d; + } + const bool int_part_empty = (*dpp == start); + if (**dpp != '.') return !int_part_empty; + for (*dpp += 1; std::isdigit(**dpp); *dpp += 1) { + const int d = **dpp - '0'; // contiguous digits + if (*frac_scale <= kint64max / 10) { + *frac_part *= 10; + *frac_part += d; + *frac_scale *= 10; + } + } + return !int_part_empty || *frac_scale != 1; } // A helper for ParseDuration() that parses a leading unit designator (e.g., @@ -859,13 +876,16 @@ bool ParseDuration(const std::string& dur_string, Duration* d) { Duration dur; while (*start != '\0') { - double n = 0; + int64_t int_part; + int64_t frac_part; + int64_t frac_scale; Duration unit; - if (!ConsumeDurationNumber(&start, &n) || + if (!ConsumeDurationNumber(&start, &int_part, &frac_part, &frac_scale) || !ConsumeDurationUnit(&start, &unit)) { return false; } - dur += sign * n * unit; + if (int_part != 0) dur += sign * int_part * unit; + if (frac_part != 0) dur += sign * frac_part * unit / frac_scale; } *d = dur; return true; diff --git a/absl/time/duration_test.cc b/absl/time/duration_test.cc index 7f4522874..7918e1677 100644 --- a/absl/time/duration_test.cc +++ b/absl/time/duration_test.cc @@ -1573,6 +1573,7 @@ TEST(Duration, ParseDuration) { EXPECT_FALSE(absl::ParseDuration("2s ", &d)); EXPECT_FALSE(absl::ParseDuration(" 2s ", &d)); EXPECT_FALSE(absl::ParseDuration("2mt", &d)); + EXPECT_FALSE(absl::ParseDuration("1e3s", &d)); // One unit type. EXPECT_TRUE(absl::ParseDuration("1ns", &d)); @@ -1588,6 +1589,12 @@ TEST(Duration, ParseDuration) { EXPECT_TRUE(absl::ParseDuration("2h", &d)); EXPECT_EQ(absl::Hours(2), d); + // Huge counts of a unit. + EXPECT_TRUE(absl::ParseDuration("9223372036854775807us", &d)); + EXPECT_EQ(absl::Microseconds(9223372036854775807), d); + EXPECT_TRUE(absl::ParseDuration("-9223372036854775807us", &d)); + EXPECT_EQ(absl::Microseconds(-9223372036854775807), d); + // Multiple units. EXPECT_TRUE(absl::ParseDuration("2h3m4s", &d)); EXPECT_EQ(absl::Hours(2) + absl::Minutes(3) + absl::Seconds(4), d); @@ -1619,6 +1626,12 @@ TEST(Duration, ParseDuration) { EXPECT_TRUE(absl::ParseDuration("1.5h", &d)); EXPECT_EQ(1.5 * absl::Hours(1), d); + // Huge fractional counts of a unit. + EXPECT_TRUE(absl::ParseDuration("0.4294967295s", &d)); + EXPECT_EQ(absl::Nanoseconds(429496729) + absl::Nanoseconds(1) / 2, d); + EXPECT_TRUE(absl::ParseDuration("0.429496729501234567890123456789s", &d)); + EXPECT_EQ(absl::Nanoseconds(429496729) + absl::Nanoseconds(1) / 2, d); + // Negative durations. EXPECT_TRUE(absl::ParseDuration("-1s", &d)); EXPECT_EQ(absl::Seconds(-1), d);