blob: 052a2d8a8c3bcc22497ad7ceded9085907f5800c [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/containers/vector_buffer.h"
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/copy_only_int.h"
#include "base/test/move_only_int.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::internal {
namespace {
class TRIVIAL_ABI TrivialAbiWithCountingOperations {
public:
TrivialAbiWithCountingOperations(int* destruction_counter, int* move_counter)
: destruction_counter_(destruction_counter),
move_counter_(move_counter) {}
~TrivialAbiWithCountingOperations() { ++*destruction_counter_; }
// Copy construction and assignment should not be used.
TrivialAbiWithCountingOperations(const TrivialAbiWithCountingOperations&) =
delete;
TrivialAbiWithCountingOperations& operator=(
const TrivialAbiWithCountingOperations&) = delete;
// Count how many times the move constructor is used.
TrivialAbiWithCountingOperations(TrivialAbiWithCountingOperations&& rhs)
: destruction_counter_(rhs.destruction_counter_),
move_counter_(rhs.move_counter_) {
++*move_counter_;
}
// Move assignment should not be used.
TrivialAbiWithCountingOperations& operator=(
TrivialAbiWithCountingOperations&&) = delete;
private:
raw_ptr<int> destruction_counter_;
raw_ptr<int> move_counter_;
};
} // namespace
TEST(VectorBuffer, DeletePOD) {
constexpr int size = 10;
VectorBuffer<int> buffer(size);
for (int i = 0; i < size; i++) {
buffer[i] = i + 1;
}
VectorBuffer<int>::DestructRange(buffer.as_span());
// Delete should do nothing.
for (int i = 0; i < size; i++) {
EXPECT_EQ(i + 1, buffer[i]);
}
}
TEST(VectorBuffer, DeleteMoveOnly) {
constexpr int size = 10;
VectorBuffer<MoveOnlyInt> buffer(size);
for (int i = 0; i < size; i++) {
// SAFETY: `i < size`, and `size` is the buffer's allocation size, so
// `begin() + i` is inside the buffer.
new (UNSAFE_BUFFERS(buffer.begin() + i)) MoveOnlyInt(i + 1);
}
std::vector<int> destroyed_instances;
auto scoped_callback_cleanup =
MoveOnlyInt::SetScopedDestructionCallback(BindLambdaForTesting(
[&](int value) { destroyed_instances.push_back(value); }));
VectorBuffer<MoveOnlyInt>::DestructRange(buffer.as_span());
EXPECT_THAT(destroyed_instances,
::testing::ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}
TEST(VectorBuffer, PODMove) {
constexpr int size = 10;
VectorBuffer<int> dest(size);
VectorBuffer<int> original(size);
for (int i = 0; i < size; i++) {
original[i] = i + 1;
}
VectorBuffer<int>::MoveConstructRange(original.as_span(), dest.as_span());
for (int i = 0; i < size; i++) {
EXPECT_EQ(i + 1, dest[i]);
}
}
TEST(VectorBuffer, MovableMove) {
constexpr int size = 10;
VectorBuffer<MoveOnlyInt> dest(size);
VectorBuffer<MoveOnlyInt> original(size);
for (int i = 0; i < size; i++) {
// SAFETY: `i < size`, and `size` is the buffer's allocation size, so
// `begin() + i` is inside the buffer.
new (UNSAFE_BUFFERS(original.begin() + i)) MoveOnlyInt(i + 1);
}
std::vector<int> destroyed_instances;
auto scoped_callback_cleanup =
MoveOnlyInt::SetScopedDestructionCallback(BindLambdaForTesting(
[&](int value) { destroyed_instances.push_back(value); }));
VectorBuffer<MoveOnlyInt>::MoveConstructRange(original.as_span(),
dest.as_span());
for (int i = 0; i < size; i++) {
EXPECT_EQ(i + 1, dest[i].data());
}
// The original values were consumed, so when the original elements are
// destroyed, the destruction callback should report 0.
EXPECT_THAT(destroyed_instances,
::testing::ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
}
TEST(VectorBuffer, CopyToMove) {
constexpr int size = 10;
VectorBuffer<CopyOnlyInt> dest(size);
VectorBuffer<CopyOnlyInt> original(size);
for (int i = 0; i < size; i++) {
// SAFETY: `i < size`, and `size` is the buffer's allocation size, so
// `begin() + i` is inside the buffer.
new (UNSAFE_BUFFERS(original.begin() + i)) CopyOnlyInt(i + 1);
}
std::vector<int> destroyed_instances;
auto scoped_callback_cleanup =
CopyOnlyInt::SetScopedDestructionCallback(BindLambdaForTesting(
[&](int value) { destroyed_instances.push_back(value); }));
VectorBuffer<CopyOnlyInt>::MoveConstructRange(original.as_span(),
dest.as_span());
for (int i = 0; i < size; i++) {
EXPECT_EQ(i + 1, dest[i].data());
}
EXPECT_THAT(destroyed_instances,
::testing::ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}
TEST(VectorBuffer, TrivialAbiMove) {
// Currently trivial relocation doesn't work on Windows for some reason, so
// the test needs to handle both cases.
constexpr bool kHaveTrivialRelocation =
IS_TRIVIALLY_RELOCATABLE(TrivialAbiWithCountingOperations);
constexpr int size = 10;
VectorBuffer<TrivialAbiWithCountingOperations> dest(size);
int destruction_count = 0;
int move_count = 0;
VectorBuffer<TrivialAbiWithCountingOperations> original(size);
for (int i = 0; i < size; i++) {
// SAFETY: `i < size`, and `size` is the buffer's allocation size, so
// `begin() + i` is inside the buffer.
new (UNSAFE_BUFFERS(original.begin() + i))
TrivialAbiWithCountingOperations(&destruction_count, &move_count);
}
VectorBuffer<TrivialAbiWithCountingOperations>::MoveConstructRange(
original.as_span(), dest.as_span());
// We expect the move to have been performed via memcpy, without calling move
// constructors or destructors.
EXPECT_EQ(destruction_count, kHaveTrivialRelocation ? 0 : size);
EXPECT_EQ(move_count, kHaveTrivialRelocation ? 0 : size);
dest.DestructRange(dest.as_span());
EXPECT_EQ(destruction_count, kHaveTrivialRelocation ? size : size * 2);
EXPECT_EQ(move_count, kHaveTrivialRelocation ? 0 : size);
}
} // namespace base::internal