early-access version 1255
This commit is contained in:
18
src/tests/CMakeLists.txt
Executable file
18
src/tests/CMakeLists.txt
Executable file
@@ -0,0 +1,18 @@
|
||||
add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/bit_utils.cpp
|
||||
common/fibers.cpp
|
||||
common/param_package.cpp
|
||||
common/ring_buffer.cpp
|
||||
core/arm/arm_test_common.cpp
|
||||
core/arm/arm_test_common.h
|
||||
core/core_timing.cpp
|
||||
tests.cpp
|
||||
)
|
||||
|
||||
create_target_directory_groups(tests)
|
||||
|
||||
target_link_libraries(tests PRIVATE common core)
|
||||
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads)
|
||||
|
||||
add_test(NAME tests COMMAND tests)
|
90
src/tests/common/bit_field.cpp
Executable file
90
src/tests/common/bit_field.cpp
Executable file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#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,
|
||||
}});
|
||||
}
|
23
src/tests/common/bit_utils.cpp
Executable file
23
src/tests/common/bit_utils.cpp
Executable file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <math.h>
|
||||
#include "common/bit_util.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("BitUtils::CountTrailingZeroes", "[common]") {
|
||||
REQUIRE(Common::CountTrailingZeroes32(0) == 32);
|
||||
REQUIRE(Common::CountTrailingZeroes64(0) == 64);
|
||||
REQUIRE(Common::CountTrailingZeroes32(9) == 0);
|
||||
REQUIRE(Common::CountTrailingZeroes32(8) == 3);
|
||||
REQUIRE(Common::CountTrailingZeroes32(0x801000) == 12);
|
||||
REQUIRE(Common::CountTrailingZeroes64(9) == 0);
|
||||
REQUIRE(Common::CountTrailingZeroes64(8) == 3);
|
||||
REQUIRE(Common::CountTrailingZeroes64(0x801000) == 12);
|
||||
REQUIRE(Common::CountTrailingZeroes64(0x801000000000UL) == 36);
|
||||
}
|
||||
|
||||
} // namespace Common
|
367
src/tests/common/fibers.cpp
Executable file
367
src/tests/common/fibers.cpp
Executable file
@@ -0,0 +1,367 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#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();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static void WorkControl1(void* control) {
|
||||
auto* test_control = static_cast<TestControl1*>(control);
|
||||
test_control->DoWork();
|
||||
}
|
||||
|
||||
void TestControl1::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 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>(std::function<void(void*)>{WorkControl1}, this);
|
||||
items[id] = rand() % 256;
|
||||
Fiber::YieldTo(thread_fibers[id], work_fibers[id]);
|
||||
thread_fibers[id]->Exit();
|
||||
}
|
||||
|
||||
static void ThreadStart1(u32 id, TestControl1& test_control) {
|
||||
test_control.ExecuteThread(id);
|
||||
}
|
||||
|
||||
/** 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(ThreadStart1, i, std::ref(test_control));
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
static void WorkControl2_1(void* control) {
|
||||
auto* test_control = static_cast<TestControl2*>(control);
|
||||
test_control->DoWork1();
|
||||
}
|
||||
|
||||
static void WorkControl2_2(void* control) {
|
||||
auto* test_control = static_cast<TestControl2*>(control);
|
||||
test_control->DoWork2();
|
||||
}
|
||||
|
||||
static void WorkControl2_3(void* control) {
|
||||
auto* test_control = static_cast<TestControl2*>(control);
|
||||
test_control->DoWork3();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
static void ThreadStart2_1(u32 id, TestControl2& test_control) {
|
||||
test_control.ExecuteThread(id);
|
||||
test_control.CallFiber1();
|
||||
test_control.Exit();
|
||||
}
|
||||
|
||||
static void ThreadStart2_2(u32 id, TestControl2& test_control) {
|
||||
test_control.ExecuteThread(id);
|
||||
test_control.CallFiber2();
|
||||
test_control.Exit();
|
||||
}
|
||||
|
||||
/** This test checks for fiber thread exchange configuration and validates that fibers are
|
||||
* that a fiber has been succesfully transfered 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>(std::function<void(void*)>{WorkControl2_1}, &test_control);
|
||||
test_control.fiber2 =
|
||||
std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_2}, &test_control);
|
||||
test_control.fiber3 =
|
||||
std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_3}, &test_control);
|
||||
std::thread thread1(ThreadStart2_1, 0, std::ref(test_control));
|
||||
std::thread thread2(ThreadStart2_2, 1, std::ref(test_control));
|
||||
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;
|
||||
};
|
||||
|
||||
static void WorkControl3_1(void* control) {
|
||||
auto* test_control = static_cast<TestControl3*>(control);
|
||||
test_control->DoWork1();
|
||||
}
|
||||
|
||||
static void WorkControl3_2(void* control) {
|
||||
auto* test_control = static_cast<TestControl3*>(control);
|
||||
test_control->DoWork2();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
static void ThreadStart3(u32 id, TestControl3& test_control) {
|
||||
test_control.ExecuteThread(id);
|
||||
test_control.CallFiber1();
|
||||
test_control.Exit();
|
||||
}
|
||||
|
||||
/** This test checks for one two threads racing for starting the same fiber.
|
||||
* It checks execution occured 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>(std::function<void(void*)>{WorkControl3_1}, &test_control);
|
||||
test_control.fiber2 =
|
||||
std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_2}, &test_control);
|
||||
std::thread thread1(ThreadStart3, 0, std::ref(test_control));
|
||||
std::thread thread2(ThreadStart3, 1, std::ref(test_control));
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
REQUIRE(test_control.value1 == 1);
|
||||
REQUIRE(test_control.value2 == 1);
|
||||
REQUIRE(test_control.value3 == 1);
|
||||
}
|
||||
|
||||
class TestControl4;
|
||||
|
||||
static void WorkControl4(void* control);
|
||||
|
||||
class TestControl4 {
|
||||
public:
|
||||
TestControl4() {
|
||||
fiber1 = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl4}, this);
|
||||
goal_reached = false;
|
||||
rewinded = false;
|
||||
}
|
||||
|
||||
void Execute() {
|
||||
thread_fiber = Fiber::ThreadToFiber();
|
||||
Fiber::YieldTo(thread_fiber, fiber1);
|
||||
thread_fiber->Exit();
|
||||
}
|
||||
|
||||
void DoWork() {
|
||||
fiber1->SetRewindPoint(std::function<void(void*)>{WorkControl4}, this);
|
||||
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;
|
||||
};
|
||||
|
||||
static void WorkControl4(void* control) {
|
||||
auto* test_control = static_cast<TestControl4*>(control);
|
||||
test_control->DoWork();
|
||||
}
|
||||
|
||||
TEST_CASE("Fibers::Rewind", "[common]") {
|
||||
TestControl4 test_control{};
|
||||
test_control.Execute();
|
||||
REQUIRE(test_control.goal_reached);
|
||||
REQUIRE(test_control.rewinded);
|
||||
}
|
||||
|
||||
} // namespace Common
|
27
src/tests/common/param_package.cpp
Executable file
27
src/tests/common/param_package.cpp
Executable file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <math.h>
|
||||
#include "common/param_package.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("ParamPackage", "[common]") {
|
||||
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
|
130
src/tests/common/ring_buffer.cpp
Executable file
130
src/tests/common/ring_buffer.cpp
Executable file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#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, 1> 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 == 1);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4);
|
||||
|
||||
// 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 == 0);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4);
|
||||
|
||||
// Popping multiple values from a ring buffer with values should succeed.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop(2);
|
||||
REQUIRE(popped.size() == 2);
|
||||
REQUIRE(popped[0] == 0);
|
||||
REQUIRE(popped[1] == 1);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 2);
|
||||
|
||||
// Popping a single value from a ring buffer with values should succeed.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop(1);
|
||||
REQUIRE(popped.size() == 1);
|
||||
REQUIRE(popped[0] == 2);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 1);
|
||||
|
||||
// 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 == 3);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 4);
|
||||
|
||||
// Doing an unlimited pop should pop all values.
|
||||
{
|
||||
const std::vector<char> popped = buf.Pop();
|
||||
REQUIRE(popped.size() == 4);
|
||||
REQUIRE(popped[0] == 3);
|
||||
REQUIRE(popped[1] == 88);
|
||||
REQUIRE(popped[2] == 89);
|
||||
REQUIRE(popped[3] == 90);
|
||||
}
|
||||
|
||||
REQUIRE(buf.Size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("RingBuffer: Threaded Test", "[common]") {
|
||||
RingBuffer<char, 4, 2> 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], 1); c > 0) {
|
||||
REQUIRE(c == 1);
|
||||
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(1); v.size() > 0) {
|
||||
REQUIRE(v.size() == 2);
|
||||
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() == 0);
|
||||
printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty);
|
||||
}
|
||||
|
||||
} // namespace Common
|
145
src/tests/core/arm/arm_test_common.cpp
Executable file
145
src/tests/core/arm/arm_test_common.cpp
Executable file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/page_table.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/memory/page_table.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/memory.h"
|
||||
#include "tests/core/arm/arm_test_common.h"
|
||||
|
||||
namespace ArmTests {
|
||||
|
||||
TestEnvironment::TestEnvironment(bool mutable_memory_)
|
||||
: mutable_memory(mutable_memory_),
|
||||
test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
auto process = Kernel::Process::Create(system, "", Kernel::Process::ProcessType::Userland);
|
||||
page_table = &process->PageTable().PageTableImpl();
|
||||
|
||||
system.Memory().MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
|
||||
system.Memory().MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
|
||||
|
||||
kernel.MakeCurrentProcess(process.get());
|
||||
}
|
||||
|
||||
TestEnvironment::~TestEnvironment() {
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.Memory().UnmapRegion(*page_table, 0x80000000, 0x80000000);
|
||||
system.Memory().UnmapRegion(*page_table, 0x00000000, 0x80000000);
|
||||
}
|
||||
|
||||
void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) {
|
||||
SetMemory32(vaddr + 0, static_cast<u32>(value));
|
||||
SetMemory32(vaddr + 4, static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
void TestEnvironment::SetMemory32(VAddr vaddr, u32 value) {
|
||||
SetMemory16(vaddr + 0, static_cast<u16>(value));
|
||||
SetMemory16(vaddr + 2, static_cast<u16>(value >> 16));
|
||||
}
|
||||
|
||||
void TestEnvironment::SetMemory16(VAddr vaddr, u16 value) {
|
||||
SetMemory8(vaddr + 0, static_cast<u8>(value));
|
||||
SetMemory8(vaddr + 1, static_cast<u8>(value >> 8));
|
||||
}
|
||||
|
||||
void TestEnvironment::SetMemory8(VAddr vaddr, u8 value) {
|
||||
test_memory->data[vaddr] = value;
|
||||
}
|
||||
|
||||
std::vector<WriteRecord> TestEnvironment::GetWriteRecords() const {
|
||||
return write_records;
|
||||
}
|
||||
|
||||
void TestEnvironment::ClearWriteRecords() {
|
||||
write_records.clear();
|
||||
}
|
||||
|
||||
TestEnvironment::TestMemory::~TestMemory() {}
|
||||
|
||||
std::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
|
||||
const auto iter = data.find(addr);
|
||||
|
||||
if (iter == data.end()) {
|
||||
// Some arbitrary data
|
||||
return static_cast<u8>(addr);
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
|
||||
return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8;
|
||||
}
|
||||
|
||||
std::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
|
||||
return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16;
|
||||
}
|
||||
|
||||
std::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
|
||||
return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32;
|
||||
}
|
||||
|
||||
bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) {
|
||||
VAddr addr = src_addr;
|
||||
u8* data = static_cast<u8*>(dest_buffer);
|
||||
|
||||
for (std::size_t i = 0; i < size; i++, addr++, data++) {
|
||||
*data = *Read8(addr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) {
|
||||
env->write_records.emplace_back(8, addr, data);
|
||||
if (env->mutable_memory)
|
||||
env->SetMemory8(addr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) {
|
||||
env->write_records.emplace_back(16, addr, data);
|
||||
if (env->mutable_memory)
|
||||
env->SetMemory16(addr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) {
|
||||
env->write_records.emplace_back(32, addr, data);
|
||||
if (env->mutable_memory)
|
||||
env->SetMemory32(addr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) {
|
||||
env->write_records.emplace_back(64, addr, data);
|
||||
if (env->mutable_memory)
|
||||
env->SetMemory64(addr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer,
|
||||
std::size_t size) {
|
||||
VAddr addr = dest_addr;
|
||||
const u8* data = static_cast<const u8*>(src_buffer);
|
||||
|
||||
for (std::size_t i = 0; i < size; i++, addr++, data++) {
|
||||
env->write_records.emplace_back(8, addr, *data);
|
||||
if (env->mutable_memory)
|
||||
env->SetMemory8(addr, *data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ArmTests
|
93
src/tests/core/arm/arm_test_common.h
Executable file
93
src/tests/core/arm/arm_test_common.h
Executable file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace ArmTests {
|
||||
|
||||
struct WriteRecord {
|
||||
WriteRecord(std::size_t size, VAddr addr, u64 data) : size(size), addr(addr), data(data) {}
|
||||
std::size_t size;
|
||||
VAddr addr;
|
||||
u64 data;
|
||||
bool operator==(const WriteRecord& o) const {
|
||||
return std::tie(size, addr, data) == std::tie(o.size, o.addr, o.data);
|
||||
}
|
||||
};
|
||||
|
||||
class TestEnvironment final {
|
||||
public:
|
||||
/*
|
||||
* Inititalise test environment
|
||||
* @param mutable_memory If false, writes to memory can never be read back.
|
||||
* (Memory is immutable.)
|
||||
*/
|
||||
explicit TestEnvironment(bool mutable_memory = false);
|
||||
|
||||
/// Shutdown test environment
|
||||
~TestEnvironment();
|
||||
|
||||
/// Sets value at memory location vaddr.
|
||||
void SetMemory8(VAddr vaddr, u8 value);
|
||||
void SetMemory16(VAddr vaddr, u16 value);
|
||||
void SetMemory32(VAddr vaddr, u32 value);
|
||||
void SetMemory64(VAddr vaddr, u64 value);
|
||||
|
||||
/**
|
||||
* Whenever Memory::Write{8,16,32,64} is called within the test environment,
|
||||
* a new write-record is made.
|
||||
* @returns A vector of write records made since they were last cleared.
|
||||
*/
|
||||
std::vector<WriteRecord> GetWriteRecords() const;
|
||||
|
||||
/// Empties the internal write-record store.
|
||||
void ClearWriteRecords();
|
||||
|
||||
private:
|
||||
friend struct TestMemory;
|
||||
struct TestMemory final : Common::MemoryHook {
|
||||
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
|
||||
TestEnvironment* env;
|
||||
|
||||
~TestMemory() override;
|
||||
|
||||
std::optional<bool> IsValidAddress(VAddr addr) override;
|
||||
|
||||
std::optional<u8> Read8(VAddr addr) override;
|
||||
std::optional<u16> Read16(VAddr addr) override;
|
||||
std::optional<u32> Read32(VAddr addr) override;
|
||||
std::optional<u64> Read64(VAddr addr) override;
|
||||
|
||||
bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override;
|
||||
|
||||
bool Write8(VAddr addr, u8 data) override;
|
||||
bool Write16(VAddr addr, u16 data) override;
|
||||
bool Write32(VAddr addr, u32 data) override;
|
||||
bool Write64(VAddr addr, u64 data) override;
|
||||
|
||||
bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) override;
|
||||
|
||||
std::unordered_map<VAddr, u8> data;
|
||||
};
|
||||
|
||||
bool mutable_memory;
|
||||
std::shared_ptr<TestMemory> test_memory;
|
||||
std::vector<WriteRecord> write_records;
|
||||
Common::PageTable* page_table = nullptr;
|
||||
Kernel::KernelCore kernel;
|
||||
};
|
||||
|
||||
} // namespace ArmTests
|
147
src/tests/core/core_timing.cpp
Executable file
147
src/tests/core/core_timing.cpp
Executable file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2016 Dolphin Emulator Project / 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#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>
|
||||
void HostCallbackTemplate(std::uintptr_t user_data, 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;
|
||||
}
|
||||
|
||||
struct ScopeInit final {
|
||||
ScopeInit() {
|
||||
core_timing.SetMulticore(true);
|
||||
core_timing.Initialize([]() {});
|
||||
}
|
||||
~ScopeInit() {
|
||||
core_timing.Shutdown();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
9
src/tests/tests.cpp
Executable file
9
src/tests/tests.cpp
Executable file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
// Catch provides the main function since we've given it the
|
||||
// CATCH_CONFIG_MAIN preprocessor directive.
|
Reference in New Issue
Block a user