110 lines
3.2 KiB
Python
110 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
import random
|
|
|
|
|
|
from tools.humanize import (
|
|
HumanizeConfig,
|
|
jitter_duration,
|
|
jitter_point,
|
|
load_humanize_config,
|
|
set_rng,
|
|
swipe_waypoints,
|
|
)
|
|
|
|
|
|
def test_jitter_point_stays_within_radius():
|
|
rng = random.Random(42)
|
|
for _ in range(500):
|
|
x, y = jitter_point(100, 200, radius=5.0, rng=rng)
|
|
assert math.hypot(x - 100, y - 200) <= 5.0
|
|
|
|
|
|
def test_jitter_point_centered_on_input():
|
|
rng = random.Random(0)
|
|
xs = [jitter_point(0, 0, radius=4.0, rng=rng)[0] for _ in range(4000)]
|
|
assert abs(sum(xs) / len(xs)) < 0.3
|
|
|
|
|
|
def test_jitter_point_concentrated_near_target():
|
|
"""Regression: jitter must be polar-Gaussian (concentrated near target),
|
|
not uniform-on-circle (all points at exactly ``radius``).
|
|
|
|
For a folded Gaussian with sigma=radius/2, the expected mean distance is
|
|
~0.8*sigma ~= 0.4*radius. Using ``< 0.7*radius`` gives a safe margin that
|
|
fails the previous reprojection bug (mean distance == radius exactly).
|
|
"""
|
|
rng = random.Random(1234)
|
|
radius = 5.0
|
|
n = 5000
|
|
distances = [
|
|
math.hypot(x - 0.0, y - 0.0)
|
|
for x, y in (jitter_point(0.0, 0.0, radius=radius, rng=rng) for _ in range(n))
|
|
]
|
|
mean_distance = sum(distances) / n
|
|
assert mean_distance < radius * 0.7, (
|
|
f"mean distance {mean_distance:.3f} is too large; jitter looks like "
|
|
"uniform-on-circle rather than polar-Gaussian"
|
|
)
|
|
|
|
|
|
def test_jitter_duration_within_spread():
|
|
rng = random.Random(3)
|
|
for _ in range(200):
|
|
v = jitter_duration(1000, spread=0.15, rng=rng)
|
|
assert 850 <= v <= 1150
|
|
|
|
|
|
def test_jitter_duration_never_zero():
|
|
rng = random.Random(9)
|
|
for _ in range(100):
|
|
assert jitter_duration(1, spread=0.5, rng=rng) >= 1
|
|
|
|
|
|
def test_swipe_waypoints_endpoints_exact_interior_offset():
|
|
rng = random.Random(7)
|
|
pts = swipe_waypoints((0, 0), (100, 0), curvature=0.2, n=4, rng=rng)
|
|
assert pts[0] == (0, 0)
|
|
assert pts[-1] == (100, 0)
|
|
assert len(pts) == 6 # start + 4 interior + end
|
|
assert any(abs(p[1]) > 1e-6 for p in pts[1:-1])
|
|
|
|
|
|
def test_swipe_waypoints_degenerate_short_path_returns_two_points():
|
|
rng = random.Random(1)
|
|
pts = swipe_waypoints((5, 5), (5, 5), curvature=0.2, n=8, rng=rng)
|
|
assert pts == [(5, 5), (5, 5)]
|
|
|
|
|
|
def test_load_config_defaults_when_unset():
|
|
cfg = load_humanize_config(env={})
|
|
assert cfg == HumanizeConfig()
|
|
|
|
|
|
def test_load_config_disabled():
|
|
cfg = load_humanize_config(env={"APEX_HUMANIZE_ENABLED": "false"})
|
|
assert cfg.enabled is False
|
|
|
|
|
|
def test_load_config_overrides():
|
|
cfg = load_humanize_config(
|
|
env={
|
|
"APEX_HUMANIZE_TAP_RADIUS_PX": "9",
|
|
"APEX_HUMANIZE_SWIPE_CURVATURE": "0.3",
|
|
"APEX_HUMANIZE_SWIPE_WAYPOINTS": "12",
|
|
"APEX_HUMANIZE_DURATION_SPREAD": "0.2",
|
|
}
|
|
)
|
|
assert cfg.tap_radius_px == 9.0
|
|
assert cfg.swipe_curvature == 0.3
|
|
assert cfg.swipe_waypoints == 12
|
|
assert cfg.duration_spread == 0.2
|
|
|
|
|
|
def test_set_rng_makes_output_reproducible():
|
|
set_rng(random.Random(99))
|
|
a = jitter_point(0, 0, radius=5.0, rng=random.Random(99))
|
|
set_rng(None) # reset to default rng
|
|
assert isinstance(a, tuple) and len(a) == 2
|