mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
113 lines
4.8 KiB
Python
113 lines
4.8 KiB
Python
"""Execute the production TCP handler/helpers and real ring through the C ABI.
|
|
|
|
No server, game, SDL, or persistent log. Missing compilers are an explicit skip.
|
|
"""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_wtrace_dump import matching_brace
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def function(source, name):
|
|
match = re.search(r'^(?:static |extern "C" )[^;{}]+\b' + name +
|
|
r'\([^;{}]*\)\s*\{', source, re.M)
|
|
if not match:
|
|
raise AssertionError(f"production function missing: {name}")
|
|
return source[match.start():matching_brace(source, match.end() - 1) + 1]
|
|
|
|
|
|
class HostLaunchProtocol(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cc, cxx = os.environ.get("CC", "cc"), os.environ.get("CXX", "c++")
|
|
if not shutil.which(cc) or not shutil.which(cxx):
|
|
raise unittest.SkipTest("set CC and CXX to the native compilers")
|
|
cls.temp = tempfile.TemporaryDirectory()
|
|
cls.addClassCleanup(cls.temp.cleanup)
|
|
temp = Path(cls.temp.name)
|
|
debug = (ROOT / "runtime/src/debug_server.c").read_text(encoding="utf-8")
|
|
runtime = (ROOT / "runtime/src/mod_runtime.cpp").read_text(encoding="utf-8")
|
|
harness = '''#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "host_launch_timing.h"
|
|
void seed_host_launch_timing_fixture(int count);
|
|
static void debug_server_send_line(const char *line) { puts(line); }
|
|
static void send_err(int id, const char *error) { (void)id; (void)error; abort(); }
|
|
'''
|
|
for name in ("json_get_int", "json_escape_string", "handle_host_launch_timings"):
|
|
harness += function(debug, name) + "\n"
|
|
harness += '''int main(int argc, char **argv) {
|
|
if (argc != 3) return 2;
|
|
seed_host_launch_timing_fixture(atoi(argv[2]));
|
|
handle_host_launch_timings(42, argv[1]);
|
|
return 0;
|
|
}
|
|
'''
|
|
bridge = '#include "host_launch_timing.h"\n'
|
|
for name in ("host_launch_timing_snapshot", "host_launch_timing_stage_name"):
|
|
bridge += function(runtime, name) + "\n"
|
|
bridge += '''extern "C" void seed_host_launch_timing_fixture(int count) {
|
|
char label[64];
|
|
for (unsigned i = 0; i < 63; ++i) label[i] = i % 3 == 0 ? '"' : i % 3 == 1 ? '\\\\' : '\\1';
|
|
label[63] = 0;
|
|
for (int i = 0; i < count; ++i) {
|
|
PSXRecompV4::HostLaunchTimingScope scope(HOST_LAUNCH_MEDIA_PROVIDER, label, label);
|
|
if (i % 2 == 0) scope.success();
|
|
}
|
|
}
|
|
'''
|
|
(temp / "handler.c").write_text(harness, encoding="utf-8")
|
|
(temp / "bridge.cpp").write_text(bridge, encoding="utf-8")
|
|
include = str(ROOT / "runtime/include")
|
|
cls.exe = temp / ("protocol.exe" if os.name == "nt" else "protocol")
|
|
for args in ([cc, "-std=c99", "-Wall", "-Wextra", "-Werror", "-I", include,
|
|
"-c", str(temp / "handler.c"), "-o", str(temp / "handler.o")],
|
|
[cxx, "-std=c++17", "-Wall", "-Wextra", "-Werror", "-pthread",
|
|
"-I", include, str(temp / "bridge.cpp"), str(temp / "handler.o"),
|
|
"-o", str(cls.exe)]):
|
|
subprocess.run(args, check=True, capture_output=True, text=True)
|
|
|
|
def query(self, count=None, seeded=300):
|
|
request = {"cmd": "host_launch_timings"}
|
|
if count is not None:
|
|
request["count"] = count
|
|
output = subprocess.check_output([str(self.exe), json.dumps(request), str(seeded)], text=True)
|
|
return json.loads(output)
|
|
|
|
def test_wrapped_maximum_labels_and_default_count(self):
|
|
result = self.query()
|
|
self.assertEqual((result["capacity"], result["total"], result["overwritten"]), (256, 300, 44))
|
|
self.assertTrue(result["nested_durations_overlap"])
|
|
self.assertEqual([e["seq"] for e in result["entries"]], list(range(45, 301)))
|
|
for e in result["entries"]:
|
|
self.assertEqual(e["stage"], "media_provider")
|
|
self.assertEqual(e["package"], '"\\\x01' * 21)
|
|
self.assertEqual(e["feature"], e["package"])
|
|
self.assertEqual(e["labels_truncated"], 0)
|
|
self.assertEqual(e["ok"], int(e["seq"] % 2 == 1))
|
|
|
|
def test_count_clamping(self):
|
|
for count, expected in ((0, 1), (-10, 1), (1, 1), (3, 3), (256, 256), (1000, 256)):
|
|
result = self.query(count)
|
|
self.assertEqual(len(result["entries"]), expected)
|
|
self.assertEqual(result["entries"][-1]["seq"], 300)
|
|
|
|
def test_empty_ring(self):
|
|
result = self.query(seeded=0)
|
|
self.assertEqual((result["total"], result["overwritten"], result["entries"]), (0, 0, []))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
suite = unittest.defaultTestLoader.loadTestsFromTestCase(HostLaunchProtocol)
|
|
result = unittest.TextTestRunner().run(suite)
|
|
raise SystemExit(77 if result.skipped else 0 if result.wasSuccessful() else 1)
|