blob: 7aad628d4336df77cf8926a2a0f75fd13a8c6d66 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/lib/util/protected_fields.h"
#include <condition_variable>
#include <sstream>
#include <string>
#include <thread>
#include <gtest/gtest.h>
namespace cobalt::util {
TEST(ProtectedFields, BasicFunctionality) {
struct SafeField {
int protected_int;
};
ProtectedFields<SafeField> protected_fields;
protected_fields.lock()->protected_int = 100;
ASSERT_EQ(100, protected_fields.const_lock()->protected_int);
}
TEST(ProtectedFields, StressTest) {
std::vector<std::thread> threads;
struct SafeFields {
int protected_int = 0;
std::string protected_str = "";
};
ProtectedFields<SafeFields> protected_fields;
// Force lock contention.
auto locked_fields = protected_fields.const_lock();
for (auto i = 0; i < 100; i += 1) {
threads.emplace_back(std::thread([i, &protected_fields]() {
auto fields = protected_fields.lock();
fields->protected_int += 10;
std::stringstream ss;
ss << "Thread: " << i;
fields->protected_str = ss.str();
}));
}
// Nothing should have changed yet, since the lock has been held since before the threads started.
ASSERT_EQ(0, locked_fields->protected_int);
ASSERT_EQ("", locked_fields->protected_str);
locked_fields.unlock();
for (auto &thread : threads) {
thread.join();
}
ASSERT_EQ(100 * 10, protected_fields.const_lock()->protected_int);
ASSERT_NE("", protected_fields.const_lock()->protected_str);
}
TEST(ProtectedFields, ConditionVariables) {
struct SafeFields {
std::condition_variable_any started_notifier;
bool started = false;
std::condition_variable_any shutdown_notifier;
bool shutdown = false;
bool shutdown_complete = false;
};
ProtectedFields<SafeFields> protected_fields;
auto important_work = std::thread([&protected_fields]() {
auto fields = protected_fields.lock();
fields->started = true;
fields->started_notifier.notify_all();
fields->shutdown_notifier.wait(fields, [&fields]() { return fields->shutdown; });
fields->shutdown_complete = true;
});
{
// Wait until the thread has started.
auto fields = protected_fields.lock();
fields->started_notifier.wait(fields, [&fields]() { return fields->started; });
}
{
// Notify the thread to shut down.
auto fields = protected_fields.lock();
fields->shutdown = true;
fields->shutdown_notifier.notify_all();
}
important_work.join();
ASSERT_EQ(true, protected_fields.const_lock()->shutdown_complete);
}
TEST(ProtectedFields, Constructor) {
struct ConstructableFields {
uint32_t value = 1000;
std::string other_value;
int32_t num;
ConstructableFields(std::string v, int32_t num) : other_value(std::move(v)), num(num) {}
};
ProtectedFields<ConstructableFields> protected_fields("Constructed Value", 54321);
ASSERT_EQ(1000u, protected_fields.lock()->value);
ASSERT_EQ("Constructed Value", protected_fields.lock()->other_value);
ASSERT_EQ(54321, protected_fields.lock()->num);
}
TEST(RWProtectedFields, StressTest) {
std::vector<std::thread> threads;
struct SafeField {
int value = 0;
};
RWProtectedFields<SafeField> rw_protected_field;
rw_protected_field.lock()->value = 15;
ProtectedFields<int> counter(0);
// Force lock contention;
auto locked_counter = counter.lock();
// Take the reader lock, the threads should be able to continue.
auto locked_fields = rw_protected_field.const_lock();
for (auto i = 0; i < 100; i += 1) {
threads.emplace_back(std::thread([&counter, &rw_protected_field]() {
auto fields = rw_protected_field.const_lock();
*counter.lock() += fields->value;
}));
}
locked_counter.unlock();
for (auto &thread : threads) {
thread.join();
}
ASSERT_EQ(15 * 100, *counter.lock());
}
} // namespace cobalt::util