another try

This commit is contained in:
mgthepro
2022-11-05 13:58:44 +01:00
parent 4a9f2bbf2a
commit 9f63fbe700
2002 changed files with 671171 additions and 671092 deletions

View File

@@ -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,
}});
}

View File

@@ -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});
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}