Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/common-tests/misc_tests.cpp
14138 views
1
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "common/locked_ptr.h"
5
#include "common/optional_with_status.h"
6
7
#include <gtest/gtest.h>
8
9
#include <mutex>
10
#include <optional>
11
#include <string>
12
13
// ============================================================================
14
// OptionalWithStatus Tests
15
// ============================================================================
16
17
enum class TestStatus
18
{
19
Ok,
20
Missing,
21
Failed,
22
};
23
24
TEST(OptionalWithStatus, DefaultConstruction)
25
{
26
OptionalWithStatus<int, TestStatus> obj;
27
EXPECT_FALSE(obj.has_value());
28
EXPECT_FALSE(static_cast<bool>(obj));
29
EXPECT_EQ(obj.status(), TestStatus::Ok);
30
}
31
32
TEST(OptionalWithStatus, StatusOnlyConstruction)
33
{
34
OptionalWithStatus<int, TestStatus> obj(TestStatus::Missing);
35
EXPECT_FALSE(obj.has_value());
36
EXPECT_FALSE(static_cast<bool>(obj));
37
EXPECT_EQ(obj.status(), TestStatus::Missing);
38
}
39
40
TEST(OptionalWithStatus, StatusOnlyConstructionError)
41
{
42
OptionalWithStatus<int, TestStatus> obj(TestStatus::Failed);
43
EXPECT_FALSE(obj.has_value());
44
EXPECT_EQ(obj.status(), TestStatus::Failed);
45
}
46
47
TEST(OptionalWithStatus, CopyValueConstruction)
48
{
49
const int value = 42;
50
OptionalWithStatus<int, TestStatus> obj(TestStatus::Ok, value);
51
EXPECT_TRUE(obj.has_value());
52
EXPECT_TRUE(static_cast<bool>(obj));
53
EXPECT_EQ(obj.status(), TestStatus::Ok);
54
EXPECT_EQ(obj.value(), 42);
55
EXPECT_EQ(*obj, 42);
56
}
57
58
TEST(OptionalWithStatus, MoveValueConstruction)
59
{
60
std::string s = "hello";
61
OptionalWithStatus<std::string, TestStatus> obj(TestStatus::Ok, std::move(s));
62
EXPECT_TRUE(obj.has_value());
63
EXPECT_EQ(obj.status(), TestStatus::Ok);
64
EXPECT_EQ(obj.value(), "hello");
65
}
66
67
TEST(OptionalWithStatus, ArrowOperator)
68
{
69
OptionalWithStatus<std::string, TestStatus> obj(TestStatus::Ok, std::string("world"));
70
EXPECT_EQ(obj->size(), 5u);
71
}
72
73
TEST(OptionalWithStatus, ValueOr)
74
{
75
OptionalWithStatus<int, TestStatus> present(TestStatus::Ok, 10);
76
OptionalWithStatus<int, TestStatus> absent(TestStatus::Missing);
77
78
EXPECT_EQ(present.value_or(99), 10);
79
EXPECT_EQ(absent.value_or(99), 99);
80
}
81
82
TEST(OptionalWithStatus, CopyConstruction)
83
{
84
OptionalWithStatus<int, TestStatus> original(TestStatus::Ok, 7);
85
OptionalWithStatus<int, TestStatus> copy(original);
86
EXPECT_TRUE(copy.has_value());
87
EXPECT_EQ(copy.status(), TestStatus::Ok);
88
EXPECT_EQ(copy.value(), 7);
89
}
90
91
TEST(OptionalWithStatus, MoveConstruction)
92
{
93
OptionalWithStatus<std::string, TestStatus> original(TestStatus::Ok, std::string("move-me"));
94
OptionalWithStatus<std::string, TestStatus> moved(std::move(original));
95
EXPECT_TRUE(moved.has_value());
96
EXPECT_EQ(moved.status(), TestStatus::Ok);
97
EXPECT_EQ(moved.value(), "move-me");
98
// source must be emptied by the manual move
99
EXPECT_FALSE(original.has_value());
100
}
101
102
TEST(OptionalWithStatus, CopyAssignment)
103
{
104
OptionalWithStatus<int, TestStatus> a(TestStatus::Ok, 5);
105
OptionalWithStatus<int, TestStatus> b(TestStatus::Missing);
106
b = a;
107
EXPECT_TRUE(b.has_value());
108
EXPECT_EQ(b.status(), TestStatus::Ok);
109
EXPECT_EQ(b.value(), 5);
110
}
111
112
TEST(OptionalWithStatus, MoveAssignment)
113
{
114
OptionalWithStatus<std::string, TestStatus> a(TestStatus::Ok, std::string("assigned"));
115
OptionalWithStatus<std::string, TestStatus> b(TestStatus::Missing);
116
b = std::move(a);
117
EXPECT_TRUE(b.has_value());
118
EXPECT_EQ(b.status(), TestStatus::Ok);
119
EXPECT_EQ(b.value(), "assigned");
120
// source must be emptied by the manual move
121
EXPECT_FALSE(a.has_value());
122
}
123
124
TEST(OptionalWithStatus, MutableValueAccess)
125
{
126
OptionalWithStatus<int, TestStatus> obj(TestStatus::Ok, 1);
127
obj.value() = 100;
128
EXPECT_EQ(*obj, 100);
129
}
130
131
TEST(OptionalWithStatus, DistinctMissingVsError)
132
{
133
OptionalWithStatus<int, TestStatus> miss(TestStatus::Missing);
134
OptionalWithStatus<int, TestStatus> error(TestStatus::Failed);
135
136
EXPECT_EQ(miss.status(), TestStatus::Missing);
137
EXPECT_EQ(error.status(), TestStatus::Failed);
138
EXPECT_NE(miss.status(), error.status());
139
EXPECT_FALSE(miss.has_value());
140
EXPECT_FALSE(error.has_value());
141
}
142
143
TEST(OptionalWithStatus, SmallerThanEmbeddedOptional)
144
{
145
// The manual storage packs the has-value bool next to the status field, saving
146
// the padding that an embedded std::optional<T> member would add.
147
//
148
// For any T where alignof(T) > 1, sizeof(std::optional<T>) includes internal
149
// padding after its bool, so embedding it as a member wastes space.
150
// OptionalWithStatus avoids this by sharing that padding region with the status.
151
struct LargeAlign { double d; int i; };
152
static_assert(sizeof(OptionalWithStatus<LargeAlign, TestStatus>) <
153
sizeof(std::optional<LargeAlign>) + sizeof(TestStatus),
154
"OptionalWithStatus should be smaller than std::optional<T> + S");
155
}
156
157
// ============================================================================
158
// LockedPtr Tests
159
// ============================================================================
160
161
TEST(LockedPtr, DefaultConstruction)
162
{
163
LockedPtr<int, std::mutex> ptr;
164
EXPECT_FALSE(static_cast<bool>(ptr));
165
EXPECT_EQ(ptr.get_ptr(), nullptr);
166
EXPECT_EQ(ptr.get_mutex(), nullptr);
167
}
168
169
TEST(LockedPtr, LocksOnConstruction)
170
{
171
std::mutex mtx;
172
int value = 42;
173
174
{
175
LockedPtr<int, std::mutex> ptr(mtx, &value);
176
EXPECT_TRUE(static_cast<bool>(ptr));
177
EXPECT_EQ(ptr.get_ptr(), &value);
178
EXPECT_EQ(*ptr, 42);
179
// mutex is held; try_lock should fail
180
EXPECT_FALSE(mtx.try_lock());
181
}
182
183
// destructor released the lock; try_lock should succeed now
184
EXPECT_TRUE(mtx.try_lock());
185
mtx.unlock();
186
}
187
188
TEST(LockedPtr, AdoptLock)
189
{
190
std::mutex mtx;
191
mtx.lock();
192
int value = 7;
193
194
{
195
LockedPtr<int, std::mutex> ptr(mtx, &value, std::adopt_lock);
196
EXPECT_TRUE(static_cast<bool>(ptr));
197
EXPECT_EQ(*ptr, 7);
198
// lock was adopted; try_lock should still fail (ptr holds it)
199
EXPECT_FALSE(mtx.try_lock());
200
}
201
202
// released on destruction
203
EXPECT_TRUE(mtx.try_lock());
204
mtx.unlock();
205
}
206
207
TEST(LockedPtr, UniqueLockConstruction)
208
{
209
std::mutex mtx;
210
int value = 99;
211
212
std::unique_lock<std::mutex> lock(mtx);
213
EXPECT_FALSE(mtx.try_lock()); // lock held
214
215
LockedPtr<int, std::mutex> ptr(std::move(lock), &value);
216
EXPECT_TRUE(static_cast<bool>(ptr));
217
EXPECT_EQ(*ptr, 99);
218
EXPECT_FALSE(mtx.try_lock()); // still held by ptr
219
220
ptr = LockedPtr<int, std::mutex>{}; // release
221
EXPECT_TRUE(mtx.try_lock());
222
mtx.unlock();
223
}
224
225
TEST(LockedPtr, MoveConstruction)
226
{
227
std::mutex mtx;
228
int value = 3;
229
230
LockedPtr<int, std::mutex> first(mtx, &value);
231
EXPECT_FALSE(mtx.try_lock());
232
233
LockedPtr<int, std::mutex> second(std::move(first));
234
EXPECT_EQ(first.get_ptr(), nullptr);
235
EXPECT_EQ(first.get_mutex(), nullptr);
236
EXPECT_EQ(second.get_ptr(), &value);
237
EXPECT_FALSE(mtx.try_lock()); // second still holds the lock
238
}
239
240
TEST(LockedPtr, MoveAssignment)
241
{
242
std::mutex mtx1;
243
std::mutex mtx2;
244
int a = 1;
245
int b = 2;
246
247
LockedPtr<int, std::mutex> ptr1(mtx1, &a);
248
LockedPtr<int, std::mutex> ptr2(mtx2, &b);
249
250
// After move-assign, ptr2 should release mtx2 and take ownership of mtx1/ptr1
251
ptr2 = std::move(ptr1);
252
253
EXPECT_EQ(ptr2.get_ptr(), &a);
254
EXPECT_FALSE(mtx1.try_lock()); // mtx1 still locked via ptr2
255
EXPECT_TRUE(mtx2.try_lock()); // mtx2 was released
256
mtx2.unlock();
257
}
258
259
TEST(LockedPtr, ArrowOperator)
260
{
261
std::mutex mtx;
262
std::string value = "test";
263
264
LockedPtr<std::string, std::mutex> ptr(mtx, &value);
265
EXPECT_EQ(ptr->size(), 4u);
266
}
267
268
TEST(LockedPtr, RecursiveMutex)
269
{
270
std::recursive_mutex rmtx;
271
int value = 55;
272
273
LockedPtr<int, std::recursive_mutex> ptr(rmtx, &value);
274
EXPECT_TRUE(static_cast<bool>(ptr));
275
276
// recursive_mutex allows re-locking on the same thread
277
rmtx.lock();
278
rmtx.unlock();
279
280
EXPECT_EQ(*ptr, 55);
281
}
282
283