another try
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/cityhash.cpp
|
||||
common/fibers.cpp
|
||||
common/host_memory.cpp
|
||||
common/param_package.cpp
|
||||
common/ring_buffer.cpp
|
||||
common/unique_function.cpp
|
||||
core/core_timing.cpp
|
||||
core/internal_network/network.cpp
|
||||
tests.cpp
|
||||
video_core/buffer_base.cpp
|
||||
input_common/calibration_configuration_job.cpp
|
||||
)
|
||||
|
||||
create_target_directory_groups(tests)
|
||||
|
||||
target_link_libraries(tests PRIVATE common core input_common)
|
||||
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2 Threads::Threads)
|
||||
|
||||
add_test(NAME tests COMMAND tests)
|
||||
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/cityhash.cpp
|
||||
common/fibers.cpp
|
||||
common/host_memory.cpp
|
||||
common/param_package.cpp
|
||||
common/ring_buffer.cpp
|
||||
common/unique_function.cpp
|
||||
core/core_timing.cpp
|
||||
core/internal_network/network.cpp
|
||||
tests.cpp
|
||||
video_core/buffer_base.cpp
|
||||
input_common/calibration_configuration_job.cpp
|
||||
)
|
||||
|
||||
create_target_directory_groups(tests)
|
||||
|
||||
target_link_libraries(tests PRIVATE common core input_common)
|
||||
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2 Threads::Threads)
|
||||
|
||||
add_test(NAME tests COMMAND tests)
|
||||
|
@@ -1,89 +1,89 @@
|
||||
// SPDX-FileCopyrightText: 2019 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <catch2/catch.hpp>
|
||||
#include "common/bit_field.h"
|
||||
|
||||
TEST_CASE("BitField", "[common]") {
|
||||
enum class TestEnum : u32 {
|
||||
A = 0b10111101,
|
||||
B = 0b10101110,
|
||||
C = 0b00001111,
|
||||
};
|
||||
|
||||
union LEBitField {
|
||||
u32_le raw;
|
||||
BitField<0, 6, u32> a;
|
||||
BitField<6, 4, s32> b;
|
||||
BitField<10, 8, TestEnum> c;
|
||||
BitField<18, 14, u32> d;
|
||||
} le_bitfield;
|
||||
|
||||
union BEBitField {
|
||||
u32_be raw;
|
||||
BitFieldBE<0, 6, u32> a;
|
||||
BitFieldBE<6, 4, s32> b;
|
||||
BitFieldBE<10, 8, TestEnum> c;
|
||||
BitFieldBE<18, 14, u32> d;
|
||||
} be_bitfield;
|
||||
|
||||
static_assert(sizeof(LEBitField) == sizeof(u32));
|
||||
static_assert(sizeof(BEBitField) == sizeof(u32));
|
||||
static_assert(std::is_trivially_copyable_v<LEBitField>);
|
||||
static_assert(std::is_trivially_copyable_v<BEBitField>);
|
||||
|
||||
std::array<u8, 4> raw{{
|
||||
0b01101100,
|
||||
0b11110110,
|
||||
0b10111010,
|
||||
0b11101100,
|
||||
}};
|
||||
|
||||
std::memcpy(&le_bitfield, &raw, sizeof(raw));
|
||||
std::memcpy(&be_bitfield, &raw, sizeof(raw));
|
||||
|
||||
// bit fields: 11101100101110'10111101'1001'101100
|
||||
REQUIRE(le_bitfield.raw == 0b11101100'10111010'11110110'01101100);
|
||||
REQUIRE(le_bitfield.a == 0b101100);
|
||||
REQUIRE(le_bitfield.b == -7); // 1001 as two's complement
|
||||
REQUIRE(le_bitfield.c == TestEnum::A);
|
||||
REQUIRE(le_bitfield.d == 0b11101100101110);
|
||||
|
||||
le_bitfield.a.Assign(0b000111);
|
||||
le_bitfield.b.Assign(-1);
|
||||
le_bitfield.c.Assign(TestEnum::C);
|
||||
le_bitfield.d.Assign(0b01010101010101);
|
||||
std::memcpy(&raw, &le_bitfield, sizeof(raw));
|
||||
// bit fields: 01010101010101'00001111'1111'000111
|
||||
REQUIRE(le_bitfield.raw == 0b01010101'01010100'00111111'11000111);
|
||||
REQUIRE(raw == std::array<u8, 4>{{
|
||||
0b11000111,
|
||||
0b00111111,
|
||||
0b01010100,
|
||||
0b01010101,
|
||||
}});
|
||||
|
||||
// bit fields: 01101100111101'10101110'1011'101100
|
||||
REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100U);
|
||||
REQUIRE(be_bitfield.a == 0b101100);
|
||||
REQUIRE(be_bitfield.b == -5); // 1011 as two's complement
|
||||
REQUIRE(be_bitfield.c == TestEnum::B);
|
||||
REQUIRE(be_bitfield.d == 0b01101100111101);
|
||||
|
||||
be_bitfield.a.Assign(0b000111);
|
||||
be_bitfield.b.Assign(-1);
|
||||
be_bitfield.c.Assign(TestEnum::C);
|
||||
be_bitfield.d.Assign(0b01010101010101);
|
||||
std::memcpy(&raw, &be_bitfield, sizeof(raw));
|
||||
// bit fields: 01010101010101'00001111'1111'000111
|
||||
REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111U);
|
||||
REQUIRE(raw == std::array<u8, 4>{{
|
||||
0b01010101,
|
||||
0b01010100,
|
||||
0b00111111,
|
||||
0b11000111,
|
||||
}});
|
||||
}
|
||||
// SPDX-FileCopyrightText: 2019 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <catch2/catch.hpp>
|
||||
#include "common/bit_field.h"
|
||||
|
||||
TEST_CASE("BitField", "[common]") {
|
||||
enum class TestEnum : u32 {
|
||||
A = 0b10111101,
|
||||
B = 0b10101110,
|
||||
C = 0b00001111,
|
||||
};
|
||||
|
||||
union LEBitField {
|
||||
u32_le raw;
|
||||
BitField<0, 6, u32> a;
|
||||
BitField<6, 4, s32> b;
|
||||
BitField<10, 8, TestEnum> c;
|
||||
BitField<18, 14, u32> d;
|
||||
} le_bitfield;
|
||||
|
||||
union BEBitField {
|
||||
u32_be raw;
|
||||
BitFieldBE<0, 6, u32> a;
|
||||
BitFieldBE<6, 4, s32> b;
|
||||
BitFieldBE<10, 8, TestEnum> c;
|
||||
BitFieldBE<18, 14, u32> d;
|
||||
} be_bitfield;
|
||||
|
||||
static_assert(sizeof(LEBitField) == sizeof(u32));
|
||||
static_assert(sizeof(BEBitField) == sizeof(u32));
|
||||
static_assert(std::is_trivially_copyable_v<LEBitField>);
|
||||
static_assert(std::is_trivially_copyable_v<BEBitField>);
|
||||
|
||||
std::array<u8, 4> raw{{
|
||||
0b01101100,
|
||||
0b11110110,
|
||||
0b10111010,
|
||||
0b11101100,
|
||||
}};
|
||||
|
||||
std::memcpy(&le_bitfield, &raw, sizeof(raw));
|
||||
std::memcpy(&be_bitfield, &raw, sizeof(raw));
|
||||
|
||||
// bit fields: 11101100101110'10111101'1001'101100
|
||||
REQUIRE(le_bitfield.raw == 0b11101100'10111010'11110110'01101100);
|
||||
REQUIRE(le_bitfield.a == 0b101100);
|
||||
REQUIRE(le_bitfield.b == -7); // 1001 as two's complement
|
||||
REQUIRE(le_bitfield.c == TestEnum::A);
|
||||
REQUIRE(le_bitfield.d == 0b11101100101110);
|
||||
|
||||
le_bitfield.a.Assign(0b000111);
|
||||
le_bitfield.b.Assign(-1);
|
||||
le_bitfield.c.Assign(TestEnum::C);
|
||||
le_bitfield.d.Assign(0b01010101010101);
|
||||
std::memcpy(&raw, &le_bitfield, sizeof(raw));
|
||||
// bit fields: 01010101010101'00001111'1111'000111
|
||||
REQUIRE(le_bitfield.raw == 0b01010101'01010100'00111111'11000111);
|
||||
REQUIRE(raw == std::array<u8, 4>{{
|
||||
0b11000111,
|
||||
0b00111111,
|
||||
0b01010100,
|
||||
0b01010101,
|
||||
}});
|
||||
|
||||
// bit fields: 01101100111101'10101110'1011'101100
|
||||
REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100U);
|
||||
REQUIRE(be_bitfield.a == 0b101100);
|
||||
REQUIRE(be_bitfield.b == -5); // 1011 as two's complement
|
||||
REQUIRE(be_bitfield.c == TestEnum::B);
|
||||
REQUIRE(be_bitfield.d == 0b01101100111101);
|
||||
|
||||
be_bitfield.a.Assign(0b000111);
|
||||
be_bitfield.b.Assign(-1);
|
||||
be_bitfield.c.Assign(TestEnum::C);
|
||||
be_bitfield.d.Assign(0b01010101010101);
|
||||
std::memcpy(&raw, &be_bitfield, sizeof(raw));
|
||||
// bit fields: 01010101010101'00001111'1111'000111
|
||||
REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111U);
|
||||
REQUIRE(raw == std::array<u8, 4>{{
|
||||
0b01010101,
|
||||
0b01010100,
|
||||
0b00111111,
|
||||
0b11000111,
|
||||
}});
|
||||
}
|
||||
|
@@ -1,21 +1,21 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/cityhash.h"
|
||||
|
||||
constexpr char msg[] = "The blue frogs are singing under the crimson sky.\n"
|
||||
"It is time to run, Robert.";
|
||||
|
||||
using namespace Common;
|
||||
|
||||
TEST_CASE("CityHash", "[common]") {
|
||||
// These test results were built against a known good version.
|
||||
REQUIRE(CityHash64(msg, sizeof(msg)) == 0x92d5c2e9cbfbbc01);
|
||||
REQUIRE(CityHash64WithSeed(msg, sizeof(msg), 0xdead) == 0xbfbe93f21a2820dd);
|
||||
REQUIRE(CityHash64WithSeeds(msg, sizeof(msg), 0xbeef, 0xcafe) == 0xb343317955fc8a06);
|
||||
REQUIRE(CityHash128(msg, sizeof(msg)) == u128{0x98e60d0423747eaa, 0xd8694c5b6fcaede9});
|
||||
REQUIRE(CityHash128WithSeed(msg, sizeof(msg), {0xdead, 0xbeef}) ==
|
||||
u128{0xf0307dba81199ebe, 0xd77764e0c4a9eb74});
|
||||
}
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/cityhash.h"
|
||||
|
||||
constexpr char msg[] = "The blue frogs are singing under the crimson sky.\n"
|
||||
"It is time to run, Robert.";
|
||||
|
||||
using namespace Common;
|
||||
|
||||
TEST_CASE("CityHash", "[common]") {
|
||||
// These test results were built against a known good version.
|
||||
REQUIRE(CityHash64(msg, sizeof(msg)) == 0x92d5c2e9cbfbbc01);
|
||||
REQUIRE(CityHash64WithSeed(msg, sizeof(msg), 0xdead) == 0xbfbe93f21a2820dd);
|
||||
REQUIRE(CityHash64WithSeeds(msg, sizeof(msg), 0xbeef, 0xcafe) == 0xb343317955fc8a06);
|
||||
REQUIRE(CityHash128(msg, sizeof(msg)) == u128{0x98e60d0423747eaa, 0xd8694c5b6fcaede9});
|
||||
REQUIRE(CityHash128WithSeed(msg, sizeof(msg), {0xdead, 0xbeef}) ==
|
||||
u128{0xf0307dba81199ebe, 0xd77764e0c4a9eb74});
|
||||
}
|
||||
|
@@ -1,313 +1,313 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/fiber.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class ThreadIds {
|
||||
public:
|
||||
void Register(u32 id) {
|
||||
const auto thread_id = std::this_thread::get_id();
|
||||
std::scoped_lock lock{mutex};
|
||||
if (ids.contains(thread_id)) {
|
||||
throw std::logic_error{"Registering the same thread twice"};
|
||||
}
|
||||
ids.emplace(thread_id, id);
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 Get() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return ids.at(std::this_thread::get_id());
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::unordered_map<std::thread::id, u32> ids;
|
||||
};
|
||||
|
||||
class TestControl1 {
|
||||
public:
|
||||
TestControl1() = default;
|
||||
|
||||
void DoWork() {
|
||||
const u32 id = thread_ids.Get();
|
||||
u32 value = items[id];
|
||||
for (u32 i = 0; i < id; i++) {
|
||||
value++;
|
||||
}
|
||||
results[id] = value;
|
||||
Fiber::YieldTo(work_fibers[id], *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void ExecuteThread(u32 id);
|
||||
|
||||
ThreadIds thread_ids;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
|
||||
std::vector<u32> items;
|
||||
std::vector<u32> results;
|
||||
};
|
||||
|
||||
void TestControl1::ExecuteThread(u32 id) {
|
||||
thread_ids.Register(id);
|
||||
auto thread_fiber = Fiber::ThreadToFiber();
|
||||
thread_fibers[id] = thread_fiber;
|
||||
work_fibers[id] = std::make_shared<Fiber>([this] { DoWork(); });
|
||||
items[id] = rand() % 256;
|
||||
Fiber::YieldTo(thread_fibers[id], *work_fibers[id]);
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
/** This test checks for fiber setup configuration and validates that fibers are
|
||||
* doing all the work required.
|
||||
*/
|
||||
TEST_CASE("Fibers::Setup", "[common]") {
|
||||
constexpr std::size_t num_threads = 7;
|
||||
TestControl1 test_control{};
|
||||
test_control.thread_fibers.resize(num_threads);
|
||||
test_control.work_fibers.resize(num_threads);
|
||||
test_control.items.resize(num_threads, 0);
|
||||
test_control.results.resize(num_threads, 0);
|
||||
std::vector<std::thread> threads;
|
||||
for (u32 i = 0; i < num_threads; i++) {
|
||||
threads.emplace_back([&test_control, i] { test_control.ExecuteThread(i); });
|
||||
}
|
||||
for (u32 i = 0; i < num_threads; i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
for (u32 i = 0; i < num_threads; i++) {
|
||||
REQUIRE(test_control.items[i] + i == test_control.results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
class TestControl2 {
|
||||
public:
|
||||
TestControl2() = default;
|
||||
|
||||
void DoWork1() {
|
||||
trap2 = false;
|
||||
while (trap.load())
|
||||
;
|
||||
for (u32 i = 0; i < 12000; i++) {
|
||||
value1 += i;
|
||||
}
|
||||
Fiber::YieldTo(fiber1, *fiber3);
|
||||
const u32 id = thread_ids.Get();
|
||||
assert1 = id == 1;
|
||||
value2 += 5000;
|
||||
Fiber::YieldTo(fiber1, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void DoWork2() {
|
||||
while (trap2.load())
|
||||
;
|
||||
value2 = 2000;
|
||||
trap = false;
|
||||
Fiber::YieldTo(fiber2, *fiber1);
|
||||
assert3 = false;
|
||||
}
|
||||
|
||||
void DoWork3() {
|
||||
const u32 id = thread_ids.Get();
|
||||
assert2 = id == 0;
|
||||
value1 += 1000;
|
||||
Fiber::YieldTo(fiber3, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void ExecuteThread(u32 id);
|
||||
|
||||
void CallFiber1() {
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(thread_fibers[id], *fiber1);
|
||||
}
|
||||
|
||||
void CallFiber2() {
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(thread_fibers[id], *fiber2);
|
||||
}
|
||||
|
||||
void Exit();
|
||||
|
||||
bool assert1{};
|
||||
bool assert2{};
|
||||
bool assert3{true};
|
||||
u32 value1{};
|
||||
u32 value2{};
|
||||
std::atomic<bool> trap{true};
|
||||
std::atomic<bool> trap2{true};
|
||||
ThreadIds thread_ids;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
|
||||
std::shared_ptr<Common::Fiber> fiber1;
|
||||
std::shared_ptr<Common::Fiber> fiber2;
|
||||
std::shared_ptr<Common::Fiber> fiber3;
|
||||
};
|
||||
|
||||
void TestControl2::ExecuteThread(u32 id) {
|
||||
thread_ids.Register(id);
|
||||
auto thread_fiber = Fiber::ThreadToFiber();
|
||||
thread_fibers[id] = thread_fiber;
|
||||
}
|
||||
|
||||
void TestControl2::Exit() {
|
||||
const u32 id = thread_ids.Get();
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
/** This test checks for fiber thread exchange configuration and validates that fibers are
|
||||
* that a fiber has been successfully transferred from one thread to another and that the TLS
|
||||
* region of the thread is kept while changing fibers.
|
||||
*/
|
||||
TEST_CASE("Fibers::InterExchange", "[common]") {
|
||||
TestControl2 test_control{};
|
||||
test_control.thread_fibers.resize(2);
|
||||
test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
|
||||
test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
|
||||
test_control.fiber3 = std::make_shared<Fiber>([&test_control] { test_control.DoWork3(); });
|
||||
std::thread thread1{[&test_control] {
|
||||
test_control.ExecuteThread(0);
|
||||
test_control.CallFiber1();
|
||||
test_control.Exit();
|
||||
}};
|
||||
std::thread thread2{[&test_control] {
|
||||
test_control.ExecuteThread(1);
|
||||
test_control.CallFiber2();
|
||||
test_control.Exit();
|
||||
}};
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
REQUIRE(test_control.assert1);
|
||||
REQUIRE(test_control.assert2);
|
||||
REQUIRE(test_control.assert3);
|
||||
REQUIRE(test_control.value2 == 7000);
|
||||
u32 cal_value = 0;
|
||||
for (u32 i = 0; i < 12000; i++) {
|
||||
cal_value += i;
|
||||
}
|
||||
cal_value += 1000;
|
||||
REQUIRE(test_control.value1 == cal_value);
|
||||
}
|
||||
|
||||
class TestControl3 {
|
||||
public:
|
||||
TestControl3() = default;
|
||||
|
||||
void DoWork1() {
|
||||
value1 += 1;
|
||||
Fiber::YieldTo(fiber1, *fiber2);
|
||||
const u32 id = thread_ids.Get();
|
||||
value3 += 1;
|
||||
Fiber::YieldTo(fiber1, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void DoWork2() {
|
||||
value2 += 1;
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(fiber2, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void ExecuteThread(u32 id);
|
||||
|
||||
void CallFiber1() {
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(thread_fibers[id], *fiber1);
|
||||
}
|
||||
|
||||
void Exit();
|
||||
|
||||
u32 value1{};
|
||||
u32 value2{};
|
||||
u32 value3{};
|
||||
ThreadIds thread_ids;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
|
||||
std::shared_ptr<Common::Fiber> fiber1;
|
||||
std::shared_ptr<Common::Fiber> fiber2;
|
||||
};
|
||||
|
||||
void TestControl3::ExecuteThread(u32 id) {
|
||||
thread_ids.Register(id);
|
||||
auto thread_fiber = Fiber::ThreadToFiber();
|
||||
thread_fibers[id] = thread_fiber;
|
||||
}
|
||||
|
||||
void TestControl3::Exit() {
|
||||
const u32 id = thread_ids.Get();
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
/** This test checks for one two threads racing for starting the same fiber.
|
||||
* It checks execution occurred in an ordered manner and by no time there were
|
||||
* two contexts at the same time.
|
||||
*/
|
||||
TEST_CASE("Fibers::StartRace", "[common]") {
|
||||
TestControl3 test_control{};
|
||||
test_control.thread_fibers.resize(2);
|
||||
test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
|
||||
test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
|
||||
const auto race_function{[&test_control](u32 id) {
|
||||
test_control.ExecuteThread(id);
|
||||
test_control.CallFiber1();
|
||||
test_control.Exit();
|
||||
}};
|
||||
std::thread thread1([&] { race_function(0); });
|
||||
std::thread thread2([&] { race_function(1); });
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
REQUIRE(test_control.value1 == 1);
|
||||
REQUIRE(test_control.value2 == 1);
|
||||
REQUIRE(test_control.value3 == 1);
|
||||
}
|
||||
|
||||
class TestControl4;
|
||||
|
||||
class TestControl4 {
|
||||
public:
|
||||
TestControl4() {
|
||||
fiber1 = std::make_shared<Fiber>([this] { DoWork(); });
|
||||
goal_reached = false;
|
||||
rewinded = false;
|
||||
}
|
||||
|
||||
void Execute() {
|
||||
thread_fiber = Fiber::ThreadToFiber();
|
||||
Fiber::YieldTo(thread_fiber, *fiber1);
|
||||
thread_fiber->Exit();
|
||||
}
|
||||
|
||||
void DoWork() {
|
||||
fiber1->SetRewindPoint([this] { DoWork(); });
|
||||
if (rewinded) {
|
||||
goal_reached = true;
|
||||
Fiber::YieldTo(fiber1, *thread_fiber);
|
||||
}
|
||||
rewinded = true;
|
||||
fiber1->Rewind();
|
||||
}
|
||||
|
||||
std::shared_ptr<Common::Fiber> fiber1;
|
||||
std::shared_ptr<Common::Fiber> thread_fiber;
|
||||
bool goal_reached;
|
||||
bool rewinded;
|
||||
};
|
||||
|
||||
TEST_CASE("Fibers::Rewind", "[common]") {
|
||||
TestControl4 test_control{};
|
||||
test_control.Execute();
|
||||
REQUIRE(test_control.goal_reached);
|
||||
REQUIRE(test_control.rewinded);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/fiber.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class ThreadIds {
|
||||
public:
|
||||
void Register(u32 id) {
|
||||
const auto thread_id = std::this_thread::get_id();
|
||||
std::scoped_lock lock{mutex};
|
||||
if (ids.contains(thread_id)) {
|
||||
throw std::logic_error{"Registering the same thread twice"};
|
||||
}
|
||||
ids.emplace(thread_id, id);
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 Get() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return ids.at(std::this_thread::get_id());
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::unordered_map<std::thread::id, u32> ids;
|
||||
};
|
||||
|
||||
class TestControl1 {
|
||||
public:
|
||||
TestControl1() = default;
|
||||
|
||||
void DoWork() {
|
||||
const u32 id = thread_ids.Get();
|
||||
u32 value = items[id];
|
||||
for (u32 i = 0; i < id; i++) {
|
||||
value++;
|
||||
}
|
||||
results[id] = value;
|
||||
Fiber::YieldTo(work_fibers[id], *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void ExecuteThread(u32 id);
|
||||
|
||||
ThreadIds thread_ids;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
|
||||
std::vector<u32> items;
|
||||
std::vector<u32> results;
|
||||
};
|
||||
|
||||
void TestControl1::ExecuteThread(u32 id) {
|
||||
thread_ids.Register(id);
|
||||
auto thread_fiber = Fiber::ThreadToFiber();
|
||||
thread_fibers[id] = thread_fiber;
|
||||
work_fibers[id] = std::make_shared<Fiber>([this] { DoWork(); });
|
||||
items[id] = rand() % 256;
|
||||
Fiber::YieldTo(thread_fibers[id], *work_fibers[id]);
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
/** This test checks for fiber setup configuration and validates that fibers are
|
||||
* doing all the work required.
|
||||
*/
|
||||
TEST_CASE("Fibers::Setup", "[common]") {
|
||||
constexpr std::size_t num_threads = 7;
|
||||
TestControl1 test_control{};
|
||||
test_control.thread_fibers.resize(num_threads);
|
||||
test_control.work_fibers.resize(num_threads);
|
||||
test_control.items.resize(num_threads, 0);
|
||||
test_control.results.resize(num_threads, 0);
|
||||
std::vector<std::thread> threads;
|
||||
for (u32 i = 0; i < num_threads; i++) {
|
||||
threads.emplace_back([&test_control, i] { test_control.ExecuteThread(i); });
|
||||
}
|
||||
for (u32 i = 0; i < num_threads; i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
for (u32 i = 0; i < num_threads; i++) {
|
||||
REQUIRE(test_control.items[i] + i == test_control.results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
class TestControl2 {
|
||||
public:
|
||||
TestControl2() = default;
|
||||
|
||||
void DoWork1() {
|
||||
trap2 = false;
|
||||
while (trap.load())
|
||||
;
|
||||
for (u32 i = 0; i < 12000; i++) {
|
||||
value1 += i;
|
||||
}
|
||||
Fiber::YieldTo(fiber1, *fiber3);
|
||||
const u32 id = thread_ids.Get();
|
||||
assert1 = id == 1;
|
||||
value2 += 5000;
|
||||
Fiber::YieldTo(fiber1, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void DoWork2() {
|
||||
while (trap2.load())
|
||||
;
|
||||
value2 = 2000;
|
||||
trap = false;
|
||||
Fiber::YieldTo(fiber2, *fiber1);
|
||||
assert3 = false;
|
||||
}
|
||||
|
||||
void DoWork3() {
|
||||
const u32 id = thread_ids.Get();
|
||||
assert2 = id == 0;
|
||||
value1 += 1000;
|
||||
Fiber::YieldTo(fiber3, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void ExecuteThread(u32 id);
|
||||
|
||||
void CallFiber1() {
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(thread_fibers[id], *fiber1);
|
||||
}
|
||||
|
||||
void CallFiber2() {
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(thread_fibers[id], *fiber2);
|
||||
}
|
||||
|
||||
void Exit();
|
||||
|
||||
bool assert1{};
|
||||
bool assert2{};
|
||||
bool assert3{true};
|
||||
u32 value1{};
|
||||
u32 value2{};
|
||||
std::atomic<bool> trap{true};
|
||||
std::atomic<bool> trap2{true};
|
||||
ThreadIds thread_ids;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
|
||||
std::shared_ptr<Common::Fiber> fiber1;
|
||||
std::shared_ptr<Common::Fiber> fiber2;
|
||||
std::shared_ptr<Common::Fiber> fiber3;
|
||||
};
|
||||
|
||||
void TestControl2::ExecuteThread(u32 id) {
|
||||
thread_ids.Register(id);
|
||||
auto thread_fiber = Fiber::ThreadToFiber();
|
||||
thread_fibers[id] = thread_fiber;
|
||||
}
|
||||
|
||||
void TestControl2::Exit() {
|
||||
const u32 id = thread_ids.Get();
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
/** This test checks for fiber thread exchange configuration and validates that fibers are
|
||||
* that a fiber has been successfully transferred from one thread to another and that the TLS
|
||||
* region of the thread is kept while changing fibers.
|
||||
*/
|
||||
TEST_CASE("Fibers::InterExchange", "[common]") {
|
||||
TestControl2 test_control{};
|
||||
test_control.thread_fibers.resize(2);
|
||||
test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
|
||||
test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
|
||||
test_control.fiber3 = std::make_shared<Fiber>([&test_control] { test_control.DoWork3(); });
|
||||
std::thread thread1{[&test_control] {
|
||||
test_control.ExecuteThread(0);
|
||||
test_control.CallFiber1();
|
||||
test_control.Exit();
|
||||
}};
|
||||
std::thread thread2{[&test_control] {
|
||||
test_control.ExecuteThread(1);
|
||||
test_control.CallFiber2();
|
||||
test_control.Exit();
|
||||
}};
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
REQUIRE(test_control.assert1);
|
||||
REQUIRE(test_control.assert2);
|
||||
REQUIRE(test_control.assert3);
|
||||
REQUIRE(test_control.value2 == 7000);
|
||||
u32 cal_value = 0;
|
||||
for (u32 i = 0; i < 12000; i++) {
|
||||
cal_value += i;
|
||||
}
|
||||
cal_value += 1000;
|
||||
REQUIRE(test_control.value1 == cal_value);
|
||||
}
|
||||
|
||||
class TestControl3 {
|
||||
public:
|
||||
TestControl3() = default;
|
||||
|
||||
void DoWork1() {
|
||||
value1 += 1;
|
||||
Fiber::YieldTo(fiber1, *fiber2);
|
||||
const u32 id = thread_ids.Get();
|
||||
value3 += 1;
|
||||
Fiber::YieldTo(fiber1, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void DoWork2() {
|
||||
value2 += 1;
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(fiber2, *thread_fibers[id]);
|
||||
}
|
||||
|
||||
void ExecuteThread(u32 id);
|
||||
|
||||
void CallFiber1() {
|
||||
const u32 id = thread_ids.Get();
|
||||
Fiber::YieldTo(thread_fibers[id], *fiber1);
|
||||
}
|
||||
|
||||
void Exit();
|
||||
|
||||
u32 value1{};
|
||||
u32 value2{};
|
||||
u32 value3{};
|
||||
ThreadIds thread_ids;
|
||||
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
|
||||
std::shared_ptr<Common::Fiber> fiber1;
|
||||
std::shared_ptr<Common::Fiber> fiber2;
|
||||
};
|
||||
|
||||
void TestControl3::ExecuteThread(u32 id) {
|
||||
thread_ids.Register(id);
|
||||
auto thread_fiber = Fiber::ThreadToFiber();
|
||||
thread_fibers[id] = thread_fiber;
|
||||
}
|
||||
|
||||
void TestControl3::Exit() {
|
||||
const u32 id = thread_ids.Get();
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
/** This test checks for one two threads racing for starting the same fiber.
|
||||
* It checks execution occurred in an ordered manner and by no time there were
|
||||
* two contexts at the same time.
|
||||
*/
|
||||
TEST_CASE("Fibers::StartRace", "[common]") {
|
||||
TestControl3 test_control{};
|
||||
test_control.thread_fibers.resize(2);
|
||||
test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
|
||||
test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
|
||||
const auto race_function{[&test_control](u32 id) {
|
||||
test_control.ExecuteThread(id);
|
||||
test_control.CallFiber1();
|
||||
test_control.Exit();
|
||||
}};
|
||||
std::thread thread1([&] { race_function(0); });
|
||||
std::thread thread2([&] { race_function(1); });
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
REQUIRE(test_control.value1 == 1);
|
||||
REQUIRE(test_control.value2 == 1);
|
||||
REQUIRE(test_control.value3 == 1);
|
||||
}
|
||||
|
||||
class TestControl4;
|
||||
|
||||
class TestControl4 {
|
||||
public:
|
||||
TestControl4() {
|
||||
fiber1 = std::make_shared<Fiber>([this] { DoWork(); });
|
||||
goal_reached = false;
|
||||
rewinded = false;
|
||||
}
|
||||
|
||||
void Execute() {
|
||||
thread_fiber = Fiber::ThreadToFiber();
|
||||
Fiber::YieldTo(thread_fiber, *fiber1);
|
||||
thread_fiber->Exit();
|
||||
}
|
||||
|
||||
void DoWork() {
|
||||
fiber1->SetRewindPoint([this] { DoWork(); });
|
||||
if (rewinded) {
|
||||
goal_reached = true;
|
||||
Fiber::YieldTo(fiber1, *thread_fiber);
|
||||
}
|
||||
rewinded = true;
|
||||
fiber1->Rewind();
|
||||
}
|
||||
|
||||
std::shared_ptr<Common::Fiber> fiber1;
|
||||
std::shared_ptr<Common::Fiber> thread_fiber;
|
||||
bool goal_reached;
|
||||
bool rewinded;
|
||||
};
|
||||
|
||||
TEST_CASE("Fibers::Rewind", "[common]") {
|
||||
TestControl4 test_control{};
|
||||
test_control.Execute();
|
||||
REQUIRE(test_control.goal_reached);
|
||||
REQUIRE(test_control.rewinded);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
@@ -1,184 +1,184 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/host_memory.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
using Common::HostMemory;
|
||||
using namespace Common::Literals;
|
||||
|
||||
static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
|
||||
static constexpr size_t BACKING_SIZE = 4_GiB;
|
||||
|
||||
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
|
||||
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
|
||||
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple map", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x8000, 0x1000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[0] = 50;
|
||||
REQUIRE(data[0] == 50);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple mirror map", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
mem.Map(0x8000, 0x4000, 0x1000);
|
||||
|
||||
volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
|
||||
volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
|
||||
mirror_b[0] = 76;
|
||||
REQUIRE(mirror_a[0x1000] == 76);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[75] = 50;
|
||||
REQUIRE(data[75] == 50);
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[0] = 50;
|
||||
REQUIRE(data[0] == 50);
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
REQUIRE(data[0] == 50);
|
||||
|
||||
mem.Map(0x7000, 0x2000, 0x5000);
|
||||
REQUIRE(data[0x3000] == 50);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Nieche allocation", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x20000);
|
||||
mem.Unmap(0x0000, 0x4000);
|
||||
mem.Map(0x1000, 0, 0x2000);
|
||||
mem.Map(0x3000, 0, 0x1000);
|
||||
mem.Map(0, 0, 0x1000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Full unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x8000, 0x4000);
|
||||
mem.Map(0x6000, 0, 0x16000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Unmap(0x2000, 0x4000);
|
||||
mem.Map(0x2000, 0x80000, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x6000, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x2000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x1b000);
|
||||
mem.Unmap(0x3000, 0x1c000);
|
||||
mem.Map(0x3000, 0, 0x20000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Unmap(0x2000, 0x4000);
|
||||
mem.Map(0x2000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap to origin", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x4000, 0x4000);
|
||||
mem.Map(0, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap to right", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x8000, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x1000] = 17;
|
||||
|
||||
mem.Unmap(0x6000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x1000] == 17);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x3000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x4000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x3000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x0000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x1000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x0000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x2000);
|
||||
mem.Map(0x6000, 0x20000, 0x2000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x0000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x0000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/host_memory.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
using Common::HostMemory;
|
||||
using namespace Common::Literals;
|
||||
|
||||
static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
|
||||
static constexpr size_t BACKING_SIZE = 4_GiB;
|
||||
|
||||
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
|
||||
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
|
||||
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple map", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x8000, 0x1000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[0] = 50;
|
||||
REQUIRE(data[0] == 50);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple mirror map", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
mem.Map(0x8000, 0x4000, 0x1000);
|
||||
|
||||
volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
|
||||
volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
|
||||
mirror_b[0] = 76;
|
||||
REQUIRE(mirror_a[0x1000] == 76);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[75] = 50;
|
||||
REQUIRE(data[75] == 50);
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[0] = 50;
|
||||
REQUIRE(data[0] == 50);
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
REQUIRE(data[0] == 50);
|
||||
|
||||
mem.Map(0x7000, 0x2000, 0x5000);
|
||||
REQUIRE(data[0x3000] == 50);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Nieche allocation", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x20000);
|
||||
mem.Unmap(0x0000, 0x4000);
|
||||
mem.Map(0x1000, 0, 0x2000);
|
||||
mem.Map(0x3000, 0, 0x1000);
|
||||
mem.Map(0, 0, 0x1000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Full unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x8000, 0x4000);
|
||||
mem.Map(0x6000, 0, 0x16000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Unmap(0x2000, 0x4000);
|
||||
mem.Map(0x2000, 0x80000, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x6000, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x2000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x1b000);
|
||||
mem.Unmap(0x3000, 0x1c000);
|
||||
mem.Map(0x3000, 0, 0x20000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Unmap(0x2000, 0x4000);
|
||||
mem.Map(0x2000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap to origin", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x4000, 0x4000);
|
||||
mem.Map(0, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap to right", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x8000, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x1000] = 17;
|
||||
|
||||
mem.Unmap(0x6000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x1000] == 17);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x3000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x4000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x3000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x0000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x1000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x0000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x2000);
|
||||
mem.Map(0x6000, 0x20000, 0x2000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x0000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x0000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
@@ -1,28 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <math.h>
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/param_package.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("ParamPackage", "[common]") {
|
||||
Common::Log::DisableLoggingInTests();
|
||||
ParamPackage original{
|
||||
{"abc", "xyz"},
|
||||
{"def", "42"},
|
||||
{"jkl", "$$:1:$2$,3"},
|
||||
};
|
||||
original.Set("ghi", 3.14f);
|
||||
ParamPackage copy(original.Serialize());
|
||||
REQUIRE(copy.Get("abc", "") == "xyz");
|
||||
REQUIRE(copy.Get("def", 0) == 42);
|
||||
REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f);
|
||||
REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3");
|
||||
REQUIRE(copy.Get("mno", "uvw") == "uvw");
|
||||
REQUIRE(copy.Get("abc", 42) == 42);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <math.h>
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/param_package.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("ParamPackage", "[common]") {
|
||||
Common::Log::DisableLoggingInTests();
|
||||
ParamPackage original{
|
||||
{"abc", "xyz"},
|
||||
{"def", "42"},
|
||||
{"jkl", "$$:1:$2$,3"},
|
||||
};
|
||||
original.Set("ghi", 3.14f);
|
||||
ParamPackage copy(original.Serialize());
|
||||
REQUIRE(copy.Get("abc", "") == "xyz");
|
||||
REQUIRE(copy.Get("def", 0) == 42);
|
||||
REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f);
|
||||
REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3");
|
||||
REQUIRE(copy.Get("mno", "uvw") == "uvw");
|
||||
REQUIRE(copy.Get("abc", 42) == 42);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
@@ -1,129 +1,129 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <catch2/catch.hpp>
|
||||
#include "common/ring_buffer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("RingBuffer: Basic Tests", "[common]") {
|
||||
RingBuffer<char, 4> buf;
|
||||
|
||||
// Pushing values into a ring buffer with space should succeed.
|
||||
for (std::size_t i = 0; i < 4; i++) {
|
||||
const char elem = static_cast<char>(i);
|
||||
const std::size_t count = buf.Push(&elem, 1);
|
||||
REQUIRE(count == 1U);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4U);
|
||||
|
||||
// Pushing values into a full ring buffer should fail.
|
||||
{
|
||||
const char elem = static_cast<char>(42);
|
||||
const std::size_t count = buf.Push(&elem, 1);
|
||||
REQUIRE(count == 0U);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4U);
|
||||
|
||||
// Popping multiple values from a ring buffer with values should succeed.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop(2);
|
||||
REQUIRE(popped.size() == 2U);
|
||||
REQUIRE(popped[0] == 0);
|
||||
REQUIRE(popped[1] == 1);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 2U);
|
||||
|
||||
// Popping a single value from a ring buffer with values should succeed.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop(1);
|
||||
REQUIRE(popped.size() == 1U);
|
||||
REQUIRE(popped[0] == 2);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 1U);
|
||||
|
||||
// Pushing more values than space available should partially suceed.
|
||||
{
|
||||
std::vector<char> to_push(6);
|
||||
std::iota(to_push.begin(), to_push.end(), 88);
|
||||
const std::size_t count = buf.Push(to_push);
|
||||
REQUIRE(count == 3U);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4U);
|
||||
|
||||
// Doing an unlimited pop should pop all values.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop();
|
||||
REQUIRE(popped.size() == 4U);
|
||||
REQUIRE(popped[0] == 3);
|
||||
REQUIRE(popped[1] == 88);
|
||||
REQUIRE(popped[2] == 89);
|
||||
REQUIRE(popped[3] == 90);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 0U);
|
||||
}
|
||||
|
||||
TEST_CASE("RingBuffer: Threaded Test", "[common]") {
|
||||
RingBuffer<char, 8> buf;
|
||||
const char seed = 42;
|
||||
const std::size_t count = 1000000;
|
||||
std::size_t full = 0;
|
||||
std::size_t empty = 0;
|
||||
|
||||
const auto next_value = [](std::array<char, 2>& value) {
|
||||
value[0] += 1;
|
||||
value[1] += 2;
|
||||
};
|
||||
|
||||
std::thread producer{[&] {
|
||||
std::array<char, 2> value = {seed, seed};
|
||||
std::size_t i = 0;
|
||||
while (i < count) {
|
||||
if (const std::size_t c = buf.Push(&value[0], 2); c > 0) {
|
||||
REQUIRE(c == 2U);
|
||||
i++;
|
||||
next_value(value);
|
||||
} else {
|
||||
full++;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread consumer{[&] {
|
||||
std::array<char, 2> value = {seed, seed};
|
||||
std::size_t i = 0;
|
||||
while (i < count) {
|
||||
if (const std::vector<char> v = buf.Pop(2); v.size() > 0) {
|
||||
REQUIRE(v.size() == 2U);
|
||||
REQUIRE(v[0] == value[0]);
|
||||
REQUIRE(v[1] == value[1]);
|
||||
i++;
|
||||
next_value(value);
|
||||
} else {
|
||||
empty++;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
producer.join();
|
||||
consumer.join();
|
||||
|
||||
REQUIRE(buf.Size() == 0U);
|
||||
printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <catch2/catch.hpp>
|
||||
#include "common/ring_buffer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("RingBuffer: Basic Tests", "[common]") {
|
||||
RingBuffer<char, 4> buf;
|
||||
|
||||
// Pushing values into a ring buffer with space should succeed.
|
||||
for (std::size_t i = 0; i < 4; i++) {
|
||||
const char elem = static_cast<char>(i);
|
||||
const std::size_t count = buf.Push(&elem, 1);
|
||||
REQUIRE(count == 1U);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4U);
|
||||
|
||||
// Pushing values into a full ring buffer should fail.
|
||||
{
|
||||
const char elem = static_cast<char>(42);
|
||||
const std::size_t count = buf.Push(&elem, 1);
|
||||
REQUIRE(count == 0U);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4U);
|
||||
|
||||
// Popping multiple values from a ring buffer with values should succeed.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop(2);
|
||||
REQUIRE(popped.size() == 2U);
|
||||
REQUIRE(popped[0] == 0);
|
||||
REQUIRE(popped[1] == 1);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 2U);
|
||||
|
||||
// Popping a single value from a ring buffer with values should succeed.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop(1);
|
||||
REQUIRE(popped.size() == 1U);
|
||||
REQUIRE(popped[0] == 2);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 1U);
|
||||
|
||||
// Pushing more values than space available should partially suceed.
|
||||
{
|
||||
std::vector<char> to_push(6);
|
||||
std::iota(to_push.begin(), to_push.end(), 88);
|
||||
const std::size_t count = buf.Push(to_push);
|
||||
REQUIRE(count == 3U);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4U);
|
||||
|
||||
// Doing an unlimited pop should pop all values.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop();
|
||||
REQUIRE(popped.size() == 4U);
|
||||
REQUIRE(popped[0] == 3);
|
||||
REQUIRE(popped[1] == 88);
|
||||
REQUIRE(popped[2] == 89);
|
||||
REQUIRE(popped[3] == 90);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 0U);
|
||||
}
|
||||
|
||||
TEST_CASE("RingBuffer: Threaded Test", "[common]") {
|
||||
RingBuffer<char, 8> buf;
|
||||
const char seed = 42;
|
||||
const std::size_t count = 1000000;
|
||||
std::size_t full = 0;
|
||||
std::size_t empty = 0;
|
||||
|
||||
const auto next_value = [](std::array<char, 2>& value) {
|
||||
value[0] += 1;
|
||||
value[1] += 2;
|
||||
};
|
||||
|
||||
std::thread producer{[&] {
|
||||
std::array<char, 2> value = {seed, seed};
|
||||
std::size_t i = 0;
|
||||
while (i < count) {
|
||||
if (const std::size_t c = buf.Push(&value[0], 2); c > 0) {
|
||||
REQUIRE(c == 2U);
|
||||
i++;
|
||||
next_value(value);
|
||||
} else {
|
||||
full++;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread consumer{[&] {
|
||||
std::array<char, 2> value = {seed, seed};
|
||||
std::size_t i = 0;
|
||||
while (i < count) {
|
||||
if (const std::vector<char> v = buf.Pop(2); v.size() > 0) {
|
||||
REQUIRE(v.size() == 2U);
|
||||
REQUIRE(v[0] == value[0]);
|
||||
REQUIRE(v[1] == value[1]);
|
||||
i++;
|
||||
next_value(value);
|
||||
} else {
|
||||
empty++;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
producer.join();
|
||||
consumer.join();
|
||||
|
||||
REQUIRE(buf.Size() == 0U);
|
||||
printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
@@ -1,109 +1,109 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/unique_function.h"
|
||||
|
||||
namespace {
|
||||
struct Noisy {
|
||||
Noisy() : state{"Default constructed"} {}
|
||||
Noisy(Noisy&& rhs) noexcept : state{"Move constructed"} {
|
||||
rhs.state = "Moved away";
|
||||
}
|
||||
Noisy& operator=(Noisy&& rhs) noexcept {
|
||||
state = "Move assigned";
|
||||
rhs.state = "Moved away";
|
||||
return *this;
|
||||
}
|
||||
Noisy(const Noisy&) : state{"Copied constructed"} {}
|
||||
Noisy& operator=(const Noisy&) {
|
||||
state = "Copied assigned";
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string state;
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
TEST_CASE("UniqueFunction", "[common]") {
|
||||
SECTION("Capture reference") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func = [&value] { value = 5; };
|
||||
func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Capture pointer") {
|
||||
int value = 0;
|
||||
int* pointer = &value;
|
||||
Common::UniqueFunction<void> func = [pointer] { *pointer = 5; };
|
||||
func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Move object") {
|
||||
Noisy noisy;
|
||||
REQUIRE(noisy.state == "Default constructed");
|
||||
|
||||
Common::UniqueFunction<void> func = [noisy = std::move(noisy)] {
|
||||
REQUIRE(noisy.state == "Move constructed");
|
||||
};
|
||||
REQUIRE(noisy.state == "Moved away");
|
||||
func();
|
||||
}
|
||||
SECTION("Move construct function") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func = [&value] { value = 5; };
|
||||
Common::UniqueFunction<void> new_func = std::move(func);
|
||||
new_func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Move assign function") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func = [&value] { value = 5; };
|
||||
Common::UniqueFunction<void> new_func;
|
||||
new_func = std::move(func);
|
||||
new_func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Default construct then assign function") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func;
|
||||
func = [&value] { value = 5; };
|
||||
func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Pass arguments") {
|
||||
int result = 0;
|
||||
Common::UniqueFunction<void, int, int> func = [&result](int a, int b) { result = a + b; };
|
||||
func(5, 4);
|
||||
REQUIRE(result == 9);
|
||||
}
|
||||
SECTION("Pass arguments and return value") {
|
||||
Common::UniqueFunction<int, int, int> func = [](int a, int b) { return a + b; };
|
||||
REQUIRE(func(5, 4) == 9);
|
||||
}
|
||||
SECTION("Destructor") {
|
||||
int num_destroyed = 0;
|
||||
struct Foo {
|
||||
Foo(int* num_) : num{num_} {}
|
||||
Foo(Foo&& rhs) : num{std::exchange(rhs.num, nullptr)} {}
|
||||
Foo(const Foo&) = delete;
|
||||
|
||||
~Foo() {
|
||||
if (num) {
|
||||
++*num;
|
||||
}
|
||||
}
|
||||
|
||||
int* num = nullptr;
|
||||
};
|
||||
Foo object{&num_destroyed};
|
||||
{
|
||||
Common::UniqueFunction<void> func = [object = std::move(object)] {};
|
||||
REQUIRE(num_destroyed == 0);
|
||||
}
|
||||
REQUIRE(num_destroyed == 1);
|
||||
}
|
||||
}
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/unique_function.h"
|
||||
|
||||
namespace {
|
||||
struct Noisy {
|
||||
Noisy() : state{"Default constructed"} {}
|
||||
Noisy(Noisy&& rhs) noexcept : state{"Move constructed"} {
|
||||
rhs.state = "Moved away";
|
||||
}
|
||||
Noisy& operator=(Noisy&& rhs) noexcept {
|
||||
state = "Move assigned";
|
||||
rhs.state = "Moved away";
|
||||
return *this;
|
||||
}
|
||||
Noisy(const Noisy&) : state{"Copied constructed"} {}
|
||||
Noisy& operator=(const Noisy&) {
|
||||
state = "Copied assigned";
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string state;
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
TEST_CASE("UniqueFunction", "[common]") {
|
||||
SECTION("Capture reference") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func = [&value] { value = 5; };
|
||||
func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Capture pointer") {
|
||||
int value = 0;
|
||||
int* pointer = &value;
|
||||
Common::UniqueFunction<void> func = [pointer] { *pointer = 5; };
|
||||
func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Move object") {
|
||||
Noisy noisy;
|
||||
REQUIRE(noisy.state == "Default constructed");
|
||||
|
||||
Common::UniqueFunction<void> func = [noisy = std::move(noisy)] {
|
||||
REQUIRE(noisy.state == "Move constructed");
|
||||
};
|
||||
REQUIRE(noisy.state == "Moved away");
|
||||
func();
|
||||
}
|
||||
SECTION("Move construct function") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func = [&value] { value = 5; };
|
||||
Common::UniqueFunction<void> new_func = std::move(func);
|
||||
new_func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Move assign function") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func = [&value] { value = 5; };
|
||||
Common::UniqueFunction<void> new_func;
|
||||
new_func = std::move(func);
|
||||
new_func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Default construct then assign function") {
|
||||
int value = 0;
|
||||
Common::UniqueFunction<void> func;
|
||||
func = [&value] { value = 5; };
|
||||
func();
|
||||
REQUIRE(value == 5);
|
||||
}
|
||||
SECTION("Pass arguments") {
|
||||
int result = 0;
|
||||
Common::UniqueFunction<void, int, int> func = [&result](int a, int b) { result = a + b; };
|
||||
func(5, 4);
|
||||
REQUIRE(result == 9);
|
||||
}
|
||||
SECTION("Pass arguments and return value") {
|
||||
Common::UniqueFunction<int, int, int> func = [](int a, int b) { return a + b; };
|
||||
REQUIRE(func(5, 4) == 9);
|
||||
}
|
||||
SECTION("Destructor") {
|
||||
int num_destroyed = 0;
|
||||
struct Foo {
|
||||
Foo(int* num_) : num{num_} {}
|
||||
Foo(Foo&& rhs) : num{std::exchange(rhs.num, nullptr)} {}
|
||||
Foo(const Foo&) = delete;
|
||||
|
||||
~Foo() {
|
||||
if (num) {
|
||||
++*num;
|
||||
}
|
||||
}
|
||||
|
||||
int* num = nullptr;
|
||||
};
|
||||
Foo object{&num_destroyed};
|
||||
{
|
||||
Common::UniqueFunction<void> func = [object = std::move(object)] {};
|
||||
REQUIRE(num_destroyed == 0);
|
||||
}
|
||||
REQUIRE(num_destroyed == 1);
|
||||
}
|
||||
}
|
||||
|
@@ -1,145 +1,145 @@
|
||||
// SPDX-FileCopyrightText: 2016 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace {
|
||||
// Numbers are chosen randomly to make sure the correct one is given.
|
||||
constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
|
||||
constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
|
||||
std::array<s64, 5> delays{};
|
||||
|
||||
std::bitset<CB_IDS.size()> callbacks_ran_flags;
|
||||
u64 expected_callback = 0;
|
||||
|
||||
template <unsigned int IDX>
|
||||
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) {
|
||||
static_assert(IDX < CB_IDS.size(), "IDX out of range");
|
||||
callbacks_ran_flags.set(IDX);
|
||||
REQUIRE(CB_IDS[IDX] == user_data);
|
||||
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
|
||||
delays[IDX] = ns_late.count();
|
||||
++expected_callback;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct ScopeInit final {
|
||||
ScopeInit() {
|
||||
core_timing.SetMulticore(true);
|
||||
core_timing.Initialize([]() {});
|
||||
}
|
||||
|
||||
Core::Timing::CoreTiming core_timing;
|
||||
};
|
||||
|
||||
u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) {
|
||||
const u64 start = core_timing.GetGlobalTimeNs().count();
|
||||
volatile u64 placebo = 0;
|
||||
for (std::size_t i = 0; i < 1000; i++) {
|
||||
placebo = placebo + core_timing.GetGlobalTimeNs().count();
|
||||
}
|
||||
const u64 end = core_timing.GetGlobalTimeNs().count();
|
||||
return end - start;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
|
||||
ScopeInit guard;
|
||||
auto& core_timing = guard.core_timing;
|
||||
std::vector<std::shared_ptr<Core::Timing::EventType>> events{
|
||||
Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
|
||||
Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
|
||||
Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
|
||||
Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
|
||||
Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
|
||||
};
|
||||
|
||||
expected_callback = 0;
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
|
||||
const u64 one_micro = 1000U;
|
||||
for (std::size_t i = 0; i < events.size(); i++) {
|
||||
const u64 order = calls_order[i];
|
||||
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
|
||||
|
||||
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
|
||||
}
|
||||
/// test pause
|
||||
REQUIRE(callbacks_ran_flags.none());
|
||||
|
||||
core_timing.Pause(false); // No need to sync
|
||||
|
||||
while (core_timing.HasPendingEvents())
|
||||
;
|
||||
|
||||
REQUIRE(callbacks_ran_flags.all());
|
||||
|
||||
for (std::size_t i = 0; i < delays.size(); i++) {
|
||||
const double delay = static_cast<double>(delays[i]);
|
||||
const double micro = delay / 1000.0f;
|
||||
const double mili = micro / 1000.0f;
|
||||
printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
|
||||
ScopeInit guard;
|
||||
auto& core_timing = guard.core_timing;
|
||||
std::vector<std::shared_ptr<Core::Timing::EventType>> events{
|
||||
Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
|
||||
Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
|
||||
Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
|
||||
Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
|
||||
Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
|
||||
};
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
core_timing.SyncPause(false);
|
||||
|
||||
expected_callback = 0;
|
||||
|
||||
const u64 start = core_timing.GetGlobalTimeNs().count();
|
||||
const u64 one_micro = 1000U;
|
||||
|
||||
for (std::size_t i = 0; i < events.size(); i++) {
|
||||
const u64 order = calls_order[i];
|
||||
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
|
||||
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
|
||||
}
|
||||
|
||||
const u64 end = core_timing.GetGlobalTimeNs().count();
|
||||
const double scheduling_time = static_cast<double>(end - start);
|
||||
const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));
|
||||
|
||||
while (core_timing.HasPendingEvents())
|
||||
;
|
||||
|
||||
REQUIRE(callbacks_ran_flags.all());
|
||||
|
||||
for (std::size_t i = 0; i < delays.size(); i++) {
|
||||
const double delay = static_cast<double>(delays[i]);
|
||||
const double micro = delay / 1000.0f;
|
||||
const double mili = micro / 1000.0f;
|
||||
printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
|
||||
}
|
||||
|
||||
const double micro = scheduling_time / 1000.0f;
|
||||
const double mili = micro / 1000.0f;
|
||||
printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili);
|
||||
printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f,
|
||||
timer_time / 1000000.f);
|
||||
}
|
||||
// SPDX-FileCopyrightText: 2016 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace {
|
||||
// Numbers are chosen randomly to make sure the correct one is given.
|
||||
constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
|
||||
constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
|
||||
std::array<s64, 5> delays{};
|
||||
|
||||
std::bitset<CB_IDS.size()> callbacks_ran_flags;
|
||||
u64 expected_callback = 0;
|
||||
|
||||
template <unsigned int IDX>
|
||||
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) {
|
||||
static_assert(IDX < CB_IDS.size(), "IDX out of range");
|
||||
callbacks_ran_flags.set(IDX);
|
||||
REQUIRE(CB_IDS[IDX] == user_data);
|
||||
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
|
||||
delays[IDX] = ns_late.count();
|
||||
++expected_callback;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct ScopeInit final {
|
||||
ScopeInit() {
|
||||
core_timing.SetMulticore(true);
|
||||
core_timing.Initialize([]() {});
|
||||
}
|
||||
|
||||
Core::Timing::CoreTiming core_timing;
|
||||
};
|
||||
|
||||
u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) {
|
||||
const u64 start = core_timing.GetGlobalTimeNs().count();
|
||||
volatile u64 placebo = 0;
|
||||
for (std::size_t i = 0; i < 1000; i++) {
|
||||
placebo = placebo + core_timing.GetGlobalTimeNs().count();
|
||||
}
|
||||
const u64 end = core_timing.GetGlobalTimeNs().count();
|
||||
return end - start;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
|
||||
ScopeInit guard;
|
||||
auto& core_timing = guard.core_timing;
|
||||
std::vector<std::shared_ptr<Core::Timing::EventType>> events{
|
||||
Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
|
||||
Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
|
||||
Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
|
||||
Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
|
||||
Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
|
||||
};
|
||||
|
||||
expected_callback = 0;
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
|
||||
const u64 one_micro = 1000U;
|
||||
for (std::size_t i = 0; i < events.size(); i++) {
|
||||
const u64 order = calls_order[i];
|
||||
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
|
||||
|
||||
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
|
||||
}
|
||||
/// test pause
|
||||
REQUIRE(callbacks_ran_flags.none());
|
||||
|
||||
core_timing.Pause(false); // No need to sync
|
||||
|
||||
while (core_timing.HasPendingEvents())
|
||||
;
|
||||
|
||||
REQUIRE(callbacks_ran_flags.all());
|
||||
|
||||
for (std::size_t i = 0; i < delays.size(); i++) {
|
||||
const double delay = static_cast<double>(delays[i]);
|
||||
const double micro = delay / 1000.0f;
|
||||
const double mili = micro / 1000.0f;
|
||||
printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
|
||||
ScopeInit guard;
|
||||
auto& core_timing = guard.core_timing;
|
||||
std::vector<std::shared_ptr<Core::Timing::EventType>> events{
|
||||
Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
|
||||
Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
|
||||
Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
|
||||
Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
|
||||
Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
|
||||
};
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
core_timing.SyncPause(false);
|
||||
|
||||
expected_callback = 0;
|
||||
|
||||
const u64 start = core_timing.GetGlobalTimeNs().count();
|
||||
const u64 one_micro = 1000U;
|
||||
|
||||
for (std::size_t i = 0; i < events.size(); i++) {
|
||||
const u64 order = calls_order[i];
|
||||
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
|
||||
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
|
||||
}
|
||||
|
||||
const u64 end = core_timing.GetGlobalTimeNs().count();
|
||||
const double scheduling_time = static_cast<double>(end - start);
|
||||
const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));
|
||||
|
||||
while (core_timing.HasPendingEvents())
|
||||
;
|
||||
|
||||
REQUIRE(callbacks_ran_flags.all());
|
||||
|
||||
for (std::size_t i = 0; i < delays.size(); i++) {
|
||||
const double delay = static_cast<double>(delays[i]);
|
||||
const double micro = delay / 1000.0f;
|
||||
const double mili = micro / 1000.0f;
|
||||
printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
|
||||
}
|
||||
|
||||
const double micro = scheduling_time / 1000.0f;
|
||||
const double mili = micro / 1000.0f;
|
||||
printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili);
|
||||
printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f,
|
||||
timer_time / 1000000.f);
|
||||
}
|
||||
|
@@ -1,27 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
TEST_CASE("Network::Errors", "[core]") {
|
||||
Network::NetworkInstance network_instance; // initialize network
|
||||
|
||||
Network::Socket socks[2];
|
||||
for (Network::Socket& sock : socks) {
|
||||
REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM,
|
||||
Network::Protocol::TCP) == Network::Errno::SUCCESS);
|
||||
}
|
||||
|
||||
Network::SockAddrIn addr{
|
||||
Network::Domain::INET,
|
||||
{127, 0, 0, 1},
|
||||
1, // hopefully nobody running this test has something listening on port 1
|
||||
};
|
||||
REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED);
|
||||
|
||||
std::vector<u8> message{1, 2, 3, 4};
|
||||
REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN);
|
||||
}
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
TEST_CASE("Network::Errors", "[core]") {
|
||||
Network::NetworkInstance network_instance; // initialize network
|
||||
|
||||
Network::Socket socks[2];
|
||||
for (Network::Socket& sock : socks) {
|
||||
REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM,
|
||||
Network::Protocol::TCP) == Network::Errno::SUCCESS);
|
||||
}
|
||||
|
||||
Network::SockAddrIn addr{
|
||||
Network::Domain::INET,
|
||||
{127, 0, 0, 1},
|
||||
1, // hopefully nobody running this test has something listening on port 1
|
||||
};
|
||||
REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED);
|
||||
|
||||
std::vector<u8> message{1, 2, 3, 4};
|
||||
REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN);
|
||||
}
|
||||
|
@@ -1,135 +1,135 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/crc.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#include "input_common/helpers/udp_protocol.h"
|
||||
|
||||
class FakeCemuhookServer {
|
||||
public:
|
||||
FakeCemuhookServer()
|
||||
: socket(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) {}
|
||||
|
||||
~FakeCemuhookServer() {
|
||||
is_running = false;
|
||||
boost::system::error_code error_code;
|
||||
socket.shutdown(boost::asio::socket_base::shutdown_both, error_code);
|
||||
socket.close();
|
||||
if (handler.joinable()) {
|
||||
handler.join();
|
||||
}
|
||||
}
|
||||
|
||||
u16 GetPort() {
|
||||
return socket.local_endpoint().port();
|
||||
}
|
||||
|
||||
std::string GetHost() {
|
||||
return socket.local_endpoint().address().to_string();
|
||||
}
|
||||
|
||||
void Run(const std::vector<InputCommon::CemuhookUDP::Response::TouchPad> touch_movement_path) {
|
||||
constexpr size_t HeaderSize = sizeof(InputCommon::CemuhookUDP::Header);
|
||||
constexpr size_t PadDataSize =
|
||||
sizeof(InputCommon::CemuhookUDP::Message<InputCommon::CemuhookUDP::Response::PadData>);
|
||||
|
||||
REQUIRE(touch_movement_path.size() > 0);
|
||||
is_running = true;
|
||||
handler = std::thread([touch_movement_path, this]() {
|
||||
auto current_touch_position = touch_movement_path.begin();
|
||||
while (is_running) {
|
||||
boost::asio::ip::udp::endpoint sender_endpoint;
|
||||
boost::system::error_code error_code;
|
||||
auto received_size = socket.receive_from(boost::asio::buffer(receive_buffer),
|
||||
sender_endpoint, 0, error_code);
|
||||
|
||||
if (received_size < HeaderSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputCommon::CemuhookUDP::Header header{};
|
||||
std::memcpy(&header, receive_buffer.data(), HeaderSize);
|
||||
switch (header.type) {
|
||||
case InputCommon::CemuhookUDP::Type::PadData: {
|
||||
InputCommon::CemuhookUDP::Response::PadData pad_data{};
|
||||
pad_data.touch[0] = *current_touch_position;
|
||||
const auto pad_message = InputCommon::CemuhookUDP::CreateMessage(
|
||||
InputCommon::CemuhookUDP::SERVER_MAGIC, pad_data, 0);
|
||||
std::memcpy(send_buffer.data(), &pad_message, PadDataSize);
|
||||
socket.send_to(boost::asio::buffer(send_buffer, PadDataSize), sender_endpoint,
|
||||
0, error_code);
|
||||
|
||||
bool can_advance =
|
||||
std::next(current_touch_position) != touch_movement_path.end();
|
||||
if (can_advance) {
|
||||
std::advance(current_touch_position, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputCommon::CemuhookUDP::Type::PortInfo:
|
||||
case InputCommon::CemuhookUDP::Type::Version:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::ip::udp::socket socket;
|
||||
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> send_buffer;
|
||||
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> receive_buffer;
|
||||
bool is_running = false;
|
||||
std::thread handler;
|
||||
};
|
||||
|
||||
TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") {
|
||||
Common::Event complete_event;
|
||||
FakeCemuhookServer server;
|
||||
server.Run({{
|
||||
.is_active = 1,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
{
|
||||
.is_active = 1,
|
||||
.x = 200,
|
||||
.y = 200,
|
||||
}});
|
||||
|
||||
InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status{};
|
||||
u16 min_x{};
|
||||
u16 min_y{};
|
||||
u16 max_x{};
|
||||
u16 max_y{};
|
||||
InputCommon::CemuhookUDP::CalibrationConfigurationJob job(
|
||||
server.GetHost(), server.GetPort(),
|
||||
[&status,
|
||||
&complete_event](InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status_) {
|
||||
status = status_;
|
||||
if (status ==
|
||||
InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed) {
|
||||
complete_event.Set();
|
||||
}
|
||||
},
|
||||
[&](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
|
||||
min_x = min_x_;
|
||||
min_y = min_y_;
|
||||
max_x = max_x_;
|
||||
max_y = max_y_;
|
||||
});
|
||||
|
||||
complete_event.WaitUntil(std::chrono::system_clock::now() + std::chrono::seconds(10));
|
||||
REQUIRE(status == InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed);
|
||||
REQUIRE(min_x == 0);
|
||||
REQUIRE(min_y == 0);
|
||||
REQUIRE(max_x == 200);
|
||||
REQUIRE(max_y == 200);
|
||||
}
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/crc.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#include "input_common/helpers/udp_protocol.h"
|
||||
|
||||
class FakeCemuhookServer {
|
||||
public:
|
||||
FakeCemuhookServer()
|
||||
: socket(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) {}
|
||||
|
||||
~FakeCemuhookServer() {
|
||||
is_running = false;
|
||||
boost::system::error_code error_code;
|
||||
socket.shutdown(boost::asio::socket_base::shutdown_both, error_code);
|
||||
socket.close();
|
||||
if (handler.joinable()) {
|
||||
handler.join();
|
||||
}
|
||||
}
|
||||
|
||||
u16 GetPort() {
|
||||
return socket.local_endpoint().port();
|
||||
}
|
||||
|
||||
std::string GetHost() {
|
||||
return socket.local_endpoint().address().to_string();
|
||||
}
|
||||
|
||||
void Run(const std::vector<InputCommon::CemuhookUDP::Response::TouchPad> touch_movement_path) {
|
||||
constexpr size_t HeaderSize = sizeof(InputCommon::CemuhookUDP::Header);
|
||||
constexpr size_t PadDataSize =
|
||||
sizeof(InputCommon::CemuhookUDP::Message<InputCommon::CemuhookUDP::Response::PadData>);
|
||||
|
||||
REQUIRE(touch_movement_path.size() > 0);
|
||||
is_running = true;
|
||||
handler = std::thread([touch_movement_path, this]() {
|
||||
auto current_touch_position = touch_movement_path.begin();
|
||||
while (is_running) {
|
||||
boost::asio::ip::udp::endpoint sender_endpoint;
|
||||
boost::system::error_code error_code;
|
||||
auto received_size = socket.receive_from(boost::asio::buffer(receive_buffer),
|
||||
sender_endpoint, 0, error_code);
|
||||
|
||||
if (received_size < HeaderSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputCommon::CemuhookUDP::Header header{};
|
||||
std::memcpy(&header, receive_buffer.data(), HeaderSize);
|
||||
switch (header.type) {
|
||||
case InputCommon::CemuhookUDP::Type::PadData: {
|
||||
InputCommon::CemuhookUDP::Response::PadData pad_data{};
|
||||
pad_data.touch[0] = *current_touch_position;
|
||||
const auto pad_message = InputCommon::CemuhookUDP::CreateMessage(
|
||||
InputCommon::CemuhookUDP::SERVER_MAGIC, pad_data, 0);
|
||||
std::memcpy(send_buffer.data(), &pad_message, PadDataSize);
|
||||
socket.send_to(boost::asio::buffer(send_buffer, PadDataSize), sender_endpoint,
|
||||
0, error_code);
|
||||
|
||||
bool can_advance =
|
||||
std::next(current_touch_position) != touch_movement_path.end();
|
||||
if (can_advance) {
|
||||
std::advance(current_touch_position, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputCommon::CemuhookUDP::Type::PortInfo:
|
||||
case InputCommon::CemuhookUDP::Type::Version:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::ip::udp::socket socket;
|
||||
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> send_buffer;
|
||||
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> receive_buffer;
|
||||
bool is_running = false;
|
||||
std::thread handler;
|
||||
};
|
||||
|
||||
TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") {
|
||||
Common::Event complete_event;
|
||||
FakeCemuhookServer server;
|
||||
server.Run({{
|
||||
.is_active = 1,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
{
|
||||
.is_active = 1,
|
||||
.x = 200,
|
||||
.y = 200,
|
||||
}});
|
||||
|
||||
InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status{};
|
||||
u16 min_x{};
|
||||
u16 min_y{};
|
||||
u16 max_x{};
|
||||
u16 max_y{};
|
||||
InputCommon::CemuhookUDP::CalibrationConfigurationJob job(
|
||||
server.GetHost(), server.GetPort(),
|
||||
[&status,
|
||||
&complete_event](InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status_) {
|
||||
status = status_;
|
||||
if (status ==
|
||||
InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed) {
|
||||
complete_event.Set();
|
||||
}
|
||||
},
|
||||
[&](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
|
||||
min_x = min_x_;
|
||||
min_y = min_y_;
|
||||
max_x = max_x_;
|
||||
max_y = max_y_;
|
||||
});
|
||||
|
||||
complete_event.WaitUntil(std::chrono::system_clock::now() + std::chrono::seconds(10));
|
||||
REQUIRE(status == InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed);
|
||||
REQUIRE(min_x == 0);
|
||||
REQUIRE(min_y == 0);
|
||||
REQUIRE(max_x == 200);
|
||||
REQUIRE(max_y == 200);
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
// Catch provides the main function since we've given it the
|
||||
// CATCH_CONFIG_MAIN preprocessor directive.
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
// Catch provides the main function since we've given it the
|
||||
// CATCH_CONFIG_MAIN preprocessor directive.
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user