1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
/**
* @file test_readme.cpp
* @author L.-C. C.
* @brief
* @version 0.1
* @date 2019-04-18
*
* @copyright Copyright (c) 2019
*
*/
#include <safe/safe.h>
#include <doctest/doctest.h>
#include <cassert>
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
TEST_CASE("Readme without safe example")
{
std::string foo; // do I need to lock a mutex to safely access this variable ?
std::string bar;
std::string baz; // what about this one ?
std::mutex fooMutex; // don't forget to change the name of this variable if foo's name changes!
std::mutex barMutex;
{
std::lock_guard<std::mutex> lock(fooMutex); // is this the right mutex for what I am about to do ?
foo = "Hello, World!"; // I access foo here, but I could very well access bar, yet barMutex is not locked!
}
std::cout << bar << std::endl; // unprotected access, is this intended ?
std::cout << baz << std::endl; // what about this access ?
}
TEST_CASE("Readme with safe example")
{
using SafeString = safe::Safe<std::string>; // type aliases will save you a lot of typing
SafeString safeFoo; // std::string and mutex packaged together!
SafeString safeBar;
std::string baz; // now you can see that this variable has no mutex
{
safe::WriteAccess<SafeString> foo(safeFoo); // this locks the mutex and gives you access to foo, nothing more
*foo = "Hello, World!"; // access the value using pointer semantics: * and ->
}
//std::cout << safeBar << std::endl; // does not compile!
std::cout << safeBar.unsafe() << std::endl; // unprotected access: clearly expressed!
std::cout << baz << std::endl; // all good (remember, baz has no mutex!)
}
TEST_CASE("Readme basic usage without safe")
{
std::mutex mutex;
int value;
{
std::lock_guard<std::mutex> lock(mutex);
value = 42;
}
}
TEST_CASE("Readme basic usage with safe")
{
safe::Safe<int> safeValue;
{
safe::WriteAccess<safe::Safe<int>> value(safeValue);
*value = 42;
CHECK_EQ(&*value, &safeValue.unsafe());
CHECK_EQ(safeValue.unsafe(), 42);
}
{
safe::Safe<int>::WriteAccess<> value(safeValue); // equivalent to the above
CHECK_EQ(&*value, &safeValue.unsafe());
CHECK_EQ(*value, 42);
}
#if __cplusplus >= 201703L
{
auto value = safeValue.writeAccess(); // only with C++17 and later
CHECK_EQ(&*value, &safeValue.unsafe());
CHECK_EQ(*value, 42);
}
#endif
{
auto value = safeValue.writeAccess<std::unique_lock>(); // ok even pre-C++17
CHECK_EQ(&*value, &safeValue.unsafe());
CHECK_EQ(value.lock.mutex(), &safeValue.mutex());
CHECK_EQ(*value, 42);
}
}
TEST_CASE("Readme one liners")
{
safe::Safe<int> safeValue;
*safeValue.writeAccess() = 42;
CHECK_EQ(safeValue.unsafe(), 42);
{
int copy = *safeValue.readAccess();
CHECK_EQ(copy, 42);
}
{
int copy = *safeValue.writeAccess(); // this also works...
// *safeValue.readAccess() = 42; // but this obviously doesn't!
CHECK_EQ(copy, 42);
}
safeValue.assign(43);
CHECK_EQ(safeValue.unsafe(), 43);
{
safeValue.assign(42);
CHECK_EQ(safeValue.unsafe(), 42);
int copy = safeValue.copy();
CHECK_EQ(copy, 42);
}
}
TEST_CASE("Readme ref and non ref")
{
std::mutex mutex;
int value;
safe::Safe<int, std::mutex> valmut;
safe::Safe<int> valdef; // equivalent to the above, as the second template parameter defaults to std::mutex
safe::Safe<int&, std::mutex> refmut(safe::default_construct_mutex, value);
safe::Safe<int, std::mutex&> valref(mutex, 42);
safe::Safe<int&, std::mutex&> refref(mutex, value);
CHECK_EQ(&refmut.unsafe(), &value);
CHECK_EQ(valref.unsafe(), 42);
CHECK_EQ(&valref.mutex(), &mutex);
CHECK_EQ(&refref.unsafe(), &value);
CHECK_EQ(&refref.mutex(), &mutex);
}
TEST_CASE("Readme default construct lockable tag")
{
std::mutex mutex;
safe::Safe<int, std::mutex> bothDefault; // mutex and value are default constructed
safe::Safe<int, std::mutex&> noDefault(mutex, 42); // mutex and value are initialized
safe::Safe<int, std::mutex&> valueDefault(mutex); // mutex is initialized, and value is default constructed
safe::Safe<int, std::mutex> mutexDefaultTag(safe::default_construct_mutex, 42); // mutex is default constructed, and value is initialized
safe::Safe<int, std::mutex> mutexDefaultBraces({}, 42);
CHECK_EQ(noDefault.unsafe(), 42);
CHECK_EQ(&noDefault.mutex(), &mutex);
CHECK_EQ(&valueDefault.mutex(), &mutex);
CHECK_EQ(mutexDefaultTag.unsafe(), 42);
CHECK_EQ(mutexDefaultBraces.unsafe(), 42);
}
TEST_CASE("Readme flexibly construct lock")
{
safe::Safe<int> safeValue; // given a Safe object
safeValue.mutex().lock(); // with the mutex already locked...
// Because the mutex is already locked, you need to pass the std::adopt_lock tag to std::lock_guard when you construct your Access object.
// Fortunately, arguments passed to WriteAccess's constructor are forwarded to the lock's constructor.
{
safe::WriteAccess<safe::Safe<int>> value(safeValue, std::adopt_lock);
CHECK_EQ(&*value, &safeValue.unsafe());
}
safeValue.mutex().lock();
{
safe::Safe<int>::WriteAccess<> value(safeValue, std::adopt_lock);
CHECK_EQ(&*value, &safeValue.unsafe());
}
#if __cplusplus >= 201703L
safeValue.mutex().lock();
{
auto value = safeValue.writeAccess(std::adopt_lock);
CHECK_EQ(&*value, &safeValue.unsafe());
}
#endif
safeValue.mutex().lock();
{
auto value = safeValue.writeAccess<std::unique_lock>(std::adopt_lock);
CHECK_EQ(&*value, &safeValue.unsafe());
CHECK_EQ(value.lock.mutex(), &safeValue.mutex());
}
}
TEST_CASE("Readme legacy")
{
std::mutex lousyMutex;
int unsafeValue;
// Wrap the existing variables
safe::Safe<int&, std::mutex&> safeValue(lousyMutex, unsafeValue);
// do not use lousyMutex and unsafeValue directly from here on!
}
TEST_CASE("Readme condition variable")
{
std::condition_variable cv;
safe::Safe<int> safeValue;
safe::Safe<int>::WriteAccess<std::unique_lock> value(safeValue);
cv.wait(value.lock, [](){return true;});
CHECK_EQ(&*value, &safeValue.unsafe());
CHECK_EQ(value.lock.mutex(), &safeValue.mutex());
}
|