Address #14 with request correlation, bounded parser reasons, startup version reporting, and rate-limited storage and settlement logs. Preserve accounting and API identity contracts, extend portable tests, and update release versions and rollout guidance.
73 lines
4.6 KiB
Python
73 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Test native bank storage/acknowledgments with ASan/UBSan, without the VM.
|
|
|
|
Uses deps/json/single_include/nlohmann/json.hpp if restored, or --json-header
|
|
pointing to the single header at the revision in the plugin's upstream.json.
|
|
"""
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--json-header", help="Header path; {game} expands to t5/t6 for their respective pinned headers")
|
|
args = parser.parse_args()
|
|
|
|
with tempfile.TemporaryDirectory(prefix="tws-bank-") as temp:
|
|
directory = Path(temp)
|
|
for game in ("t5", "t6"):
|
|
plugin = ROOT / game / "plugins" / f"tws-{game}-gsc-utils"
|
|
header = Path(args.json_header.format(game=game)) if args.json_header else plugin / "deps/json/single_include/nlohmann/json.hpp"
|
|
if not header.is_file():
|
|
parser.error(f"Missing {header}; restore dependencies or pass --json-header")
|
|
shutil.copyfile(header, directory / "json.hpp")
|
|
(directory / "stdinc.hpp").write_text("#include <algorithm>\n#include <fstream>\n#include <sstream>\n#include <json.hpp>\n")
|
|
executable = directory / game
|
|
subprocess.run([os.environ.get("CXX", "c++"), "-std=c++20", "-Wall", "-Wextra", "-Werror",
|
|
"-fsanitize=address,undefined", "-I", str(directory), "-I", str(plugin / "src/utils"),
|
|
str(plugin / "src/utils/bank.cpp"), str(ROOT / "tools/tests/bank_cases.cpp"),
|
|
"-o", str(executable)], check=True)
|
|
work = directory / f"{game}-data"
|
|
subprocess.run([str(executable), str(work)], check=True)
|
|
# Separate OS processes prove snapshots/identities don't depend on VM or
|
|
# DLL memory. A newly booted producer recovers before its own start.
|
|
subprocess.run([str(executable), str(work), "produce"], check=True)
|
|
subprocess.run([str(executable), str(work), "recover"], check=True)
|
|
subprocess.run([str(executable), str(work), "empty"], check=True)
|
|
|
|
# Compile the actual registration adapter as well, substituting only
|
|
# engine interfaces. This does not validate the Windows ABI/VM linker.
|
|
stub = (ROOT / "tools/tests/http_component_stub.hpp").read_text()
|
|
stub = stub.replace("std::vector<script_value> get_keys() const", "void emplace_back(const script_value& value) { (*data)[std::to_string(data->size())] = value; }\n std::vector<script_value> get_keys() const")
|
|
stub = stub.replace('template <typename F> void add(const char*, F) {}', r"""
|
|
inline std::map<std::string, std::function<scripting::script_value(const std::vector<std::string>&)>> functions;
|
|
template <typename F> void add(const char* name, F callback)
|
|
{
|
|
functions[name] = [callback](const std::vector<std::string>& args) -> scripting::script_value
|
|
{
|
|
if constexpr (std::is_invocable_v<F>) return callback();
|
|
else if constexpr (std::is_invocable_v<F, std::string>) return callback(args.at(0));
|
|
else if constexpr (std::is_invocable_v<F, std::string, std::string>) return callback(args.at(0), args.at(1));
|
|
else return callback(args.at(0), args.at(1), args.at(2));
|
|
};
|
|
}
|
|
""")
|
|
(directory / "component_stub.hpp").write_text(stub)
|
|
component = re.sub(r'^#include[^\n]*\n', '', (plugin / "src/component/bank.cpp").read_text(), flags=re.MULTILINE)
|
|
source = directory / "component.cpp"
|
|
source.write_text('#include "component_stub.hpp"\n#include "bank.hpp"\n#include <array>\n' + component + (ROOT / 'tools/tests/bank_component_cases.cpp').read_text())
|
|
subprocess.run([os.environ.get("CXX", "c++"), "-std=c++20", "-Wall", "-Wextra", "-Werror", "-fsanitize=address,undefined",
|
|
"-I", str(directory), "-I", str(plugin / "src/utils"), str(source),
|
|
str(plugin / "src/utils/bank.cpp"), "-o", str(directory / "component")], check=True)
|
|
result = subprocess.run([str(directory / "component")], cwd=directory, check=True, capture_output=True, text=True)
|
|
lines = result.stdout.splitlines()
|
|
for operation in ("load", "write", "remove"):
|
|
assert sum(f"operation={operation} outcome=storage_failure" in line for line in lines) == 1, lines
|
|
assert sum(f"operation={operation} outcome=recovered suppressed=1" in line for line in lines) == 1, lines
|
|
assert "private-secret" not in result.stdout and "Authorization" not in result.stdout
|
|
print(f"PASS {game}: native storage, acknowledgment, process restart and component checks", flush=True)
|