File: test_readme.cpp

package info (click to toggle)
safe 1.0.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 268 kB
  • sloc: cpp: 513; ansic: 63; makefile: 4
file content (204 lines) | stat: -rw-r--r-- 5,874 bytes parent folder | download
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());
}