dncurrency/nano/core_test/utility.cpp

294 lines
No EOL
7.6 KiB
C++

#include <nano/lib/optional_ptr.hpp>
#include <nano/lib/rate_limiting.hpp>
#include <nano/lib/threading.hpp>
#include <nano/lib/timer.hpp>
#include <nano/lib/utility.hpp>
#include <nano/secure/utility.hpp>
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
#include <future>
using namespace std::chrono_literals;
TEST (rate, basic)
{
nano::rate::token_bucket bucket (10, 10);
// Initial burst
ASSERT_TRUE (bucket.try_consume (10));
ASSERT_FALSE (bucket.try_consume (10));
// With a fill rate of 10 tokens/sec, await 1/3 sec and get 3 tokens
std::this_thread::sleep_for (300ms);
ASSERT_TRUE (bucket.try_consume (3));
ASSERT_FALSE (bucket.try_consume (10));
// Allow time for the bucket to completely refill and do a full burst
std::this_thread::sleep_for (1s);
ASSERT_TRUE (bucket.try_consume (10));
ASSERT_EQ (bucket.largest_burst (), 10);
}
TEST (rate, network)
{
// For the purpose of the test, one token represents 1MB instead of one byte.
// Allow for 10 mb/s bursts (max bucket size), 5 mb/s long term rate
nano::rate::token_bucket bucket (10, 5);
// Initial burst of 10 mb/s over two calls
ASSERT_TRUE (bucket.try_consume (5));
ASSERT_EQ (bucket.largest_burst (), 5);
ASSERT_TRUE (bucket.try_consume (5));
ASSERT_EQ (bucket.largest_burst (), 10);
ASSERT_FALSE (bucket.try_consume (5));
// After 200 ms, the 5 mb/s fillrate means we have 1 mb available
std::this_thread::sleep_for (200ms);
ASSERT_TRUE (bucket.try_consume (1));
ASSERT_FALSE (bucket.try_consume (1));
}
TEST (rate, unlimited)
{
nano::rate::token_bucket bucket (0, 0);
ASSERT_TRUE (bucket.try_consume (5));
ASSERT_EQ (bucket.largest_burst (), 5);
ASSERT_TRUE (bucket.try_consume (static_cast<size_t> (1e9)));
ASSERT_EQ (bucket.largest_burst (), static_cast<size_t> (1e9));
// With unlimited tokens, consuming always succeed
ASSERT_TRUE (bucket.try_consume (static_cast<size_t> (1e9)));
ASSERT_EQ (bucket.largest_burst (), static_cast<size_t> (1e9));
}
TEST (optional_ptr, basic)
{
struct valtype
{
int64_t x{ 1 };
int64_t y{ 2 };
int64_t z{ 3 };
};
nano::optional_ptr<valtype> opt;
ASSERT_FALSE (opt);
ASSERT_FALSE (opt.is_initialized ());
{
auto val = valtype{};
opt = val;
ASSERT_LT (sizeof (opt), sizeof (val));
std::unique_ptr<valtype> uptr;
ASSERT_EQ (sizeof (opt), sizeof (uptr));
}
ASSERT_TRUE (opt);
ASSERT_TRUE (opt.is_initialized ());
ASSERT_EQ (opt->x, 1);
ASSERT_EQ (opt->y, 2);
ASSERT_EQ (opt->z, 3);
}
TEST (thread, thread_pool)
{
std::atomic<bool> passed_sleep{ false };
auto func = [&passed_sleep] () {
std::this_thread::sleep_for (std::chrono::seconds (1));
passed_sleep = true;
};
nano::thread_pool workers (1u, nano::thread_role::name::unknown);
workers.push_task (func);
ASSERT_FALSE (passed_sleep);
nano::timer<std::chrono::milliseconds> timer_l;
timer_l.start ();
while (!passed_sleep)
{
if (timer_l.since_start () > std::chrono::seconds (10))
{
break;
}
}
ASSERT_TRUE (passed_sleep);
}
TEST (thread_pool_alarm, one)
{
nano::thread_pool workers (1u, nano::thread_role::name::unknown);
std::atomic<bool> done (false);
nano::mutex mutex;
nano::condition_variable condition;
workers.add_timed_task (std::chrono::steady_clock::now (), [&] () {
{
nano::lock_guard<nano::mutex> lock (mutex);
done = true;
}
condition.notify_one ();
});
nano::unique_lock<nano::mutex> unique (mutex);
condition.wait (unique, [&] () { return !!done; });
}
TEST (thread_pool_alarm, many)
{
nano::thread_pool workers (50u, nano::thread_role::name::unknown);
std::atomic<int> count (0);
nano::mutex mutex;
nano::condition_variable condition;
for (auto i (0); i < 50; ++i)
{
workers.add_timed_task (std::chrono::steady_clock::now (), [&] () {
{
nano::lock_guard<nano::mutex> lock (mutex);
count += 1;
}
condition.notify_one ();
});
}
nano::unique_lock<nano::mutex> unique (mutex);
condition.wait (unique, [&] () { return count == 50; });
}
TEST (thread_pool_alarm, top_execution)
{
nano::thread_pool workers (1u, nano::thread_role::name::unknown);
int value1 (0);
int value2 (0);
nano::mutex mutex;
std::promise<bool> promise;
workers.add_timed_task (std::chrono::steady_clock::now (), [&] () {
nano::lock_guard<nano::mutex> lock (mutex);
value1 = 1;
value2 = 1;
});
workers.add_timed_task (std::chrono::steady_clock::now () + std::chrono::milliseconds (1), [&] () {
nano::lock_guard<nano::mutex> lock (mutex);
value2 = 2;
promise.set_value (false);
});
promise.get_future ().get ();
nano::lock_guard<nano::mutex> lock (mutex);
ASSERT_EQ (1, value1);
ASSERT_EQ (2, value2);
}
TEST (filesystem, remove_all_files)
{
auto path = nano::unique_path ();
auto dummy_directory = path / "tmp";
boost::filesystem::create_directories (dummy_directory);
auto dummy_file1 = path / "my_file1.txt";
auto dummy_file2 = path / "my_file2.txt";
std::ofstream (dummy_file1.string ());
std::ofstream (dummy_file2.string ());
// Check all exist
ASSERT_TRUE (boost::filesystem::exists (dummy_directory));
ASSERT_TRUE (boost::filesystem::exists (dummy_file1));
ASSERT_TRUE (boost::filesystem::exists (dummy_file2));
// Should remove only the files
nano::remove_all_files_in_dir (path);
ASSERT_TRUE (boost::filesystem::exists (dummy_directory));
ASSERT_FALSE (boost::filesystem::exists (dummy_file1));
ASSERT_FALSE (boost::filesystem::exists (dummy_file2));
}
TEST (filesystem, move_all_files)
{
auto path = nano::unique_path ();
auto dummy_directory = path / "tmp";
boost::filesystem::create_directories (dummy_directory);
auto dummy_file1 = dummy_directory / "my_file1.txt";
auto dummy_file2 = dummy_directory / "my_file2.txt";
std::ofstream (dummy_file1.string ());
std::ofstream (dummy_file2.string ());
// Check all exist
ASSERT_TRUE (boost::filesystem::exists (dummy_directory));
ASSERT_TRUE (boost::filesystem::exists (dummy_file1));
ASSERT_TRUE (boost::filesystem::exists (dummy_file2));
// Should move only the files
nano::move_all_files_to_dir (dummy_directory, path);
ASSERT_TRUE (boost::filesystem::exists (dummy_directory));
ASSERT_TRUE (boost::filesystem::exists (path / "my_file1.txt"));
ASSERT_TRUE (boost::filesystem::exists (path / "my_file2.txt"));
ASSERT_FALSE (boost::filesystem::exists (dummy_file1));
ASSERT_FALSE (boost::filesystem::exists (dummy_file2));
}
TEST (relaxed_atomic_integral, basic)
{
nano::relaxed_atomic_integral<uint32_t> atomic{ 0 };
ASSERT_EQ (0, atomic++);
ASSERT_EQ (1, atomic);
ASSERT_EQ (2, ++atomic);
ASSERT_EQ (2, atomic);
ASSERT_EQ (2, atomic.load ());
ASSERT_EQ (2, atomic--);
ASSERT_EQ (1, atomic);
ASSERT_EQ (0, --atomic);
ASSERT_EQ (0, atomic);
ASSERT_EQ (0, atomic.fetch_add (2));
ASSERT_EQ (2, atomic);
ASSERT_EQ (2, atomic.fetch_sub (1));
ASSERT_EQ (1, atomic);
atomic.store (3);
ASSERT_EQ (3, atomic);
uint32_t expected{ 2 };
ASSERT_FALSE (atomic.compare_exchange_strong (expected, 1));
ASSERT_EQ (3, expected);
ASSERT_EQ (3, atomic);
ASSERT_TRUE (atomic.compare_exchange_strong (expected, 1));
ASSERT_EQ (1, atomic);
ASSERT_EQ (3, expected);
// Weak can fail spuriously, try a few times
bool res{ false };
for (int i = 0; i < 1000; ++i)
{
res |= atomic.compare_exchange_weak (expected, 2);
expected = 1;
}
ASSERT_TRUE (res);
ASSERT_EQ (2, atomic);
}
TEST (relaxed_atomic_integral, many_threads)
{
std::vector<std::thread> threads;
auto num = 4;
nano::relaxed_atomic_integral<uint32_t> atomic{ 0 };
for (int i = 0; i < num; ++i)
{
threads.emplace_back ([&atomic] {
for (int i = 0; i < 10000; ++i)
{
++atomic;
atomic--;
atomic++;
--atomic;
atomic.fetch_add (2);
atomic.fetch_sub (2);
}
});
}
for (auto & thread : threads)
{
thread.join ();
}
// Check values
ASSERT_EQ (0, atomic);
}