File: test_safe.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 (247 lines) | stat: -rw-r--r-- 6,939 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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
 * @file test_lockable.cpp
 * @author L.-C. C.
 * @brief 
 * @version 0.1
 * @date 2018-11-24
 * 
 * @copyright Copyright (c) 2018
 * 
 */

#include <safe/safe.h>

#include <doctest/doctest.h>

#include <algorithm>
#include <mutex>

class CountingLock
{
public:
	void lock()
	{
		++m_lockCount;
		m_isFaulted |= m_isLocked; // if faulted, stay faulted
		m_isLocked = true;
	}
	bool try_lock()
	{
		++m_tryCount;
		const bool ret = !m_isLocked;
		m_isLocked = true;
		return ret;
	}
	void unlock()
	{
		++m_unlockCount;
		m_isFaulted |= !m_isLocked; // if faulted, stay faulted
		m_isLocked = false;
	}

	bool isLocked() const
	{
		return m_isLocked;
	}
	bool isFaulted() const
	{
		return m_isFaulted;
	}

	bool checkCounts(unsigned int lockCount, unsigned int tryCount, unsigned int unlockCount) const
	{
		return m_lockCount==lockCount && m_tryCount==tryCount && m_unlockCount==unlockCount;
	}

private:
	bool m_isLocked = false;
	bool m_isFaulted = false;

	unsigned int m_lockCount = 0;
	unsigned int m_tryCount = 0;
	unsigned int m_unlockCount = 0;
};

class CountingValue
{
public:
	CountingValue() = default;
	CountingValue(const CountingValue& other):
		m_copyConstructCount(++other.m_copyConstructCount),
		m_moveConstructCount(other.m_moveConstructCount),
		m_copyAssignCount(other.m_copyAssignCount),
		m_moveAssignCount(other.m_moveAssignCount),
		m_movedFrom(other.m_movedFrom)
	{}
	CountingValue(CountingValue&& other):
		m_copyConstructCount(other.m_copyConstructCount),
		m_moveConstructCount(++other.m_moveConstructCount),
		m_copyAssignCount(other.m_copyAssignCount),
		m_moveAssignCount(other.m_moveAssignCount),
		m_movedFrom(other.m_movedFrom)
	{
		other.m_movedFrom = true;
	}
	CountingValue& operator=(const CountingValue& other)
	{
		m_copyConstructCount = other.m_copyConstructCount;
		m_moveConstructCount = other.m_moveConstructCount;
		m_copyAssignCount = 1 + std::max(m_copyAssignCount, other.m_copyAssignCount);
		other.m_copyAssignCount = m_copyAssignCount;
		m_moveAssignCount = other.m_moveAssignCount;
		m_movedFrom = other.m_movedFrom;
		return *this;
	}
	CountingValue& operator=(CountingValue&& other)
	{
		m_copyConstructCount = other.m_copyConstructCount;
		m_moveConstructCount = other.m_moveConstructCount;
		m_copyAssignCount = other.m_copyAssignCount;
		m_moveAssignCount = 1 + std::max(m_moveAssignCount, other.m_moveAssignCount);
		other.m_moveAssignCount = m_moveAssignCount;
		m_movedFrom = other.m_movedFrom;
		other.m_movedFrom = true;
		return *this;
	}
	
	bool checkCounts(
		unsigned int copyConstructCount,
		unsigned int moveConstructCount,
		unsigned int copyAssignCount,
		unsigned int moveAssignCount,
		bool movedFrom) const
	{
		return
			m_copyConstructCount==copyConstructCount &&
			m_moveConstructCount==moveConstructCount &&
			m_copyAssignCount==copyAssignCount &&
			m_moveAssignCount==moveAssignCount &&
			m_movedFrom==movedFrom;
	}

private:
	mutable unsigned int m_copyConstructCount = 0;
	unsigned int m_moveConstructCount = 0;
	mutable unsigned int m_copyAssignCount = 0;
	unsigned int m_moveAssignCount = 0;
	bool m_movedFrom = false;
};

class Checker
{
public:
	Checker(CountingLock& lock):
		m_lock(lock)
	{}
	Checker(const Checker& other):
		m_isOk(other.m_isOk && other.m_lock.isLocked() && !other.m_lock.isFaulted()),
		m_lock(other.m_lock),
		m_value(other.m_value)
	{}
	Checker(Checker&& other):
		m_isOk(other.m_isOk && other.m_lock.isLocked() && !other.m_lock.isFaulted()),
		m_lock(other.m_lock),
		m_value(std::move(other.m_value))
	{}
	Checker& operator=(const Checker& other)
	{
		m_isOk &= other.m_isOk && other.m_lock.isLocked() && !other.m_lock.isFaulted(); // if not ok, stays not ok
		m_lock = other.m_lock;
		m_value = other.m_value;
		return *this;
	}
	Checker& operator=(Checker&& other)
	{
		m_isOk &= other.m_isOk && other.m_lock.isLocked() && !other.m_lock.isFaulted(); // if not ok, stays not ok
		m_lock = other.m_lock;
		m_value = std::move(other.m_value);
		return *this;
	}

	bool isOk() const {return m_isOk;}

	bool checkValueCounts(
		bool copyConstructed,
		bool moveConstructed,
		unsigned int copyAssignCount,
		unsigned int moveAssignCount,
		bool movedFrom) const
	{
		return m_value.checkCounts(copyConstructed, moveConstructed, copyAssignCount, moveAssignCount, movedFrom);
	}

private:
	bool m_isOk = true;
	CountingLock& m_lock;
	CountingValue m_value;
};

TEST_CASE("Copy member function locks the mutex before copying (and unlocks afterwards!)")
{
	CountingLock lock;
	Checker checker(lock);
	safe::Safe<Checker&, CountingLock&> safeChecker(lock, checker);

	// Mutex, lock and checker are in the proper inital state
	CHECK_FALSE(lock.isLocked());
	CHECK_FALSE(lock.isFaulted());
	CHECK(checker.isOk());
	// Safe points to the right objects
	CHECK_EQ(&safeChecker.unsafe(), &checker);
	CHECK_EQ(&safeChecker.mutex(), &lock);

	const auto& checkerCopy = safeChecker.copy();

	// Check that the right number of calls were made to the lock
	CHECK(lock.checkCounts(1,0,1));
	// Check that the lock is unlocked and not faulted
	CHECK_FALSE(lock.isLocked());
	CHECK_FALSE(lock.isFaulted());
	// Check that checkerCopy was copy constructed
	CHECK(checkerCopy.checkValueCounts(1, 0, 0, 0, false));
	// Check the lock was locked when checkerCopy was constructed
	CHECK(checkerCopy.isOk());
	// Check that checker was not moved from
	CHECK(checker.checkValueCounts(1, 0, 0, 0, false));
	// Check checker and checkerCopy are different instances
	CHECK_NE(&checker, &checkerCopy);
}

TEST_CASE("Assign member functions lock the mutex before assigning (and unlocks afterwards!)")
{
	CountingLock lock;
	Checker checker(lock);
	safe::Safe<Checker&, CountingLock&> safeChecker(lock, checker);
	Checker otherChecker(lock);

	// TODO: use a fixture (to make initial checks...)!

	safeChecker.assign<std::unique_lock>(otherChecker, std::try_to_lock);

	// Check that the right number of calls were made to the lock
	CHECK(lock.checkCounts(0,1,1));
	// Check that the lock is unlocked and not faulted
	CHECK_FALSE(lock.isLocked());
	CHECK_FALSE(lock.isFaulted());
	// Check that checker was copy assigned once
	CHECK(checker.checkValueCounts(0, 0, 1, 0, false));
	// Check the lock was locked when checker was assigned
	CHECK(checker.isOk());
	// Check that otherChecker was not moved from
	CHECK(otherChecker.checkValueCounts(0, 0, 1, 0, false));

	// Do the same thing with a move assign
	safeChecker.assign(std::move(otherChecker));

	// Check that the right number of calls were made to the lock
	CHECK(lock.checkCounts(1,1,2));
	// Check that the lock is unlocked and not faulted
	CHECK_FALSE(lock.isLocked());
	CHECK_FALSE(lock.isFaulted());
	// Check that checker was copy and move assigned once
	CHECK(checker.checkValueCounts(0, 0, 1, 1, false));
	// Check the lock was locked when checker was assigned
	CHECK(checker.isOk());
	// Check that otherChecker was moved from
	CHECK(otherChecker.checkValueCounts(0, 0, 1, 1, true));
}