summaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/CMakeLists.txt2
-rw-r--r--src/tests/common/fibers.cpp358
-rw-r--r--src/tests/core/host_timing.cpp142
3 files changed, 502 insertions, 0 deletions
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index c7038b217..3f750b51c 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,12 +1,14 @@
1add_executable(tests 1add_executable(tests
2 common/bit_field.cpp 2 common/bit_field.cpp
3 common/bit_utils.cpp 3 common/bit_utils.cpp
4 common/fibers.cpp
4 common/multi_level_queue.cpp 5 common/multi_level_queue.cpp
5 common/param_package.cpp 6 common/param_package.cpp
6 common/ring_buffer.cpp 7 common/ring_buffer.cpp
7 core/arm/arm_test_common.cpp 8 core/arm/arm_test_common.cpp
8 core/arm/arm_test_common.h 9 core/arm/arm_test_common.h
9 core/core_timing.cpp 10 core/core_timing.cpp
11 core/host_timing.cpp
10 tests.cpp 12 tests.cpp
11) 13)
12 14
diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp
new file mode 100644
index 000000000..12536b6d8
--- /dev/null
+++ b/src/tests/common/fibers.cpp
@@ -0,0 +1,358 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <cstdlib>
7#include <functional>
8#include <memory>
9#include <thread>
10#include <unordered_map>
11#include <vector>
12
13#include <catch2/catch.hpp>
14#include <math.h>
15#include "common/common_types.h"
16#include "common/fiber.h"
17#include "common/spin_lock.h"
18
19namespace Common {
20
21class TestControl1 {
22public:
23 TestControl1() = default;
24
25 void DoWork();
26
27 void ExecuteThread(u32 id);
28
29 std::unordered_map<std::thread::id, u32> ids;
30 std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
31 std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
32 std::vector<u32> items;
33 std::vector<u32> results;
34};
35
36static void WorkControl1(void* control) {
37 auto* test_control = static_cast<TestControl1*>(control);
38 test_control->DoWork();
39}
40
41void TestControl1::DoWork() {
42 std::thread::id this_id = std::this_thread::get_id();
43 u32 id = ids[this_id];
44 u32 value = items[id];
45 for (u32 i = 0; i < id; i++) {
46 value++;
47 }
48 results[id] = value;
49 Fiber::YieldTo(work_fibers[id], thread_fibers[id]);
50}
51
52void TestControl1::ExecuteThread(u32 id) {
53 std::thread::id this_id = std::this_thread::get_id();
54 ids[this_id] = id;
55 auto thread_fiber = Fiber::ThreadToFiber();
56 thread_fibers[id] = thread_fiber;
57 work_fibers[id] = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl1}, this);
58 items[id] = rand() % 256;
59 Fiber::YieldTo(thread_fibers[id], work_fibers[id]);
60 thread_fibers[id]->Exit();
61}
62
63static void ThreadStart1(u32 id, TestControl1& test_control) {
64 test_control.ExecuteThread(id);
65}
66
67/** This test checks for fiber setup configuration and validates that fibers are
68 * doing all the work required.
69 */
70TEST_CASE("Fibers::Setup", "[common]") {
71 constexpr u32 num_threads = 7;
72 TestControl1 test_control{};
73 test_control.thread_fibers.resize(num_threads);
74 test_control.work_fibers.resize(num_threads);
75 test_control.items.resize(num_threads, 0);
76 test_control.results.resize(num_threads, 0);
77 std::vector<std::thread> threads;
78 for (u32 i = 0; i < num_threads; i++) {
79 threads.emplace_back(ThreadStart1, i, std::ref(test_control));
80 }
81 for (u32 i = 0; i < num_threads; i++) {
82 threads[i].join();
83 }
84 for (u32 i = 0; i < num_threads; i++) {
85 REQUIRE(test_control.items[i] + i == test_control.results[i]);
86 }
87}
88
89class TestControl2 {
90public:
91 TestControl2() = default;
92
93 void DoWork1() {
94 trap2 = false;
95 while (trap.load())
96 ;
97 for (u32 i = 0; i < 12000; i++) {
98 value1 += i;
99 }
100 Fiber::YieldTo(fiber1, fiber3);
101 std::thread::id this_id = std::this_thread::get_id();
102 u32 id = ids[this_id];
103 assert1 = id == 1;
104 value2 += 5000;
105 Fiber::YieldTo(fiber1, thread_fibers[id]);
106 }
107
108 void DoWork2() {
109 while (trap2.load())
110 ;
111 value2 = 2000;
112 trap = false;
113 Fiber::YieldTo(fiber2, fiber1);
114 assert3 = false;
115 }
116
117 void DoWork3() {
118 std::thread::id this_id = std::this_thread::get_id();
119 u32 id = ids[this_id];
120 assert2 = id == 0;
121 value1 += 1000;
122 Fiber::YieldTo(fiber3, thread_fibers[id]);
123 }
124
125 void ExecuteThread(u32 id);
126
127 void CallFiber1() {
128 std::thread::id this_id = std::this_thread::get_id();
129 u32 id = ids[this_id];
130 Fiber::YieldTo(thread_fibers[id], fiber1);
131 }
132
133 void CallFiber2() {
134 std::thread::id this_id = std::this_thread::get_id();
135 u32 id = ids[this_id];
136 Fiber::YieldTo(thread_fibers[id], fiber2);
137 }
138
139 void Exit();
140
141 bool assert1{};
142 bool assert2{};
143 bool assert3{true};
144 u32 value1{};
145 u32 value2{};
146 std::atomic<bool> trap{true};
147 std::atomic<bool> trap2{true};
148 std::unordered_map<std::thread::id, u32> ids;
149 std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
150 std::shared_ptr<Common::Fiber> fiber1;
151 std::shared_ptr<Common::Fiber> fiber2;
152 std::shared_ptr<Common::Fiber> fiber3;
153};
154
155static void WorkControl2_1(void* control) {
156 auto* test_control = static_cast<TestControl2*>(control);
157 test_control->DoWork1();
158}
159
160static void WorkControl2_2(void* control) {
161 auto* test_control = static_cast<TestControl2*>(control);
162 test_control->DoWork2();
163}
164
165static void WorkControl2_3(void* control) {
166 auto* test_control = static_cast<TestControl2*>(control);
167 test_control->DoWork3();
168}
169
170void TestControl2::ExecuteThread(u32 id) {
171 std::thread::id this_id = std::this_thread::get_id();
172 ids[this_id] = id;
173 auto thread_fiber = Fiber::ThreadToFiber();
174 thread_fibers[id] = thread_fiber;
175}
176
177void TestControl2::Exit() {
178 std::thread::id this_id = std::this_thread::get_id();
179 u32 id = ids[this_id];
180 thread_fibers[id]->Exit();
181}
182
183static void ThreadStart2_1(u32 id, TestControl2& test_control) {
184 test_control.ExecuteThread(id);
185 test_control.CallFiber1();
186 test_control.Exit();
187}
188
189static void ThreadStart2_2(u32 id, TestControl2& test_control) {
190 test_control.ExecuteThread(id);
191 test_control.CallFiber2();
192 test_control.Exit();
193}
194
195/** This test checks for fiber thread exchange configuration and validates that fibers are
196 * that a fiber has been succesfully transfered from one thread to another and that the TLS
197 * region of the thread is kept while changing fibers.
198 */
199TEST_CASE("Fibers::InterExchange", "[common]") {
200 TestControl2 test_control{};
201 test_control.thread_fibers.resize(2);
202 test_control.fiber1 =
203 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_1}, &test_control);
204 test_control.fiber2 =
205 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_2}, &test_control);
206 test_control.fiber3 =
207 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_3}, &test_control);
208 std::thread thread1(ThreadStart2_1, 0, std::ref(test_control));
209 std::thread thread2(ThreadStart2_2, 1, std::ref(test_control));
210 thread1.join();
211 thread2.join();
212 REQUIRE(test_control.assert1);
213 REQUIRE(test_control.assert2);
214 REQUIRE(test_control.assert3);
215 REQUIRE(test_control.value2 == 7000);
216 u32 cal_value = 0;
217 for (u32 i = 0; i < 12000; i++) {
218 cal_value += i;
219 }
220 cal_value += 1000;
221 REQUIRE(test_control.value1 == cal_value);
222}
223
224class TestControl3 {
225public:
226 TestControl3() = default;
227
228 void DoWork1() {
229 value1 += 1;
230 Fiber::YieldTo(fiber1, fiber2);
231 std::thread::id this_id = std::this_thread::get_id();
232 u32 id = ids[this_id];
233 value3 += 1;
234 Fiber::YieldTo(fiber1, thread_fibers[id]);
235 }
236
237 void DoWork2() {
238 value2 += 1;
239 std::thread::id this_id = std::this_thread::get_id();
240 u32 id = ids[this_id];
241 Fiber::YieldTo(fiber2, thread_fibers[id]);
242 }
243
244 void ExecuteThread(u32 id);
245
246 void CallFiber1() {
247 std::thread::id this_id = std::this_thread::get_id();
248 u32 id = ids[this_id];
249 Fiber::YieldTo(thread_fibers[id], fiber1);
250 }
251
252 void Exit();
253
254 u32 value1{};
255 u32 value2{};
256 u32 value3{};
257 std::unordered_map<std::thread::id, u32> ids;
258 std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
259 std::shared_ptr<Common::Fiber> fiber1;
260 std::shared_ptr<Common::Fiber> fiber2;
261};
262
263static void WorkControl3_1(void* control) {
264 auto* test_control = static_cast<TestControl3*>(control);
265 test_control->DoWork1();
266}
267
268static void WorkControl3_2(void* control) {
269 auto* test_control = static_cast<TestControl3*>(control);
270 test_control->DoWork2();
271}
272
273void TestControl3::ExecuteThread(u32 id) {
274 std::thread::id this_id = std::this_thread::get_id();
275 ids[this_id] = id;
276 auto thread_fiber = Fiber::ThreadToFiber();
277 thread_fibers[id] = thread_fiber;
278}
279
280void TestControl3::Exit() {
281 std::thread::id this_id = std::this_thread::get_id();
282 u32 id = ids[this_id];
283 thread_fibers[id]->Exit();
284}
285
286static void ThreadStart3(u32 id, TestControl3& test_control) {
287 test_control.ExecuteThread(id);
288 test_control.CallFiber1();
289 test_control.Exit();
290}
291
292/** This test checks for one two threads racing for starting the same fiber.
293 * It checks execution occured in an ordered manner and by no time there were
294 * two contexts at the same time.
295 */
296TEST_CASE("Fibers::StartRace", "[common]") {
297 TestControl3 test_control{};
298 test_control.thread_fibers.resize(2);
299 test_control.fiber1 =
300 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_1}, &test_control);
301 test_control.fiber2 =
302 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_2}, &test_control);
303 std::thread thread1(ThreadStart3, 0, std::ref(test_control));
304 std::thread thread2(ThreadStart3, 1, std::ref(test_control));
305 thread1.join();
306 thread2.join();
307 REQUIRE(test_control.value1 == 1);
308 REQUIRE(test_control.value2 == 1);
309 REQUIRE(test_control.value3 == 1);
310}
311
312class TestControl4;
313
314static void WorkControl4(void* control);
315
316class TestControl4 {
317public:
318 TestControl4() {
319 fiber1 = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl4}, this);
320 goal_reached = false;
321 rewinded = false;
322 }
323
324 void Execute() {
325 thread_fiber = Fiber::ThreadToFiber();
326 Fiber::YieldTo(thread_fiber, fiber1);
327 thread_fiber->Exit();
328 }
329
330 void DoWork() {
331 fiber1->SetRewindPoint(std::function<void(void*)>{WorkControl4}, this);
332 if (rewinded) {
333 goal_reached = true;
334 Fiber::YieldTo(fiber1, thread_fiber);
335 }
336 rewinded = true;
337 fiber1->Rewind();
338 }
339
340 std::shared_ptr<Common::Fiber> fiber1;
341 std::shared_ptr<Common::Fiber> thread_fiber;
342 bool goal_reached;
343 bool rewinded;
344};
345
346static void WorkControl4(void* control) {
347 auto* test_control = static_cast<TestControl4*>(control);
348 test_control->DoWork();
349}
350
351TEST_CASE("Fibers::Rewind", "[common]") {
352 TestControl4 test_control{};
353 test_control.Execute();
354 REQUIRE(test_control.goal_reached);
355 REQUIRE(test_control.rewinded);
356}
357
358} // namespace Common
diff --git a/src/tests/core/host_timing.cpp b/src/tests/core/host_timing.cpp
new file mode 100644
index 000000000..556254098
--- /dev/null
+++ b/src/tests/core/host_timing.cpp
@@ -0,0 +1,142 @@
1// Copyright 2016 Dolphin Emulator Project / 2017 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <catch2/catch.hpp>
6
7#include <array>
8#include <bitset>
9#include <cstdlib>
10#include <memory>
11#include <string>
12
13#include "common/file_util.h"
14#include "core/core.h"
15#include "core/host_timing.h"
16
17// Numbers are chosen randomly to make sure the correct one is given.
18static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
19static constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals
20static constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
21static std::array<s64, 5> delays{};
22
23static std::bitset<CB_IDS.size()> callbacks_ran_flags;
24static u64 expected_callback = 0;
25
26template <unsigned int IDX>
27void HostCallbackTemplate(u64 userdata, s64 nanoseconds_late) {
28 static_assert(IDX < CB_IDS.size(), "IDX out of range");
29 callbacks_ran_flags.set(IDX);
30 REQUIRE(CB_IDS[IDX] == userdata);
31 REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
32 delays[IDX] = nanoseconds_late;
33 ++expected_callback;
34}
35
36struct ScopeInit final {
37 ScopeInit() {
38 core_timing.Initialize();
39 }
40 ~ScopeInit() {
41 core_timing.Shutdown();
42 }
43
44 Core::HostTiming::CoreTiming core_timing;
45};
46
47#pragma optimize("", off)
48
49static u64 TestTimerSpeed(Core::HostTiming::CoreTiming& core_timing) {
50 u64 start = core_timing.GetGlobalTimeNs().count();
51 u64 placebo = 0;
52 for (std::size_t i = 0; i < 1000; i++) {
53 placebo += core_timing.GetGlobalTimeNs().count();
54 }
55 u64 end = core_timing.GetGlobalTimeNs().count();
56 return (end - start);
57}
58
59#pragma optimize("", on)
60
61TEST_CASE("HostTiming[BasicOrder]", "[core]") {
62 ScopeInit guard;
63 auto& core_timing = guard.core_timing;
64 std::vector<std::shared_ptr<Core::HostTiming::EventType>> events{
65 Core::HostTiming::CreateEvent("callbackA", HostCallbackTemplate<0>),
66 Core::HostTiming::CreateEvent("callbackB", HostCallbackTemplate<1>),
67 Core::HostTiming::CreateEvent("callbackC", HostCallbackTemplate<2>),
68 Core::HostTiming::CreateEvent("callbackD", HostCallbackTemplate<3>),
69 Core::HostTiming::CreateEvent("callbackE", HostCallbackTemplate<4>),
70 };
71
72 expected_callback = 0;
73
74 core_timing.SyncPause(true);
75
76 u64 one_micro = 1000U;
77 for (std::size_t i = 0; i < events.size(); i++) {
78 u64 order = calls_order[i];
79 core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]);
80 }
81 /// test pause
82 REQUIRE(callbacks_ran_flags.none());
83
84 core_timing.Pause(false); // No need to sync
85
86 while (core_timing.HasPendingEvents())
87 ;
88
89 REQUIRE(callbacks_ran_flags.all());
90
91 for (std::size_t i = 0; i < delays.size(); i++) {
92 const double delay = static_cast<double>(delays[i]);
93 const double micro = delay / 1000.0f;
94 const double mili = micro / 1000.0f;
95 printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
96 }
97}
98
99TEST_CASE("HostTiming[BasicOrderNoPausing]", "[core]") {
100 ScopeInit guard;
101 auto& core_timing = guard.core_timing;
102 std::vector<std::shared_ptr<Core::HostTiming::EventType>> events{
103 Core::HostTiming::CreateEvent("callbackA", HostCallbackTemplate<0>),
104 Core::HostTiming::CreateEvent("callbackB", HostCallbackTemplate<1>),
105 Core::HostTiming::CreateEvent("callbackC", HostCallbackTemplate<2>),
106 Core::HostTiming::CreateEvent("callbackD", HostCallbackTemplate<3>),
107 Core::HostTiming::CreateEvent("callbackE", HostCallbackTemplate<4>),
108 };
109
110 core_timing.SyncPause(true);
111 core_timing.SyncPause(false);
112
113 expected_callback = 0;
114
115 u64 start = core_timing.GetGlobalTimeNs().count();
116 u64 one_micro = 1000U;
117 for (std::size_t i = 0; i < events.size(); i++) {
118 u64 order = calls_order[i];
119 core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]);
120 }
121 u64 end = core_timing.GetGlobalTimeNs().count();
122 const double scheduling_time = static_cast<double>(end - start);
123 const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));
124
125 while (core_timing.HasPendingEvents())
126 ;
127
128 REQUIRE(callbacks_ran_flags.all());
129
130 for (std::size_t i = 0; i < delays.size(); i++) {
131 const double delay = static_cast<double>(delays[i]);
132 const double micro = delay / 1000.0f;
133 const double mili = micro / 1000.0f;
134 printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
135 }
136
137 const double micro = scheduling_time / 1000.0f;
138 const double mili = micro / 1000.0f;
139 printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili);
140 printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f,
141 timer_time / 1000000.f);
142}