First crack at a test suite
This commit is contained in:
parent
090627b3c1
commit
549cd4a5a1
|
@ -111,3 +111,12 @@ add_subdirectory(src)
|
|||
if (BUILD_QT_SDL)
|
||||
add_subdirectory(src/frontend/qt_sdl)
|
||||
endif()
|
||||
|
||||
option(MELONDS_BUILD_TESTS "Build the melonDS test suite." OFF)
|
||||
|
||||
if (MELONDS_BUILD_TESTS)
|
||||
include(CTest)
|
||||
message(STATUS "Enabling melonDS test suite.")
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
|
@ -0,0 +1,65 @@
|
|||
cmake_policy(SET CMP0110 NEW)
|
||||
|
||||
if (NOT NDS_ROM)
|
||||
message(WARNING "NDS_ROM must be set to the path of an NDS ROM; tests that require one won't run.")
|
||||
set(NDS_ROM "NDS_ROM-NOTFOUND" CACHE FILEPATH "Path to an NDS ROM" FORCE)
|
||||
else()
|
||||
message(DEBUG "NDS_ROM: ${NDS_ROM}")
|
||||
endif()
|
||||
|
||||
include(CMakePrintHelpers)
|
||||
|
||||
function(add_melonds_test)
|
||||
set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND DISABLED)
|
||||
set(oneValueArgs TARGET NAME ROM)
|
||||
set(multiValueArgs ARGS LABEL)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 MELONDS_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}")
|
||||
|
||||
cmake_print_variables(MELONDS_TEST_TARGET MELONDS_TEST_NAME MELONDS_TEST_ROM MELONDS_TEST_ARGS MELONDS_TEST_LABEL MELONDS_TEST_WILL_FAIL MELONDS_TEST_DISABLED)
|
||||
add_test(
|
||||
NAME "${MELONDS_TEST_NAME}"
|
||||
COMMAND "${MELONDS_TEST_TARGET}"
|
||||
${MELONDS_TEST_ARGS}
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
|
||||
if (MELONDS_TEST_ROM)
|
||||
list(APPEND REQUIRED_FILES "${MELONDS_TEST_ROM}")
|
||||
endif()
|
||||
|
||||
macro(expose_system_file SYSFILE)
|
||||
if (MELONDS_TEST_${SYSFILE})
|
||||
list(APPEND REQUIRED_FILES "${${SYSFILE}}")
|
||||
list(APPEND ENVIRONMENT "${SYSFILE}=${${SYSFILE}}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
expose_system_file(ARM7_BIOS)
|
||||
expose_system_file(ARM9_BIOS)
|
||||
expose_system_file(ARM7_DSI_BIOS)
|
||||
expose_system_file(ARM9_DSI_BIOS)
|
||||
expose_system_file(NDS_FIRMWARE)
|
||||
expose_system_file(DSI_FIRMWARE)
|
||||
expose_system_file(DSI_NAND)
|
||||
|
||||
set_tests_properties("${MELONDS_TEST_NAME}" PROPERTIES LABELS "${MELONDS_TEST_LABEL}") # This is already a list
|
||||
set_tests_properties("${MELONDS_TEST_NAME}" PROPERTIES ENVIRONMENT "${ENVIRONMENT}")
|
||||
set_tests_properties("${MELONDS_TEST_NAME}" PROPERTIES REQUIRED_FILES "${REQUIRED_FILES}")
|
||||
|
||||
if (MELONDS_TEST_WILL_FAIL)
|
||||
set_tests_properties("${MELONDS_TEST_NAME}" PROPERTIES WILL_FAIL TRUE)
|
||||
endif()
|
||||
|
||||
if (MELONDS_TEST_DISABLED)
|
||||
set_tests_properties("${MELONDS_TEST_NAME}" PROPERTIES DISABLED TRUE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_test_executable TEST_PROGRAM)
|
||||
add_executable("${TEST_PROGRAM}" "${TEST_PROGRAM}.cpp")
|
||||
target_link_libraries("${TEST_PROGRAM}" PRIVATE core melonDS-tests-common)
|
||||
target_include_directories("${TEST_PROGRAM}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src")
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(cases)
|
|
@ -0,0 +1 @@
|
|||
add_subdirectory(basic)
|
|
@ -0,0 +1,22 @@
|
|||
# Test executables must be defined separately from the actual tests,
|
||||
# as the same test executable might be used for multiple tests
|
||||
# (with different parameters each time).
|
||||
|
||||
add_test_executable(NDSCreated)
|
||||
add_test_executable(MultipleNDSCreated)
|
||||
add_test_executable(MultipleNDSCreatedInDifferentOrder)
|
||||
|
||||
add_melonds_test(
|
||||
NAME "NDS is created and destroyed"
|
||||
TARGET NDSCreated
|
||||
)
|
||||
|
||||
add_melonds_test(
|
||||
NAME "Multiple NDS systems are created and destroyed"
|
||||
TARGET MultipleNDSCreated
|
||||
)
|
||||
|
||||
add_melonds_test(
|
||||
NAME "Multiple NDS systems are created and destroyed in different orders"
|
||||
TARGET MultipleNDSCreatedInDifferentOrder
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include "NDS.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// volatile so it's not optimized out
|
||||
volatile auto nds1 = std::make_unique<melonDS::NDS>();
|
||||
volatile auto nds2 = std::make_unique<melonDS::NDS>();
|
||||
volatile auto nds3 = std::make_unique<melonDS::NDS>();
|
||||
volatile auto nds4 = std::make_unique<melonDS::NDS>();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include "NDS.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// volatile so it's not optimized out
|
||||
auto nds1 = std::make_unique<volatile melonDS::NDS>();
|
||||
auto nds2 = std::make_unique<volatile melonDS::NDS>();
|
||||
auto nds3 = std::make_unique<volatile melonDS::NDS>();
|
||||
auto nds4 = std::make_unique<volatile melonDS::NDS>();
|
||||
|
||||
nds3 = nullptr;
|
||||
nds1 = nullptr;
|
||||
nds4 = nullptr;
|
||||
nds2 = nullptr;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include "NDS.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// volatile so it's not optimized out
|
||||
volatile auto nds = std::make_unique<melonDS::NDS>();
|
||||
|
||||
// NDS is probably waaaay too big for the stack
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
include(FixInterfaceIncludes)
|
||||
|
||||
add_library(melonDS-tests-common STATIC
|
||||
Platform.cpp
|
||||
TestCommon.cpp
|
||||
TestCommon.h
|
||||
)
|
||||
|
||||
# The test program is built with C++20 so we can use std::counting_semaphore
|
||||
set_target_properties(melonDS-tests-common PROPERTIES CXX_STANDARD 20)
|
||||
|
||||
target_link_libraries(melonDS-tests-common PRIVATE core)
|
||||
target_include_directories(melonDS-tests-common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../src")
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include "Platform.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
|
||||
namespace melonDS::Platform
|
||||
{
|
||||
void SignalStop(StopReason reason) {}
|
||||
int InstanceID() { return 0; }
|
||||
|
||||
std::string InstanceFileSuffix() { return ""; }
|
||||
|
||||
constexpr char AccessMode(FileMode mode, bool file_exists)
|
||||
{
|
||||
if (!(mode & FileMode::Write))
|
||||
// If we're only opening the file for reading...
|
||||
return 'r';
|
||||
|
||||
if (mode & (FileMode::NoCreate))
|
||||
// If we're not allowed to create a new file...
|
||||
return 'r'; // Open in "r+" mode (IsExtended will add the "+")
|
||||
|
||||
if ((mode & FileMode::Preserve) && file_exists)
|
||||
// If we're not allowed to overwrite a file that already exists...
|
||||
return 'r'; // Open in "r+" mode (IsExtended will add the "+")
|
||||
|
||||
return 'w';
|
||||
}
|
||||
|
||||
constexpr bool IsExtended(FileMode mode)
|
||||
{
|
||||
// fopen's "+" flag always opens the file for read/write
|
||||
return (mode & FileMode::ReadWrite) == FileMode::ReadWrite;
|
||||
}
|
||||
|
||||
static std::string GetModeString(FileMode mode, bool file_exists)
|
||||
{
|
||||
std::string modeString;
|
||||
|
||||
modeString += AccessMode(mode, file_exists);
|
||||
|
||||
if (IsExtended(mode))
|
||||
modeString += '+';
|
||||
|
||||
if (!(mode & FileMode::Text))
|
||||
modeString += 'b';
|
||||
|
||||
return modeString;
|
||||
}
|
||||
|
||||
FileHandle* OpenFile(const std::string& path, FileMode mode)
|
||||
{
|
||||
if ((mode & FileMode::ReadWrite) == FileMode::None)
|
||||
{ // If we aren't reading or writing, then we can't open the file
|
||||
Log(LogLevel::Error, "Attempted to open \"%s\" in neither read nor write mode (FileMode 0x%x)\n", path.c_str(), mode);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool file_exists = std::filesystem::exists(path);
|
||||
std::string modeString = GetModeString(mode, file_exists);
|
||||
|
||||
FILE* file = fopen(path.c_str(), modeString.c_str());
|
||||
if (file)
|
||||
{
|
||||
Log(LogLevel::Debug, "Opened \"%s\" with FileMode 0x%x (effective mode \"%s\")\n", path.c_str(), mode, modeString.c_str());
|
||||
return reinterpret_cast<FileHandle *>(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Warn, "Failed to open \"%s\" with FileMode 0x%x (effective mode \"%s\")\n", path.c_str(), mode, modeString.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FileHandle* OpenLocalFile(const std::string& path, FileMode mode)
|
||||
{
|
||||
return OpenFile(path, mode);
|
||||
}
|
||||
|
||||
bool CloseFile(FileHandle* file)
|
||||
{
|
||||
return fclose(reinterpret_cast<FILE *>(file)) == 0;
|
||||
}
|
||||
|
||||
bool IsEndOfFile(FileHandle* file)
|
||||
{
|
||||
return feof(reinterpret_cast<FILE *>(file)) != 0;
|
||||
}
|
||||
|
||||
bool FileReadLine(char* str, int count, FileHandle* file)
|
||||
{
|
||||
return fgets(str, count, reinterpret_cast<FILE *>(file)) != nullptr;
|
||||
}
|
||||
|
||||
bool FileExists(const std::string& name)
|
||||
{
|
||||
FileHandle* f = OpenFile(name, FileMode::Read);
|
||||
if (!f) return false;
|
||||
CloseFile(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalFileExists(const std::string& name)
|
||||
{
|
||||
FileHandle* f = OpenLocalFile(name, FileMode::Read);
|
||||
if (!f) return false;
|
||||
CloseFile(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSeek(FileHandle* file, s64 offset, FileSeekOrigin origin)
|
||||
{
|
||||
int stdorigin;
|
||||
switch (origin)
|
||||
{
|
||||
case FileSeekOrigin::Start: stdorigin = SEEK_SET; break;
|
||||
case FileSeekOrigin::Current: stdorigin = SEEK_CUR; break;
|
||||
case FileSeekOrigin::End: stdorigin = SEEK_END; break;
|
||||
}
|
||||
|
||||
return fseek(reinterpret_cast<FILE *>(file), offset, stdorigin) == 0;
|
||||
}
|
||||
|
||||
void FileRewind(FileHandle* file)
|
||||
{
|
||||
rewind(reinterpret_cast<FILE *>(file));
|
||||
}
|
||||
|
||||
u64 FileRead(void* data, u64 size, u64 count, FileHandle* file)
|
||||
{
|
||||
return fread(data, size, count, reinterpret_cast<FILE *>(file));
|
||||
}
|
||||
|
||||
bool FileFlush(FileHandle* file)
|
||||
{
|
||||
return fflush(reinterpret_cast<FILE *>(file)) == 0;
|
||||
}
|
||||
|
||||
u64 FileWrite(const void* data, u64 size, u64 count, FileHandle* file)
|
||||
{
|
||||
return fwrite(data, size, count, reinterpret_cast<FILE *>(file));
|
||||
}
|
||||
|
||||
u64 FileWriteFormatted(FileHandle* file, const char* fmt, ...)
|
||||
{
|
||||
if (fmt == nullptr)
|
||||
return 0;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
u64 ret = vfprintf(reinterpret_cast<FILE *>(file), fmt, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
u64 FileLength(FileHandle* file)
|
||||
{
|
||||
FILE* stdfile = reinterpret_cast<FILE *>(file);
|
||||
long pos = ftell(stdfile);
|
||||
fseek(stdfile, 0, SEEK_END);
|
||||
long len = ftell(stdfile);
|
||||
fseek(stdfile, pos, SEEK_SET);
|
||||
return len;
|
||||
}
|
||||
|
||||
void Log(LogLevel level, const char* fmt, ...)
|
||||
{
|
||||
if (fmt == nullptr)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
struct Thread
|
||||
{
|
||||
std::thread thread;
|
||||
};
|
||||
|
||||
Thread* Thread_Create(std::function<void()> func)
|
||||
{
|
||||
return new Thread { std::thread(func) };
|
||||
}
|
||||
|
||||
void Thread_Free(Thread* thread)
|
||||
{
|
||||
if (thread)
|
||||
{
|
||||
thread->thread.join();
|
||||
delete thread;
|
||||
}
|
||||
}
|
||||
|
||||
void Thread_Wait(Thread* thread)
|
||||
{
|
||||
if (thread)
|
||||
{
|
||||
thread->thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
struct Semaphore
|
||||
{
|
||||
std::counting_semaphore<> semaphore;
|
||||
|
||||
Semaphore() : semaphore(0) {}
|
||||
};
|
||||
|
||||
Semaphore* Semaphore_Create()
|
||||
{
|
||||
return new Semaphore;
|
||||
}
|
||||
|
||||
void Semaphore_Free(Semaphore* sema)
|
||||
{
|
||||
delete sema;
|
||||
}
|
||||
|
||||
void Semaphore_Reset(Semaphore* sema)
|
||||
{
|
||||
while (sema->semaphore.try_acquire());
|
||||
}
|
||||
|
||||
void Semaphore_Wait(Semaphore* sema)
|
||||
{
|
||||
sema->semaphore.acquire();
|
||||
}
|
||||
|
||||
void Semaphore_Post(Semaphore* sema, int count)
|
||||
{
|
||||
sema->semaphore.release(count);
|
||||
}
|
||||
|
||||
struct Mutex
|
||||
{
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
Mutex* Mutex_Create()
|
||||
{
|
||||
return new Mutex;
|
||||
}
|
||||
|
||||
void Mutex_Free(Mutex* mutex)
|
||||
{
|
||||
delete mutex;
|
||||
}
|
||||
|
||||
void Mutex_Lock(Mutex* mutex)
|
||||
{
|
||||
if (mutex)
|
||||
mutex->mutex.lock();
|
||||
}
|
||||
|
||||
void Mutex_Unlock(Mutex* mutex)
|
||||
{
|
||||
if (mutex)
|
||||
mutex->mutex.unlock();
|
||||
}
|
||||
|
||||
bool Mutex_TryLock(Mutex* mutex)
|
||||
{
|
||||
if (!mutex)
|
||||
return false;
|
||||
|
||||
return mutex->mutex.try_lock();
|
||||
}
|
||||
|
||||
void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) {}
|
||||
void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) {}
|
||||
void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen) {}
|
||||
void WriteDateTime(int year, int month, int day, int hour, int minute, int second) {}
|
||||
bool MP_Init() { return false; }
|
||||
void MP_DeInit() {}
|
||||
void MP_Begin() {}
|
||||
void MP_End() {}
|
||||
int MP_SendPacket(u8* data, int len, u64 timestamp) { return 0; }
|
||||
int MP_RecvPacket(u8* data, u64* timestamp) { return 0;}
|
||||
int MP_SendCmd(u8* data, int len, u64 timestamp) { return 0; }
|
||||
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) { return 0;}
|
||||
int MP_SendAck(u8* data, int len, u64 timestamp) { return 0; }
|
||||
int MP_RecvHostPacket(u8* data, u64* timestamp) { return 0; }
|
||||
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) { return 0; }
|
||||
bool LAN_Init() { return false; }
|
||||
void LAN_DeInit() {}
|
||||
int LAN_SendPacket(u8* data, int len) { return 0; }
|
||||
int LAN_RecvPacket(u8* data) { return 0; }
|
||||
void Camera_Start(int num) {}
|
||||
void Camera_Stop(int num) {}
|
||||
void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv) {}
|
||||
DynamicLibrary* DynamicLibrary_Load(const char* lib) { return nullptr;}
|
||||
void DynamicLibrary_Unload(DynamicLibrary* lib) {}
|
||||
void* DynamicLibrary_LoadFunction(DynamicLibrary* lib, const char* name) { return nullptr; }
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include "TestCommon.h"
|
||||
|
||||
namespace melonDS
|
||||
{
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef MELONDS_TESTCOMMON_H
|
||||
#define MELONDS_TESTCOMMON_H
|
||||
|
||||
namespace melonDS
|
||||
{
|
||||
}
|
||||
#endif //MELONDS_TESTCOMMON_H
|
Loading…
Reference in New Issue