[libcxx-commits] [libcxx] [libc++][test] Add exception tests for vector capacity operations (PR #118141)
Peng Liu via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Dec 16 08:10:15 PST 2024
================
@@ -0,0 +1,314 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: no-exceptions
+
+// This test file validates that std::vector<T>::reserve provides a strong exception guarantee if T is
+// Cpp17MoveInsertible and no exception is thrown by the move constructor of T during the reserve call.
+// It also checks that if T's move constructor is not noexcept, reserve provides only a basic exception
+// guarantee.
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "MoveOnly.h"
+#include "count_new.h"
+#include "increasing_allocator.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <typename T, typename Alloc>
+void test_allocation_exception_for_strong_guarantee(
+ std::vector<T, Alloc>& v, const std::vector<T>& values, std::size_t new_cap) {
+ assert(v.size() == values.size());
+ T* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+
+ try {
+ v.reserve(new_cap);
+ } catch (...) { // std::length_error, std::bad_alloc
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i] == values[i]);
+ }
+}
+
+template <typename T, typename Alloc>
+void test_copy_ctor_exception_for_strong_guarantee(std::vector<throwing_data<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ throwing_data<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_cap = 2 * old_cap;
+
+ try {
+ v.reserve(new_cap);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i].data_ == values[i]);
+ }
+}
+
+#if TEST_STD_VER >= 11
+
+template <typename T, typename Alloc>
+void test_move_ctor_exception_for_basic_guarantee(std::vector<move_only_throwing_t<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ try {
+ v.reserve(2 * v.capacity());
+ } catch (...) {
+ // After a failure during element-wise move, the vector elements are left in a valid but unspecified state.
+ for (std::size_t i = 0; i < v.size(); ++i) {
+ assert(((void)v[i],
+ (void)v[i].data_,
+ (void)v[i].throw_after_n_,
+ (void)(v[i] == v[i]),
+ v[i].moved_from_ || v[i].data_ == values[i]));
+ }
+ }
+}
+
+#endif
+
+// Check the strong exception guarantee during reallocation failures
+void test_allocation_exceptions() {
+ //
+ // Tests for std::length_error during reallocation failures
+ //
+ {
+ std::vector<int> v;
+ test_allocation_exception_for_strong_guarantee(v, std::vector<int>(), v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, min_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, safe_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, test_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<int, increasing_allocator<int>> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+#endif
+
+ //
+ // Tests for std::bad_alloc during reallocation failures
+ //
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ v.reserve(30);
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 11
+ {
+ std::vector<MoveOnly> in(10);
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<MoveOnly> in(10);
+ in.insert(in.cbegin() + 5, MoveOnly(42));
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ v.reserve(30);
+ v.insert(v.cbegin() + 5, MoveOnly(42));
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
----------------
winner245 wrote:
Could you please give me some feedback on `test_move_ctor_exception_for_basic_guarantee()`? This test addresses exceptions in the move constructor of `T`, where the strong exception guarantee for these operations is waived. I'm not entirely sure if my testing approach is 100% correct, although all CIs are green.
https://github.com/llvm/llvm-project/pull/118141
More information about the libcxx-commits
mailing list