Export of internal Abseil changes.
-- 441d1aa02483cdc510eb2fef012b31384fd8e3a6 by Eric Fiselier <ericwf@google.com>: Fix str_format with non-POSIX libc implementations. PiperOrigin-RevId: 218441122 -- da6190130e74222af6eb161a5593364341370370 by Jon Cohen <cohenjon@google.com>: Refactor ExceptionSafetyTester::Test in order to remove the levels of indirection related to unpacking tuples. PiperOrigin-RevId: 218403355 GitOrigin-RevId: 441d1aa02483cdc510eb2fef012b31384fd8e3a6 Change-Id: I6f6b978eb96fe261e8ee41ecdce185e5356a601d
This commit is contained in:
parent
8efc526087
commit
94c298e2a0
5 changed files with 154 additions and 136 deletions
|
@ -770,6 +770,18 @@ TEST(ExceptionCheckTest, ModifyingChecker) {
|
|||
.Test(invoker));
|
||||
}
|
||||
|
||||
TEST(ExceptionSafetyTesterTest, ResetsCountdown) {
|
||||
auto test =
|
||||
testing::MakeExceptionSafetyTester()
|
||||
.WithInitialValue(ThrowingValue<>())
|
||||
.WithContracts([](ThrowingValue<>*) { return AssertionSuccess(); })
|
||||
.WithOperation([](ThrowingValue<>*) {});
|
||||
ASSERT_TRUE(test.Test());
|
||||
// If the countdown isn't reset because there were no exceptions thrown, then
|
||||
// this will fail with a termination from an unhandled exception
|
||||
EXPECT_TRUE(test.Test());
|
||||
}
|
||||
|
||||
struct NonCopyable : public NonNegative {
|
||||
NonCopyable(const NonCopyable&) = delete;
|
||||
NonCopyable() : NonNegative{0} {}
|
||||
|
|
|
@ -23,6 +23,10 @@ exceptions_internal::NoThrowTag nothrow_ctor;
|
|||
|
||||
exceptions_internal::StrongGuaranteeTagType strong_guarantee;
|
||||
|
||||
exceptions_internal::ExceptionSafetyTestBuilder<> MakeExceptionSafetyTester() {
|
||||
return {};
|
||||
}
|
||||
|
||||
namespace exceptions_internal {
|
||||
|
||||
int countdown = -1;
|
||||
|
|
|
@ -190,70 +190,6 @@ class TrackedObject {
|
|||
|
||||
~TrackedObject() noexcept { ConstructorTracker::ObjectDestructed(this); }
|
||||
};
|
||||
|
||||
template <typename Factory, typename Operation, typename Contract>
|
||||
absl::optional<testing::AssertionResult> TestSingleContractAtCountdownImpl(
|
||||
const Factory& factory, const Operation& operation, int count,
|
||||
const Contract& contract) {
|
||||
auto t_ptr = factory();
|
||||
absl::optional<testing::AssertionResult> current_res;
|
||||
SetCountdown(count);
|
||||
try {
|
||||
operation(t_ptr.get());
|
||||
} catch (const exceptions_internal::TestException& e) {
|
||||
current_res.emplace(contract(t_ptr.get()));
|
||||
if (!current_res.value()) {
|
||||
*current_res << e.what() << " failed contract check";
|
||||
}
|
||||
}
|
||||
UnsetCountdown();
|
||||
return current_res;
|
||||
}
|
||||
|
||||
template <typename Factory, typename Operation>
|
||||
absl::optional<testing::AssertionResult> TestSingleContractAtCountdownImpl(
|
||||
const Factory& factory, const Operation& operation, int count,
|
||||
StrongGuaranteeTagType) {
|
||||
using TPtr = typename decltype(factory())::pointer;
|
||||
auto t_is_strong = [&](TPtr t) { return *t == *factory(); };
|
||||
return TestSingleContractAtCountdownImpl(factory, operation, count,
|
||||
t_is_strong);
|
||||
}
|
||||
|
||||
template <typename Factory, typename Operation, typename Contract>
|
||||
int TestSingleContractAtCountdown(
|
||||
const Factory& factory, const Operation& operation, int count,
|
||||
const Contract& contract,
|
||||
absl::optional<testing::AssertionResult>* reduced_res) {
|
||||
// If reduced_res is empty, it means the current call to
|
||||
// TestSingleContractAtCountdown(...) is the first test being run so we do
|
||||
// want to run it. Alternatively, if it's not empty (meaning a previous test
|
||||
// has run) we want to check if it passed. If the previous test did pass, we
|
||||
// want to contine running tests so we do want to run the current one. If it
|
||||
// failed, we want to short circuit so as not to overwrite the AssertionResult
|
||||
// output. If that's the case, we do not run the current test and instead we
|
||||
// simply return.
|
||||
if (!reduced_res->has_value() || reduced_res->value()) {
|
||||
*reduced_res =
|
||||
TestSingleContractAtCountdownImpl(factory, operation, count, contract);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Factory, typename Operation, typename... Contracts>
|
||||
inline absl::optional<testing::AssertionResult> TestAllContractsAtCountdown(
|
||||
const Factory& factory, const Operation& operation, int count,
|
||||
const Contracts&... contracts) {
|
||||
absl::optional<testing::AssertionResult> reduced_res;
|
||||
|
||||
// Run each checker, short circuiting after the first failure
|
||||
int dummy[] = {
|
||||
0, (TestSingleContractAtCountdown(factory, operation, count, contracts,
|
||||
&reduced_res))...};
|
||||
static_cast<void>(dummy);
|
||||
return reduced_res;
|
||||
}
|
||||
|
||||
} // namespace exceptions_internal
|
||||
|
||||
extern exceptions_internal::NoThrowTag nothrow_ctor;
|
||||
|
@ -871,7 +807,7 @@ testing::AssertionResult TestNothrowOp(const Operation& operation) {
|
|||
|
||||
namespace exceptions_internal {
|
||||
|
||||
// Dummy struct for ExceptionSafetyTester<> partial state.
|
||||
// Dummy struct for ExceptionSafetyTestBuilder<> partial state.
|
||||
struct UninitializedT {};
|
||||
|
||||
template <typename T>
|
||||
|
@ -893,20 +829,97 @@ using EnableIfTestable = typename absl::enable_if_t<
|
|||
|
||||
template <typename Factory = UninitializedT,
|
||||
typename Operation = UninitializedT, typename... Contracts>
|
||||
class ExceptionSafetyTester;
|
||||
class ExceptionSafetyTestBuilder;
|
||||
|
||||
} // namespace exceptions_internal
|
||||
|
||||
exceptions_internal::ExceptionSafetyTester<> MakeExceptionSafetyTester();
|
||||
/*
|
||||
* Constructs an empty ExceptionSafetyTestBuilder. All
|
||||
* ExceptionSafetyTestBuilder objects are immutable and all With[thing] mutation
|
||||
* methods return new instances of ExceptionSafetyTestBuilder.
|
||||
*
|
||||
* In order to test a T for exception safety, a factory for that T, a testable
|
||||
* operation, and at least one contract callback returning an assertion
|
||||
* result must be applied using the respective methods.
|
||||
*/
|
||||
exceptions_internal::ExceptionSafetyTestBuilder<> MakeExceptionSafetyTester();
|
||||
|
||||
namespace exceptions_internal {
|
||||
template <typename T>
|
||||
struct IsUniquePtr : std::false_type {};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct IsUniquePtr<std::unique_ptr<T, D>> : std::true_type {};
|
||||
|
||||
template <typename Factory>
|
||||
struct FactoryPtrTypeHelper {
|
||||
using type = decltype(std::declval<const Factory&>()());
|
||||
|
||||
static_assert(IsUniquePtr<type>::value, "Factories must return a unique_ptr");
|
||||
};
|
||||
|
||||
template <typename Factory>
|
||||
using FactoryPtrType = typename FactoryPtrTypeHelper<Factory>::type;
|
||||
|
||||
template <typename Factory>
|
||||
using FactoryElementType = typename FactoryPtrType<Factory>::element_type;
|
||||
|
||||
template <typename T>
|
||||
class ExceptionSafetyTest {
|
||||
using Factory = std::function<std::unique_ptr<T>()>;
|
||||
using Operation = std::function<void(T*)>;
|
||||
using Contract = std::function<AssertionResult(T*)>;
|
||||
|
||||
public:
|
||||
template <typename... Contracts>
|
||||
explicit ExceptionSafetyTest(const Factory& f, const Operation& op,
|
||||
const Contracts&... contracts)
|
||||
: factory_(f), operation_(op), contracts_{WrapContract(contracts)...} {}
|
||||
|
||||
AssertionResult Test() const {
|
||||
for (int count = 0;; ++count) {
|
||||
exceptions_internal::ConstructorTracker ct(count);
|
||||
|
||||
for (const auto& contract : contracts_) {
|
||||
auto t_ptr = factory_();
|
||||
try {
|
||||
SetCountdown(count);
|
||||
operation_(t_ptr.get());
|
||||
// Unset for the case that the operation throws no exceptions, which
|
||||
// would leave the countdown set and break the *next* exception safety
|
||||
// test after this one.
|
||||
UnsetCountdown();
|
||||
return AssertionSuccess();
|
||||
} catch (const exceptions_internal::TestException& e) {
|
||||
if (!contract(t_ptr.get())) {
|
||||
return AssertionFailure() << e.what() << " failed contract check";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename ContractFn>
|
||||
Contract WrapContract(const ContractFn& contract) {
|
||||
return [contract](T* t_ptr) { return AssertionResult(contract(t_ptr)); };
|
||||
}
|
||||
|
||||
Contract WrapContract(StrongGuaranteeTagType) {
|
||||
return [this](T* t_ptr) { return AssertionResult(*factory_() == *t_ptr); };
|
||||
}
|
||||
|
||||
Factory factory_;
|
||||
Operation operation_;
|
||||
std::vector<Contract> contracts_;
|
||||
};
|
||||
|
||||
/*
|
||||
* Builds a tester object that tests if performing a operation on a T follows
|
||||
* exception safety guarantees. Verification is done via contract assertion
|
||||
* callbacks applied to T instances post-throw.
|
||||
*
|
||||
* Template parameters for ExceptionSafetyTester:
|
||||
* Template parameters for ExceptionSafetyTestBuilder:
|
||||
*
|
||||
* - Factory: The factory object (passed in via tester.WithFactory(...) or
|
||||
* tester.WithInitialValue(...)) must be invocable with the signature
|
||||
|
@ -933,13 +946,13 @@ namespace exceptions_internal {
|
|||
* please.
|
||||
*/
|
||||
template <typename Factory, typename Operation, typename... Contracts>
|
||||
class ExceptionSafetyTester {
|
||||
class ExceptionSafetyTestBuilder {
|
||||
public:
|
||||
/*
|
||||
* Returns a new ExceptionSafetyTester with an included T factory based on the
|
||||
* provided T instance. The existing factory will not be included in the newly
|
||||
* created tester instance. The created factory returns a new T instance by
|
||||
* copy-constructing the provided const T& t.
|
||||
* Returns a new ExceptionSafetyTestBuilder with an included T factory based
|
||||
* on the provided T instance. The existing factory will not be included in
|
||||
* the newly created tester instance. The created factory returns a new T
|
||||
* instance by copy-constructing the provided const T& t.
|
||||
*
|
||||
* Preconditions for tester.WithInitialValue(const T& t):
|
||||
*
|
||||
|
@ -948,41 +961,41 @@ class ExceptionSafetyTester {
|
|||
* tester.WithFactory(...).
|
||||
*/
|
||||
template <typename T>
|
||||
ExceptionSafetyTester<DefaultFactory<T>, Operation, Contracts...>
|
||||
ExceptionSafetyTestBuilder<DefaultFactory<T>, Operation, Contracts...>
|
||||
WithInitialValue(const T& t) const {
|
||||
return WithFactory(DefaultFactory<T>(t));
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a new ExceptionSafetyTester with the provided T factory included.
|
||||
* The existing factory will not be included in the newly-created tester
|
||||
* instance. This method is intended for use with types lacking a copy
|
||||
* Returns a new ExceptionSafetyTestBuilder with the provided T factory
|
||||
* included. The existing factory will not be included in the newly-created
|
||||
* tester instance. This method is intended for use with types lacking a copy
|
||||
* constructor. Types that can be copy-constructed should instead use the
|
||||
* method tester.WithInitialValue(...).
|
||||
*/
|
||||
template <typename NewFactory>
|
||||
ExceptionSafetyTester<absl::decay_t<NewFactory>, Operation, Contracts...>
|
||||
ExceptionSafetyTestBuilder<absl::decay_t<NewFactory>, Operation, Contracts...>
|
||||
WithFactory(const NewFactory& new_factory) const {
|
||||
return {new_factory, operation_, contracts_};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a new ExceptionSafetyTester with the provided testable operation
|
||||
* included. The existing operation will not be included in the newly created
|
||||
* tester.
|
||||
* Returns a new ExceptionSafetyTestBuilder with the provided testable
|
||||
* operation included. The existing operation will not be included in the
|
||||
* newly created tester.
|
||||
*/
|
||||
template <typename NewOperation>
|
||||
ExceptionSafetyTester<Factory, absl::decay_t<NewOperation>, Contracts...>
|
||||
ExceptionSafetyTestBuilder<Factory, absl::decay_t<NewOperation>, Contracts...>
|
||||
WithOperation(const NewOperation& new_operation) const {
|
||||
return {factory_, new_operation, contracts_};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a new ExceptionSafetyTester with the provided MoreContracts...
|
||||
* Returns a new ExceptionSafetyTestBuilder with the provided MoreContracts...
|
||||
* combined with the Contracts... that were already included in the instance
|
||||
* on which the method was called. Contracts... cannot be removed or replaced
|
||||
* once added to an ExceptionSafetyTester instance. A fresh object must be
|
||||
* created in order to get an empty Contracts... list.
|
||||
* once added to an ExceptionSafetyTestBuilder instance. A fresh object must
|
||||
* be created in order to get an empty Contracts... list.
|
||||
*
|
||||
* In addition to passing in custom contract assertion callbacks, this method
|
||||
* accepts `testing::strong_guarantee` as an argument which checks T instances
|
||||
|
@ -991,7 +1004,7 @@ class ExceptionSafetyTester {
|
|||
* properly rolled back.
|
||||
*/
|
||||
template <typename... MoreContracts>
|
||||
ExceptionSafetyTester<Factory, Operation, Contracts...,
|
||||
ExceptionSafetyTestBuilder<Factory, Operation, Contracts...,
|
||||
absl::decay_t<MoreContracts>...>
|
||||
WithContracts(const MoreContracts&... more_contracts) const {
|
||||
return {
|
||||
|
@ -1039,48 +1052,27 @@ class ExceptionSafetyTester {
|
|||
typename LazyOperation = Operation,
|
||||
typename = EnableIfTestable<sizeof...(Contracts), Factory, LazyOperation>>
|
||||
testing::AssertionResult Test() const {
|
||||
return TestImpl(operation_, absl::index_sequence_for<Contracts...>());
|
||||
return Test(operation_);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename, typename...>
|
||||
friend class ExceptionSafetyTester;
|
||||
friend class ExceptionSafetyTestBuilder;
|
||||
|
||||
friend ExceptionSafetyTester<> testing::MakeExceptionSafetyTester();
|
||||
friend ExceptionSafetyTestBuilder<> testing::MakeExceptionSafetyTester();
|
||||
|
||||
ExceptionSafetyTester() {}
|
||||
ExceptionSafetyTestBuilder() {}
|
||||
|
||||
ExceptionSafetyTester(const Factory& f, const Operation& o,
|
||||
ExceptionSafetyTestBuilder(const Factory& f, const Operation& o,
|
||||
const std::tuple<Contracts...>& i)
|
||||
: factory_(f), operation_(o), contracts_(i) {}
|
||||
|
||||
template <typename SelectedOperation, size_t... Indices>
|
||||
testing::AssertionResult TestImpl(const SelectedOperation& selected_operation,
|
||||
testing::AssertionResult TestImpl(SelectedOperation selected_operation,
|
||||
absl::index_sequence<Indices...>) const {
|
||||
// Starting from 0 and counting upwards until one of the exit conditions is
|
||||
// hit...
|
||||
for (int count = 0;; ++count) {
|
||||
exceptions_internal::ConstructorTracker ct(count);
|
||||
|
||||
// Run the full exception safety test algorithm for the current countdown
|
||||
auto reduced_res =
|
||||
TestAllContractsAtCountdown(factory_, selected_operation, count,
|
||||
std::get<Indices>(contracts_)...);
|
||||
// If there is no value in the optional, no contracts were run because no
|
||||
// exception was thrown. This means that the test is complete and the loop
|
||||
// can exit successfully.
|
||||
if (!reduced_res.has_value()) {
|
||||
return testing::AssertionSuccess();
|
||||
}
|
||||
// If the optional is not empty and the value is falsy, an contract check
|
||||
// failed so the test must exit to propegate the failure.
|
||||
if (!reduced_res.value()) {
|
||||
return reduced_res.value();
|
||||
}
|
||||
// If the optional is not empty and the value is not falsy, it means
|
||||
// exceptions were thrown but the contracts passed so the test must
|
||||
// continue to run.
|
||||
}
|
||||
return ExceptionSafetyTest<FactoryElementType<Factory>>(
|
||||
factory_, selected_operation, std::get<Indices>(contracts_)...)
|
||||
.Test();
|
||||
}
|
||||
|
||||
Factory factory_;
|
||||
|
@ -1090,20 +1082,6 @@ class ExceptionSafetyTester {
|
|||
|
||||
} // namespace exceptions_internal
|
||||
|
||||
/*
|
||||
* Constructs an empty ExceptionSafetyTester. All ExceptionSafetyTester
|
||||
* objects are immutable and all With[thing] mutation methods return new
|
||||
* instances of ExceptionSafetyTester.
|
||||
*
|
||||
* In order to test a T for exception safety, a factory for that T, a testable
|
||||
* operation, and at least one contract callback returning an assertion
|
||||
* result must be applied using the respective methods.
|
||||
*/
|
||||
inline exceptions_internal::ExceptionSafetyTester<>
|
||||
MakeExceptionSafetyTester() {
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_
|
||||
|
|
|
@ -20,6 +20,16 @@
|
|||
namespace absl {
|
||||
namespace str_format_internal {
|
||||
|
||||
namespace {
|
||||
struct ClearErrnoGuard {
|
||||
ClearErrnoGuard() : old_value(errno) { errno = 0; }
|
||||
~ClearErrnoGuard() {
|
||||
if (!errno) errno = old_value;
|
||||
}
|
||||
int old_value;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void BufferRawSink::Write(string_view v) {
|
||||
size_t to_write = std::min(v.size(), size_);
|
||||
std::memcpy(buffer_, v.data(), to_write);
|
||||
|
@ -30,14 +40,27 @@ void BufferRawSink::Write(string_view v) {
|
|||
|
||||
void FILERawSink::Write(string_view v) {
|
||||
while (!v.empty() && !error_) {
|
||||
// Reset errno to zero in case the libc implementation doesn't set errno
|
||||
// when a failure occurs.
|
||||
ClearErrnoGuard guard;
|
||||
|
||||
if (size_t result = std::fwrite(v.data(), 1, v.size(), output_)) {
|
||||
// Some progress was made.
|
||||
count_ += result;
|
||||
v.remove_prefix(result);
|
||||
} else {
|
||||
// Some error occurred.
|
||||
if (errno != EINTR) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else if (errno) {
|
||||
error_ = errno;
|
||||
} else if (std::ferror(output_)) {
|
||||
// Non-POSIX compliant libc implementations may not set errno, so we
|
||||
// have check the streams error indicator.
|
||||
error_ = EBADF;
|
||||
} else {
|
||||
// We're likely on a non-POSIX system that encountered EINTR but had no
|
||||
// way of reporting it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,6 +242,7 @@ class TempFile {
|
|||
std::string ReadFile() {
|
||||
std::fseek(file_, 0, SEEK_END);
|
||||
int size = std::ftell(file_);
|
||||
EXPECT_GT(size, 0);
|
||||
std::rewind(file_);
|
||||
std::string str(2 * size, ' ');
|
||||
int read_bytes = std::fread(&str[0], 1, str.size(), file_);
|
||||
|
@ -270,7 +271,7 @@ TEST_F(FormatEntryPointTest, FPrintFError) {
|
|||
EXPECT_EQ(errno, EBADF);
|
||||
}
|
||||
|
||||
#if __GNUC__
|
||||
#if __GLIBC__
|
||||
TEST_F(FormatEntryPointTest, FprintfTooLarge) {
|
||||
std::FILE* f = std::fopen("/dev/null", "w");
|
||||
int width = 2000000000;
|
||||
|
@ -297,7 +298,7 @@ TEST_F(FormatEntryPointTest, PrintF) {
|
|||
EXPECT_EQ(result, 30);
|
||||
EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
|
||||
}
|
||||
#endif // __GNUC__
|
||||
#endif // __GLIBC__
|
||||
|
||||
TEST_F(FormatEntryPointTest, SNPrintF) {
|
||||
char buffer[16];
|
||||
|
|
Loading…
Reference in a new issue