add dora-joy node publishing gamepad axes and buttons
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"""Shared fakes. Nothing here needs a gamepad."""
|
||||
|
||||
import pytest
|
||||
|
||||
from dora_joy.config import Config
|
||||
from dora_joy.gamepad import Sample
|
||||
|
||||
|
||||
def stick(axes=None, buttons=None, present=True):
|
||||
"""Build one gamepad reading, padded to a full six axes."""
|
||||
values = list(axes or [])
|
||||
values += [0.0] * (6 - len(values))
|
||||
# A trigger at rest reads -1, and a test that forgot to say so would look
|
||||
# like both triggers held down.
|
||||
for index in (2, 5):
|
||||
if axes is None or index >= len(axes):
|
||||
values[index] = -1.0
|
||||
return Sample(axes=values, buttons=list(buttons or []), present=present)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_env(monkeypatch):
|
||||
"""Run every test against a bare environment, whatever the shell set."""
|
||||
monkeypatch.delenv("JOYSTICK_INDEX", raising=False)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config():
|
||||
"""Return the default configuration."""
|
||||
return Config.from_env()
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Settings, and refusing the ones that cannot work."""
|
||||
|
||||
import pytest
|
||||
|
||||
from dora_joy.config import Config
|
||||
|
||||
|
||||
def test_the_first_pad_is_the_default():
|
||||
assert Config.from_env().joystick_index == 0
|
||||
|
||||
|
||||
def test_the_index_comes_from_the_environment(monkeypatch):
|
||||
monkeypatch.setenv("JOYSTICK_INDEX", "2")
|
||||
|
||||
assert Config.from_env().joystick_index == 2
|
||||
|
||||
|
||||
def test_an_index_that_is_not_a_number_names_the_variable(monkeypatch):
|
||||
monkeypatch.setenv("JOYSTICK_INDEX", "left")
|
||||
|
||||
with pytest.raises(ValueError, match="JOYSTICK_INDEX"):
|
||||
Config.from_env()
|
||||
|
||||
|
||||
def test_a_negative_index_is_refused_at_startup(monkeypatch):
|
||||
# Not at the first read of the pad, which is the one moment nobody is
|
||||
# watching the node's stderr.
|
||||
monkeypatch.setenv("JOYSTICK_INDEX", "-1")
|
||||
|
||||
with pytest.raises(ValueError, match="JOYSTICK_INDEX"):
|
||||
Config.from_env()
|
||||
@@ -0,0 +1,55 @@
|
||||
"""The wire shape: what a consumer is promised."""
|
||||
|
||||
import pyarrow as pa
|
||||
|
||||
from dora_joy.gamepad import ABSENT, Sample
|
||||
from dora_joy.message import JOY, pack
|
||||
from tests.conftest import stick
|
||||
|
||||
|
||||
def test_a_reading_survives_the_round_trip():
|
||||
packed = pack(stick(axes=[0.5, -0.5], buttons=[False, True]))
|
||||
|
||||
row = packed[0].as_py()
|
||||
|
||||
assert row["axes"][:2] == [0.5, -0.5]
|
||||
assert row["buttons"] == [False, True]
|
||||
assert row["present"] is True
|
||||
|
||||
|
||||
def test_an_absent_pad_keeps_the_schema():
|
||||
# pyarrow infers `list<null>` from an empty list, so a pad being unplugged
|
||||
# would otherwise change the type of the port halfway through a run.
|
||||
assert pack(ABSENT).type == JOY
|
||||
assert pack(stick()).type == JOY
|
||||
|
||||
|
||||
def test_an_absent_pad_says_so_rather_than_reading_as_centred():
|
||||
row = pack(ABSENT)[0].as_py()
|
||||
|
||||
assert row["present"] is False
|
||||
assert row["axes"] == []
|
||||
|
||||
|
||||
def test_axes_are_float32_and_buttons_are_bool():
|
||||
packed = pack(stick(axes=[1.0]))
|
||||
|
||||
assert packed.type.field("axes").type == pa.list_(pa.float32())
|
||||
assert packed.type.field("buttons").type == pa.list_(pa.bool_())
|
||||
|
||||
|
||||
def test_a_pad_with_more_axes_than_six_is_published_whole():
|
||||
# A pad whose d-pad arrives as two extra axes is exactly what the probe is
|
||||
# for, so nothing may be trimmed on the way out.
|
||||
row = pack(Sample(axes=[0.0] * 8, buttons=[False] * 12, present=True))[0].as_py()
|
||||
|
||||
assert len(row["axes"]) == 8
|
||||
assert len(row["buttons"]) == 12
|
||||
|
||||
|
||||
def test_nothing_is_shaped_on_the_way_out():
|
||||
# No deadzone here: a consumer that wants one applies its own, and two
|
||||
# deadzones on one number is a bug nobody can see.
|
||||
row = pack(stick(axes=[0.01]))[0].as_py()
|
||||
|
||||
assert row["axes"][0] != 0.0
|
||||
@@ -0,0 +1,54 @@
|
||||
"""The probe: what it draws, without a pad or a terminal."""
|
||||
|
||||
from dora_joy.gamepad import Sample
|
||||
from dora_joy.probe import bar, render
|
||||
from tests.conftest import stick
|
||||
|
||||
|
||||
def test_a_centred_stick_puts_the_marker_in_the_middle():
|
||||
drawn = bar(0.0)
|
||||
|
||||
assert drawn[len(drawn) // 2] == "#"
|
||||
|
||||
|
||||
def test_the_bar_leans_the_way_the_stick_is_pushed():
|
||||
left = bar(-1.0)
|
||||
right = bar(1.0)
|
||||
|
||||
assert left.index("#") < left.index("|")
|
||||
assert right.index("#") > right.index("|")
|
||||
|
||||
|
||||
def test_a_reading_beyond_full_deflection_stays_on_the_bar():
|
||||
# SDL has been known to report slightly outside -1..1 on a worn stick.
|
||||
assert "#" in bar(-4.0)
|
||||
assert "#" in bar(4.0)
|
||||
|
||||
|
||||
def test_no_gamepad_says_so_and_names_the_index_it_looked_for(config):
|
||||
config.joystick_index = 2
|
||||
|
||||
drawn = render(Sample(present=False), config)
|
||||
|
||||
assert "no gamepad found" in drawn
|
||||
assert "index 2" in drawn
|
||||
|
||||
|
||||
def test_every_axis_the_pad_reports_is_shown(config):
|
||||
# A pad whose sticks land on unexpected indices is what this is for, so
|
||||
# nothing may be hidden.
|
||||
drawn = render(Sample(axes=[0.0] * 8, buttons=[], present=True), config)
|
||||
|
||||
assert "axes (8 reported)" in drawn
|
||||
for index in range(8):
|
||||
assert f" {index} " in drawn
|
||||
|
||||
|
||||
def test_the_buttons_being_held_are_listed_by_index(config):
|
||||
drawn = render(stick(buttons=[False, True, False, True]), config)
|
||||
|
||||
assert "1, 3" in drawn
|
||||
|
||||
|
||||
def test_a_pad_with_nothing_pressed_says_so(config):
|
||||
assert "none pressed" in render(stick(), config)
|
||||
Reference in New Issue
Block a user