mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 06:00:09 +02:00
Consent 2026-10-03, after a non-lawyer review: who runs this and how to reach them, the dataset is public (Hugging Face, possibly abroad), the Hugging Face username shows next to the contributor id, purposes (no identification), safety, the maintainer grant passes to whoever maintains Frametop next, withdrawal before and after merge, rights such as the GDPR's, and what a new version means. Residents of Illinois, Texas and Washington can't take part for now (biometric privacy laws): a third checkbox, profile consent.region_ok, checked by validate.py from this consent version on. The DRAFT banners are gone, so uploads no longer need FT_HANDREC_ALLOW_UPLOAD. hands/rec/install.sh installs the recorder on a Frame with Frametop: container packages, hand tracking and panel builds, ft-camd's capabilities, menu entry. test_qml_backend also checks each call's argument count against the slots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
2.7 KiB
Python
71 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""main.qml against ft_handrec.Backend: every backend.name(...) the window calls is a slot, and
|
|
every backend.name it reads is a property or a slot. A method that lost its @Slot shows up in
|
|
QML only as "is not a function" when its button is pressed (2026-10-03: Export did nothing).
|
|
Needs PySide6 (the dev container); skipped without it.
|
|
|
|
python3 hands/rec/tests/test_qml_backend.py
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
import unittest
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
REC = os.path.dirname(HERE)
|
|
sys.path.insert(0, REC)
|
|
|
|
|
|
class QmlBackendTest(unittest.TestCase):
|
|
def setUp(self):
|
|
try:
|
|
import ft_handrec
|
|
except ImportError as e:
|
|
self.skipTest(f"no PySide6: {e}")
|
|
meta = self.meta = ft_handrec.Backend.staticMetaObject
|
|
self.slots = {bytes(meta.method(i).name()).decode() for i in range(meta.methodCount())}
|
|
self.props = {meta.property(i).name() for i in range(meta.propertyCount())}
|
|
with open(os.path.join(REC, "main.qml")) as f:
|
|
self.qml = f.read()
|
|
|
|
def test_calls_are_slots(self):
|
|
called = set(re.findall(r"\bbackend\.(\w+)\s*\(", self.qml))
|
|
self.assertTrue(called)
|
|
self.assertEqual(sorted(called - self.slots), [], "called from main.qml but not a slot")
|
|
|
|
def test_call_arity(self):
|
|
"""Each backend.name(a, b) call passes as many arguments as some slot of that name takes
|
|
(a decorator left at the old count fails only when the button is pressed)."""
|
|
arity = {}
|
|
meta = self.meta
|
|
for i in range(meta.methodCount()):
|
|
mm = meta.method(i)
|
|
arity.setdefault(bytes(mm.name()).decode(), set()).add(mm.parameterCount())
|
|
bad = []
|
|
for m in re.finditer(r"\bbackend\.(\w+)\s*\(", self.qml):
|
|
depth, args, k, seen = 1, 0, m.end(), False
|
|
while depth and k < len(self.qml):
|
|
c = self.qml[k]
|
|
if c in "([{":
|
|
depth += 1
|
|
elif c in ")]}":
|
|
depth -= 1
|
|
elif c == "," and depth == 1:
|
|
args += 1
|
|
elif not c.isspace() and depth >= 1:
|
|
seen = True
|
|
k += 1
|
|
n = args + 1 if seen else 0
|
|
if m.group(1) in arity and n not in arity[m.group(1)]:
|
|
bad.append("%s: %d arguments, slots take %s" % (m.group(1), n, sorted(arity[m.group(1)])))
|
|
self.assertEqual(bad, [])
|
|
|
|
def test_reads_exist(self):
|
|
read = set(re.findall(r"\bbackend\.(\w+)\b(?!\s*\()", self.qml))
|
|
self.assertTrue(read)
|
|
self.assertEqual(sorted(read - self.props - self.slots), [], "read in main.qml but not on Backend")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|