test-xinput.py 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #!/usr/bin/env python3
  2. """Build and run native XInput regression tests without the Arduino SDK."""
  3. import argparse
  4. import json
  5. import os
  6. from pathlib import Path
  7. import shlex
  8. import subprocess
  9. import tempfile
  10. ROOT = Path(__file__).resolve().parents[1]
  11. TESTS = ROOT / "tests" / "xinput"
  12. def run(command):
  13. print("+", shlex.join(str(arg) for arg in command), flush=True)
  14. subprocess.run(command, check=True)
  15. def fixture_header(destination):
  16. data = json.loads((TESTS / "fixtures.json").read_text())
  17. rows = []
  18. for console in data["consoles"]:
  19. fields = [json.dumps(console[key]) for key in
  20. ("console", "nonce", "identification", "init", "init_response")]
  21. for key in ("verify", "verify_response"):
  22. fields.append("{" + ",".join(map(json.dumps, console[key])) + "}")
  23. rows.append("{" + ",".join(fields) + "}")
  24. text = "static const Fixture fixtures[] = {" + ",\n".join(rows) + "};\n"
  25. for key in ("mac_wrap", "salt_wrap"):
  26. text += f"static const char {key}[] = {json.dumps(data[key])};\n"
  27. destination.write_text(text)
  28. def main():
  29. parser = argparse.ArgumentParser(description=__doc__)
  30. parser.add_argument("--asan", action="store_true",
  31. help="also enable AddressSanitizer (requires host runtime)")
  32. parser.add_argument("--check-fixtures", action="store_true",
  33. help="regenerate/compare independent fixtures; needs OpenSSL")
  34. args = parser.parse_args()
  35. if args.check_fixtures:
  36. actual = subprocess.check_output(
  37. [os.sys.executable, str(TESTS / "reference.py")], text=True)
  38. if json.loads(actual) != json.loads((TESTS / "fixtures.json").read_text()):
  39. raise SystemExit("Independent fixture regeneration differs")
  40. print("PASS independent OpenSSL/Python fixture regeneration", flush=True)
  41. cc = shlex.split(os.environ.get("CC", "cc"))
  42. cxx = shlex.split(os.environ.get("CXX", "c++"))
  43. flags = ["-O1", "-g", "-Wall", "-Wextra", "-Werror",
  44. "-fno-omit-frame-pointer", "-fsanitize=undefined",
  45. "-fsanitize-undefined-trap-on-error"]
  46. if args.asan:
  47. flags.append("-fsanitize=address")
  48. with tempfile.TemporaryDirectory(prefix="openpuck-xinput-") as name:
  49. build = Path(name)
  50. fixture_header(build / "fixtures.h")
  51. includes = ["-I" + str(TESTS), "-I" + str(ROOT / "OpenPuck"),
  52. "-I" + str(build)]
  53. sources = [ROOT / "OpenPuck" / "src" / "libxsm3" / name for name in
  54. ("excrypt_des.c", "excrypt_sha.c", "excrypt_parve.c",
  55. "usbdsec.c", "xsm3.c")]
  56. sources += [ROOT / "OpenPuck" / name for name in
  57. ("xinput_auth.cpp", "xinput_usb.cpp", "xinput_strings.cpp",
  58. "mode_xinput.cpp")]
  59. sources += [TESTS / "test.cpp", TESTS / "mode_test.cpp"]
  60. objects = []
  61. for source in sources:
  62. obj = build / (source.name + ".o")
  63. compiler = cc if source.suffix == ".c" else cxx
  64. standard = "-std=c11" if source.suffix == ".c" else "-std=c++17"
  65. run(compiler + [standard] + flags + includes +
  66. ["-c", str(source), "-o", str(obj)])
  67. objects.append(str(obj))
  68. executable = build / "test-xinput"
  69. run(cxx + flags + objects + ["-o", str(executable)])
  70. run([str(executable)])
  71. print("PASS native XInput tests (UBSan trap" +
  72. (" + ASan" if args.asan else "") + ")", flush=True)
  73. if __name__ == "__main__":
  74. main()