Browse Source

better tests, headless tests, idk man bunch of stuff

tags/v5
parent
commit
10dfbb6d87
Signed by: govanify GPG Key ID: DE62E1E2A6145556
7 changed files with 149 additions and 107 deletions
  1. +21
    -0
      LICENSE
  2. +14
    -0
      RELEASE_README.md
  3. +10
    -10
      src/pcsx2_ipc.h
  4. +74
    -80
      src/tests.cpp
  5. +21
    -6
      utils/build-release.sh
  6. +8
    -4
      utils/default.nix
  7. +1
    -7
      utils/pretty-tests.py

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Gauvain "GovanifY" Roussel-Tarbouriech

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 14
- 0
RELEASE_README.md View File

@@ -0,0 +1,14 @@
PCSX2 IPC API
====

This release zip file contains 4 folders:

* docs: Gives you documentation on how to use the API either in a pdf or
in html, open index.html in your browser!
* example: An example program running the C++ API for you to hack on.
* bindings: Bindings of the API in different programming languages, you might
have to build libraries to use those, please refer to their own readme files.
* tests: A precise report on what was tested before the release was published,
alongside a log file.



+ 10
- 10
src/pcsx2_ipc.h View File

@@ -219,16 +219,16 @@ class PCSX2Ipc {
* byte sent by the IPC to differentiate between commands.
*/
enum IPCCommand : unsigned char {
MsgRead8 = 0, /**< Read 8 bit value to memory. */
MsgRead16 = 1, /**< Read 16 bit value to memory. */
MsgRead32 = 2, /**< Read 32 bit value to memory. */
MsgRead64 = 3, /**< Read 64 bit value to memory. */
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
MsgUnimplemented, /**< Unimplemented IPC message. */
MsgMultiCommand = 0xFF, /**< Treats multiple IPC commands in batch. */
MsgRead8 = 0, /**< Read 8 bit value to memory. */
MsgRead16 = 1, /**< Read 16 bit value to memory. */
MsgRead32 = 2, /**< Read 32 bit value to memory. */
MsgRead64 = 3, /**< Read 64 bit value to memory. */
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
MsgUnimplemented = 0xFE, /**< Unimplemented IPC message. */
MsgMultiCommand = 0xFF, /**< Treats multiple IPC commands in batch. */
};

/**


+ 74
- 80
src/tests.cpp View File

@@ -30,103 +30,100 @@ auto open_pcsx2() -> void {
strcpy(pcsx2_path, env_p);
strcat(pcsx2_path, " &");
if (system(pcsx2_path) != 0) {
printf("PCSX2 failed to execute!\n");
printf("PCSX2 failed to execute! Have you set your environment "
"variables?\n");
}
}
}

#ifdef _WIN32
[[maybe_unused]] auto kill_pcsx2() -> void {
if (system("tskill PCSX2") != 0) {
printf("Failed to kill PCSX2!\n");
}
}
auto kill_pcsx2() -> int { return system("tskill PCSX2"); }
#else
[[maybe_unused]] auto kill_pcsx2() -> void {
if (system("pkill PCSX2") != 0) {
printf("Failed to kill PCSX2!\n");
}
}
auto kill_pcsx2() -> int { return system("pkill PCSX2"); }

#endif

SCENARIO("PCSX2 can be interacted with remotely through IPC", "[pcsx2_ipc]") {

// ensure we have a clean environment
kill_pcsx2();

WHEN("PCSX2 is not started") {
PCSX2Ipc *ipc = new PCSX2Ipc();
THEN("Errors should happen") {
try {
ipc->Write<u64>(0x00347D34, 5);
REQUIRE(0 == 1);
} catch (...) {}
PCSX2Ipc *ipc = new PCSX2Ipc();
REQUIRE_THROWS(ipc->Write<u64>(0x00347D34, 5));
}
}

GIVEN("PCSX2 with IPC started") {

open_pcsx2();
msleep(5000);

// we wait for PCSX2 to be reachable within 10 seconds.
PCSX2Ipc *check = new PCSX2Ipc();
int i = 0;
while (i < 20) {
try {
check->Read<u8>(0x00347D34);
break;
} catch (...) {
msleep(500);
i++;
// F
if (i >= 20)
REQUIRE(0 == 1);
}
}

WHEN("We want to communicate with PCSX2") {
THEN("It returns errors on invalid commands") {
PCSX2Ipc *ipc = new PCSX2Ipc();

try {
// unknown opcode test
char c_cmd[1];
// if we ever implement this opcode this command won't fail
c_cmd[0] = 0xFE;
char c_ret[1];
char c_cmd[1];
c_cmd[0] = PCSX2Ipc::MsgUnimplemented;
char c_ret[1];
// send unimplement message to server
REQUIRE_THROWS(
ipc->SendCommand(PCSX2Ipc::IPCBuffer{ 1, c_cmd },
PCSX2Ipc::IPCBuffer{ 1, c_ret });
REQUIRE(0 == 1);
} catch (...) {}

try {
// write fail test
char c_cmd[1];
c_cmd[0] = 0xFE;
char c_ret[1];
ipc->SendCommand(PCSX2Ipc::IPCBuffer{ INT_MAX, c_cmd },
PCSX2Ipc::IPCBuffer{ 1, c_ret });
REQUIRE(0 == 1);
} catch (...) {}
PCSX2Ipc::IPCBuffer{ 1, c_ret }));

try {
ipc->Write<u128>(0x00347D34, 5);
REQUIRE(0 == 1);
} catch (...) {}
// make unimplemented write
REQUIRE_THROWS(ipc->Write<u128>(0x00347D34, 5));

try {
ipc->Read<u128>(0x00347D34);
REQUIRE(0 == 1);
} catch (...) {}
kill_pcsx2();
// make unimplemented read
REQUIRE_THROWS(ipc->Read<u128>(0x00347D34));
}

THEN("It returns errors when socket issues happen") {
PCSX2Ipc *ipc = new PCSX2Ipc();

try {
// read fail test
char c_cmd[1];
c_cmd[0] = 0xFE;
ipc->SendCommand(
PCSX2Ipc::IPCBuffer{ 1, c_cmd },
PCSX2Ipc::IPCBuffer{ INT_MAX, (char *)0x00 });
REQUIRE(0 == 1);

} catch (...) {}

kill_pcsx2();
char c_cmd[1];
c_cmd[0] = 0xFE;

// trying to read from socket with INT_MAX and/or pointer from
// an address space that is not yours will return an errno. We
// do both to simulate a connection issue.
REQUIRE_THROWS(ipc->SendCommand(
PCSX2Ipc::IPCBuffer{ 1, c_cmd },
PCSX2Ipc::IPCBuffer{ INT_MAX, (char *)0x00 }));

// trying to write to a socket with INT_MAX and/or pointer from
// an address space that is not yours will return an errno. We
// do both to simulate a connection issue.
REQUIRE_THROWS(ipc->SendCommand(
PCSX2Ipc::IPCBuffer{ INT_MAX, (char *)0x00 },
PCSX2Ipc::IPCBuffer{ 1, c_cmd }));
}
}

WHEN("We want to read/write to the memory") {
THEN("The read/writes are consistent") {
PCSX2Ipc *ipc = new PCSX2Ipc();

try {
// we do a bunch of writes, read back, ensure the result is
// correct and ensure we didn't threw an error.
REQUIRE_NOTHROW([&]() {
PCSX2Ipc *ipc = new PCSX2Ipc();
ipc->Write<u64>(0x00347D34, 5);
ipc->Write<u32>(0x00347D44, 6);
ipc->Write<u16>(0x00347D54, 7);
@@ -135,20 +132,19 @@ SCENARIO("PCSX2 can be interacted with remotely through IPC", "[pcsx2_ipc]") {
REQUIRE(ipc->Read<u32>(0x00347D44) == 6);
REQUIRE(ipc->Read<u16>(0x00347D54) == 7);
REQUIRE(ipc->Read<u8>(0x00347D64) == 8);
} catch (...) {
// we shouldn't throw an exception, ever
REQUIRE(0 == 1);
}

kill_pcsx2();
}());
}
}

WHEN("We want to execute multiple operations in a row") {
THEN("The operations get executed successfully and consistently") {
PCSX2Ipc *ipc = new PCSX2Ipc();

try {
// we do a bunch of writes, read back, ensure the result is
// correct and ensure we didn't threw an error, in batch mode
// this time.
REQUIRE_NOTHROW([&]() {
PCSX2Ipc *ipc = new PCSX2Ipc();

ipc->InitializeBatch();
ipc->Write<u64, true>(0x00347E34, 5);
ipc->Write<u32, true>(0x00347E44, 6);
@@ -157,6 +153,7 @@ SCENARIO("PCSX2 can be interacted with remotely through IPC", "[pcsx2_ipc]") {
ipc->SendCommand(ipc->FinalizeBatch());

msleep(1);

ipc->InitializeBatch();
ipc->Read<u64, true>(0x00347E34);
ipc->Read<u32, true>(0x00347E44);
@@ -169,36 +166,33 @@ SCENARIO("PCSX2 can be interacted with remotely through IPC", "[pcsx2_ipc]") {
REQUIRE(ipc->GetReply<PCSX2Ipc::MsgRead16>(resr, 2) == 7);
REQUIRE(ipc->GetReply<PCSX2Ipc::MsgRead32>(resr, 1) == 6);
REQUIRE(ipc->GetReply<PCSX2Ipc::MsgRead64>(resr, 0) == 5);
} catch (...) {
// we shouldn't throw an exception, ever
REQUIRE(0 == 1);
}
}());
}

THEN("We error out when packets are too big") {
PCSX2Ipc *ipc = new PCSX2Ipc();

try {
// write packets too big
REQUIRE_THROWS([&]() {
ipc->InitializeBatch();
for (int i = 0; i < 60000; i++) {
ipc->Write<u64, true>(0x00347E34, 5);
}
ipc->SendCommand(ipc->FinalizeBatch());
}());

REQUIRE(0 == 1);
} catch (...) {}

try {
// read packets too big
REQUIRE_THROWS([&]() {
ipc->InitializeBatch();
for (int i = 0; i < 60000; i++) {
ipc->Read<u64, true>(0x00347E34);
}
ipc->SendCommand(ipc->FinalizeBatch());

REQUIRE(0 == 1);
} catch (...) {}
}());
}
kill_pcsx2();
}

// probably a good idea to kill it once we're done testing
kill_pcsx2();
}
}

+ 21
- 6
utils/build-release.sh View File

@@ -3,6 +3,7 @@
# organize folders
cd ..
rm -rf release
rm -rf build
mkdir -p release/example
cp -rf src/pcsx2_ipc.h release/
cp -rf windows-qt.pro meson.build src/ release/example/
@@ -23,18 +24,32 @@ find release -type d -name obj -prune -exec rm -rf {} \;
find release -type d -name libpcsx2_ipc_c.so -prune -exec rm -rf {} \;
find release -type d -name target -prune -exec rm -rf {} \;

# we restart our virtual X server for test cases, our test cases kill it.
killall Xvfb
Xvfb :99 &
# test cases, to see if we've broken something between releases
# and code coverage because why not :D
meson build -Db_coverage=true
cd build
# pcsx2 takes time to start up D:
meson test --timeout-multiplier=10
if meson test; then
echo "Tests ran successfully, time to build the release!"
else
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${RED}TESTS FAILED!!!\nYou broke it, PEBKAC${NC}"
# make sure i don't forget to read the logs
rm -rf ../release
rm -rf ../release.zip
exit 1
fi

# we build the coverage report and finish the release folder
ninja coverage-html
mkdir -p release/tests
mkdir -p ../release/tests
cp -rf meson-logs/coveragereport/ ../release/tests
python ../utils/pretty-tests.py meson-logs/testlog.json > release/tests/result.txt
python ../utils/pretty-tests.py meson-logs/testlog.json > ../release/tests/result.txt
cp -rf ../LICENSE ../release/
cp -rf ../RELEASE_README.md ../release/README.md
cd ..


# make the release zip
zip -r release.zip release &> /dev/null

+ 8
- 4
utils/default.nix View File

@@ -2,8 +2,6 @@
}:
pkgs.mkShell {
name = "pcsx2ipc";
nativeBuildInputs = [
];
buildInputs = [
pkgs.doxygen
pkgs.gnumake
@@ -23,10 +21,16 @@ pkgs.mkShell {
pkgs.gcovr
pkgs.catch2
pkgs.pkgconfig
pkgs.xorg.xorgserver
];

shellHook = ''
# about PCSX2_TEST:
# probably a good idea to configure PCSX2 beforehand with the plugins and
# enable console to stdio. I use Xvfb to make PCSX2 run headlessly
# on linux, I run it in build-release.sh
shellHook = ''
export DISPLAY=:99
export CARGO_HOME=$HOME/.cache/cargo
export PCSX2_TEST="/tmp/pcsx2_debug/bin/PCSX2 --nogui ~/Documents/projects/programming/hacking/games/KINGDOM_HEARTS/KH2FM/KH2FM.ISO"
export PCSX2_TEST="/tmp/pcsx2_debug/bin/PCSX2 ~/Documents/projects/programming/hacking/games/KINGDOM_HEARTS/KH2FM/KH2FM.ISO"
'';
}

+ 1
- 7
utils/pretty-tests.py View File

@@ -1,14 +1,8 @@
import json
import sys

# some JSON:
f=open(sys.argv[1])

# parse x:
y = json.loads(f.read())

print("Tests results: " + str(y["result"]))

print("Tests duration: " + str(y["duration"]))

print("Tests output:\n" + str(y["stdout"]))
print("Tests output:\n~~~~~~~~~~~~~~~~~~~~\n" + str(y["stdout"]))

Loading…
Cancel
Save