blob: 23ce65ba933d7d772e03d42150d74e3b5a92f3a4 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdint>
#include "missive/storage/resources/resource_interface.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "base/test/task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::Eq;
namespace reporting {
namespace {
class TestCallbackWaiter {
public:
TestCallbackWaiter() = default;
TestCallbackWaiter(const TestCallbackWaiter& other) = delete;
TestCallbackWaiter& operator=(const TestCallbackWaiter& other) = delete;
void Attach() {
const size_t old_counter = counter_.fetch_add(1);
DCHECK_GT(old_counter, 0u) << "Cannot attach when already being released";
}
void Signal() {
const size_t old_counter = counter_.fetch_sub(1);
DCHECK_GT(old_counter, 0u) << "Already being released";
if (old_counter == 1u) {
// Dropped last owner.
run_loop_.Quit();
}
}
void Wait() {
Signal(); // Rid of the constructor's ownership.
run_loop_.Run();
}
private:
std::atomic<size_t> counter_{1}; // Owned by constructor.
base::RunLoop run_loop_;
};
class ResourceInterfaceTest
: public ::testing::TestWithParam<ResourceInterface*> {
protected:
ResourceInterface* resource_interface() const { return GetParam(); }
private:
base::test::TaskEnvironment task_environment_;
};
TEST_P(ResourceInterfaceTest, NestedReservationTest) {
uint64_t size = resource_interface()->GetTotal();
while ((size / 2) > 0u) {
size /= 2;
EXPECT_TRUE(resource_interface()->Reserve(size));
}
for (; size < resource_interface()->GetTotal(); size *= 2) {
resource_interface()->Discard(size);
}
EXPECT_THAT(resource_interface()->GetUsed(), Eq(0u));
}
TEST_P(ResourceInterfaceTest, SimultaneousReservationTest) {
uint64_t size = resource_interface()->GetTotal();
// Schedule reservations.
TestCallbackWaiter reserve_waiter;
while ((size / 2) > 0u) {
size /= 2;
reserve_waiter.Attach();
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(
[](size_t size, ResourceInterface* resource_interface,
TestCallbackWaiter* waiter) {
EXPECT_TRUE(resource_interface->Reserve(size));
waiter->Signal();
},
size, resource_interface(), &reserve_waiter));
}
reserve_waiter.Wait();
// Schedule discards.
TestCallbackWaiter discard_waiter;
for (; size < resource_interface()->GetTotal(); size *= 2) {
discard_waiter.Attach();
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(
[](size_t size, ResourceInterface* resource_interface,
TestCallbackWaiter* waiter) {
resource_interface->Discard(size);
waiter->Signal();
},
size, resource_interface(), &discard_waiter));
}
discard_waiter.Wait();
EXPECT_THAT(resource_interface()->GetUsed(), Eq(0u));
}
TEST_P(ResourceInterfaceTest, SimultaneousScopedReservationTest) {
uint64_t size = resource_interface()->GetTotal();
TestCallbackWaiter waiter;
while ((size / 2) > 0u) {
size /= 2;
waiter.Attach();
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(
[](size_t size, ResourceInterface* resource_interface,
TestCallbackWaiter* waiter) {
{ ScopedReservation(size, resource_interface); }
waiter->Signal();
},
size, resource_interface(), &waiter));
}
waiter.Wait();
EXPECT_THAT(resource_interface()->GetUsed(), Eq(0u));
}
TEST_P(ResourceInterfaceTest, ReservationOverMaxTest) {
EXPECT_FALSE(
resource_interface()->Reserve(resource_interface()->GetTotal() + 1));
EXPECT_TRUE(resource_interface()->Reserve(resource_interface()->GetTotal()));
resource_interface()->Discard(resource_interface()->GetTotal());
EXPECT_THAT(resource_interface()->GetUsed(), Eq(0u));
}
INSTANTIATE_TEST_SUITE_P(VariousResources,
ResourceInterfaceTest,
testing::Values(GetMemoryResource(),
GetDiskResource()));
} // namespace
} // namespace reporting