#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # # Copyright © 2018 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice (including the next # paragraph) shall be included in all copies or substantial portions of the # Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import argparse import os import sys import unittest import yaml import re from pkg_resources import parse_version class TestYaml(unittest.TestCase): filename = '' @classmethod def setUpClass(cls): with open(cls.filename) as f: cls.yaml = yaml.safe_load(f) def dict_key_crosscheck(self, d, keys): '''Check that each key in d is in keys, and that each key is in d''' self.assertEqual(sorted(d.keys()), sorted(keys)) def libinput_events(self, filter=None): '''Returns all libinput events in the recording, regardless of the device''' devices = self.yaml['devices'] for d in devices: events = d['events'] if not events: raise unittest.SkipTest() for e in events: try: libinput = e['libinput'] except KeyError: continue for ev in libinput: if (filter is None or ev['type'] == filter or isinstance(filter, list) and ev['type'] in filter): yield ev def test_sections_exist(self): sections = ['version', 'ndevices', 'libinput', 'system', 'devices'] for section in sections: self.assertIn(section, self.yaml) def test_version(self): version = self.yaml['version'] self.assertTrue(isinstance(version, int)) self.assertEqual(version, 1) def test_ndevices(self): ndevices = self.yaml['ndevices'] self.assertTrue(isinstance(ndevices, int)) self.assertGreaterEqual(ndevices, 1) self.assertEqual(ndevices, len(self.yaml['devices'])) def test_libinput(self): libinput = self.yaml['libinput'] version = libinput['version'] self.assertTrue(isinstance(version, str)) self.assertGreaterEqual(parse_version(version), parse_version('1.10.0')) git = libinput['git'] self.assertTrue(isinstance(git, str)) self.assertNotEqual(git, 'unknown') def test_system(self): system = self.yaml['system'] kernel = system['kernel'] self.assertTrue(isinstance(kernel, str)) self.assertEqual(kernel, os.uname().release) dmi = system['dmi'] self.assertTrue(isinstance(dmi, str)) with open('/sys/class/dmi/id/modalias') as f: sys_dmi = f.read()[:-1] # trailing newline self.assertEqual(dmi, sys_dmi) def test_devices_sections_exist(self): devices = self.yaml['devices'] for d in devices: self.assertIn('node', d) self.assertIn('evdev', d) self.assertIn('udev', d) def test_evdev_sections_exist(self): sections = ['name', 'id', 'codes', 'properties'] devices = self.yaml['devices'] for d in devices: evdev = d['evdev'] for s in sections: self.assertIn(s, evdev) def test_evdev_name(self): devices = self.yaml['devices'] for d in devices: evdev = d['evdev'] name = evdev['name'] self.assertTrue(isinstance(name, str)) self.assertGreaterEqual(len(name), 5) def test_evdev_id(self): devices = self.yaml['devices'] for d in devices: evdev = d['evdev'] id = evdev['id'] self.assertTrue(isinstance(id, list)) self.assertEqual(len(id), 4) self.assertGreater(id[0], 0) self.assertGreater(id[1], 0) def test_evdev_properties(self): devices = self.yaml['devices'] for d in devices: evdev = d['evdev'] properties = evdev['properties'] self.assertTrue(isinstance(properties, list)) def test_hid(self): devices = self.yaml['devices'] for d in devices: hid = d['hid'] self.assertTrue(isinstance(hid, list)) for byte in hid: self.assertGreaterEqual(byte, 0) self.assertLessEqual(byte, 255) def test_udev_sections_exist(self): sections = ['properties'] devices = self.yaml['devices'] for d in devices: udev = d['udev'] for s in sections: self.assertIn(s, udev) def test_udev_properties(self): devices = self.yaml['devices'] for d in devices: udev = d['udev'] properties = udev['properties'] self.assertTrue(isinstance(properties, list)) self.assertGreater(len(properties), 0) self.assertIn('ID_INPUT=1', properties) for p in properties: self.assertTrue(re.match('[A-Z0-9_]+=.+', p)) def test_udev_id_inputs(self): devices = self.yaml['devices'] for d in devices: udev = d['udev'] properties = udev['properties'] id_inputs = [p for p in properties if p.startswith('ID_INPUT')] # We expect ID_INPUT and ID_INPUT_something, but might get more # than one of the latter self.assertGreaterEqual(len(id_inputs), 2) def test_events_have_section(self): devices = self.yaml['devices'] for d in devices: events = d['events'] if not events: raise unittest.SkipTest() for e in events: self.assertTrue('evdev' in e or 'libinput' in e) def test_events_evdev(self): devices = self.yaml['devices'] for d in devices: events = d['events'] if not events: raise unittest.SkipTest() for e in events: try: evdev = e['evdev'] except KeyError: continue for ev in evdev: self.assertEqual(len(ev), 5) # Last event in each frame is SYN_REPORT ev_syn = evdev[-1] self.assertEqual(ev_syn[2], 0) self.assertEqual(ev_syn[3], 0) # SYN_REPORT value is 1 in case of some key repeats self.assertLessEqual(ev_syn[4], 1) def test_events_evdev_syn_report(self): devices = self.yaml['devices'] for d in devices: events = d['events'] if not events: raise unittest.SkipTest() for e in events: try: evdev = e['evdev'] except KeyError: continue for ev in evdev[:-1]: self.assertFalse(ev[2] == 0 and ev[3] == 0) def test_events_libinput(self): devices = self.yaml['devices'] for d in devices: events = d['events'] if not events: raise unittest.SkipTest() for e in events: try: libinput = e['libinput'] except KeyError: continue self.assertTrue(isinstance(libinput, list)) for ev in libinput: self.assertTrue(isinstance(ev, dict)) def test_events_libinput_type(self): types = ['POINTER_MOTION', 'POINTER_MOTION_ABSOLUTE', 'POINTER_AXIS', 'POINTER_BUTTON', 'DEVICE_ADDED', 'KEYBOARD_KEY', 'TOUCH_DOWN', 'TOUCH_MOTION', 'TOUCH_UP', 'TOUCH_FRAME', 'GESTURE_SWIPE_BEGIN', 'GESTURE_SWIPE_UPDATE', 'GESTURE_SWIPE_END', 'GESTURE_PINCH_BEGIN', 'GESTURE_PINCH_UPDATE', 'GESTURE_PINCH_END', 'TABLET_TOOL_AXIS', 'TABLET_TOOL_PROXIMITY', 'TABLET_TOOL_BUTTON', 'TABLET_TOOL_TIP', 'TABLET_PAD_STRIP', 'TABLET_PAD_RING', 'TABLET_PAD_BUTTON', 'SWITCH_TOGGLE', ] for e in self.libinput_events(): self.assertIn('type', e) self.assertIn(e['type'], types) def test_events_libinput_time(self): # DEVICE_ADDED has no time # first event may have 0.0 time if the first frame generates a # libinput event. try: for e in list(self.libinput_events())[2:]: self.assertIn('time', e) self.assertGreater(e['time'], 0.0) self.assertLess(e['time'], 60.0) except IndexError: pass def test_events_libinput_device_added(self): keys = ['type', 'seat', 'logical_seat'] for e in self.libinput_events('DEVICE_ADDED'): self.dict_key_crosscheck(e, keys) self.assertEqual(e['seat'], 'seat0') self.assertEqual(e['logical_seat'], 'default') def test_events_libinput_pointer_motion(self): keys = ['type', 'time', 'delta', 'unaccel'] for e in self.libinput_events('POINTER_MOTION'): self.dict_key_crosscheck(e, keys) delta = e['delta'] self.assertTrue(isinstance(delta, list)) self.assertEqual(len(delta), 2) for d in delta: self.assertTrue(isinstance(d, float)) unaccel = e['unaccel'] self.assertTrue(isinstance(unaccel, list)) self.assertEqual(len(unaccel), 2) for d in unaccel: self.assertTrue(isinstance(d, float)) def test_events_libinput_pointer_button(self): keys = ['type', 'time', 'button', 'state', 'seat_count'] for e in self.libinput_events('POINTER_BUTTON'): self.dict_key_crosscheck(e, keys) button = e['button'] self.assertGreater(button, 0x100) # BTN_0 self.assertLess(button, 0x160) # KEY_OK state = e['state'] self.assertIn(state, ['pressed', 'released']) scount = e['seat_count'] self.assertGreaterEqual(scount, 0) def test_events_libinput_pointer_absolute(self): keys = ['type', 'time', 'point', 'transformed'] for e in self.libinput_events('POINTER_MOTION_ABSOLUTE'): self.dict_key_crosscheck(e, keys) point = e['point'] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: self.assertTrue(isinstance(p, float)) self.assertGreater(p, 0.0) self.assertLess(p, 300.0) transformed = e['transformed'] self.assertTrue(isinstance(transformed, list)) self.assertEqual(len(transformed), 2) for t in transformed: self.assertTrue(isinstance(t, float)) self.assertGreater(t, 0.0) self.assertLess(t, 100.0) def test_events_libinput_touch(self): keys = ['type', 'time', 'slot', 'seat_slot'] for e in self.libinput_events(): if (not e['type'].startswith('TOUCH_') or e['type'] == 'TOUCH_FRAME'): continue for k in keys: self.assertIn(k, e.keys()) slot = e['slot'] seat_slot = e['seat_slot'] self.assertGreaterEqual(slot, 0) self.assertGreaterEqual(seat_slot, 0) def test_events_libinput_touch_down(self): keys = ['type', 'time', 'slot', 'seat_slot', 'point', 'transformed'] for e in self.libinput_events('TOUCH_DOWN'): self.dict_key_crosscheck(e, keys) point = e['point'] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: self.assertTrue(isinstance(p, float)) self.assertGreater(p, 0.0) self.assertLess(p, 300.0) transformed = e['transformed'] self.assertTrue(isinstance(transformed, list)) self.assertEqual(len(transformed), 2) for t in transformed: self.assertTrue(isinstance(t, float)) self.assertGreater(t, 0.0) self.assertLess(t, 100.0) def test_events_libinput_touch_motion(self): keys = ['type', 'time', 'slot', 'seat_slot', 'point', 'transformed'] for e in self.libinput_events('TOUCH_MOTION'): self.dict_key_crosscheck(e, keys) point = e['point'] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: self.assertTrue(isinstance(p, float)) self.assertGreater(p, 0.0) self.assertLess(p, 300.0) transformed = e['transformed'] self.assertTrue(isinstance(transformed, list)) self.assertEqual(len(transformed), 2) for t in transformed: self.assertTrue(isinstance(t, float)) self.assertGreater(t, 0.0) self.assertLess(t, 100.0) def test_events_libinput_touch_frame(self): devices = self.yaml['devices'] for d in devices: events = d['events'] if not events: raise unittest.SkipTest() for e in events: try: evdev = e['libinput'] except KeyError: continue need_frame = False for ev in evdev: t = ev['type'] if not t.startswith('TOUCH_'): self.assertFalse(need_frame) continue if t == 'TOUCH_FRAME': self.assertTrue(need_frame) need_frame = False else: need_frame = True self.assertFalse(need_frame) def test_events_libinput_gesture_pinch(self): keys = ['type', 'time', 'nfingers', 'delta', 'unaccel', 'angle_delta', 'scale'] for e in self.libinput_events(['GESTURE_PINCH_BEGIN', 'GESTURE_PINCH_UPDATE', 'GESTURE_PINCH_END']): self.dict_key_crosscheck(e, keys) delta = e['delta'] self.assertTrue(isinstance(delta, list)) self.assertEqual(len(delta), 2) for d in delta: self.assertTrue(isinstance(d, float)) unaccel = e['unaccel'] self.assertTrue(isinstance(unaccel, list)) self.assertEqual(len(unaccel), 2) for d in unaccel: self.assertTrue(isinstance(d, float)) adelta = e['angle_delta'] self.assertTrue(isinstance(adelta, list)) self.assertEqual(len(adelta), 2) for d in adelta: self.assertTrue(isinstance(d, float)) scale = e['scale'] self.assertTrue(isinstance(scale, list)) self.assertEqual(len(scale), 2) for d in scale: self.assertTrue(isinstance(d, float)) def test_events_libinput_gesture_swipe(self): keys = ['type', 'time', 'nfingers', 'delta', 'unaccel'] for e in self.libinput_events(['GESTURE_SWIPE_BEGIN', 'GESTURE_SWIPE_UPDATE', 'GESTURE_SWIPE_END']): self.dict_key_crosscheck(e, keys) delta = e['delta'] self.assertTrue(isinstance(delta, list)) self.assertEqual(len(delta), 2) for d in delta: self.assertTrue(isinstance(d, float)) unaccel = e['unaccel'] self.assertTrue(isinstance(unaccel, list)) self.assertEqual(len(unaccel), 2) for d in unaccel: self.assertTrue(isinstance(d, float)) def test_events_libinput_tablet_pad_button(self): keys = ['type', 'time', 'button', 'state', 'mode', 'is-toggle'] for e in self.libinput_events('TABLET_PAD_BUTTON'): self.dict_key_crosscheck(e, keys) b = e['button'] self.assertTrue(isinstance(b, int)) self.assertGreaterEqual(b, 0) self.assertLessEqual(b, 16) state = e['state'] self.assertIn(state, ['pressed', 'released']) m = e['mode'] self.assertTrue(isinstance(m, int)) self.assertGreaterEqual(m, 0) self.assertLessEqual(m, 3) t = e['is-toggle'] self.assertTrue(isinstance(t, bool)) def test_events_libinput_tablet_pad_ring(self): keys = ['type', 'time', 'number', 'position', 'source', 'mode'] for e in self.libinput_events('TABLET_PAD_RING'): self.dict_key_crosscheck(e, keys) n = e['number'] self.assertTrue(isinstance(n, int)) self.assertGreaterEqual(n, 0) self.assertLessEqual(n, 4) p = e['position'] self.assertTrue(isinstance(p, float)) if p != -1.0: # special 'end' case self.assertGreaterEqual(p, 0.0) self.assertLess(p, 360.0) m = e['mode'] self.assertTrue(isinstance(m, int)) self.assertGreaterEqual(m, 0) self.assertLessEqual(m, 3) s = e['source'] self.assertIn(s, ['finger', 'unknown']) def test_events_libinput_tablet_pad_strip(self): keys = ['type', 'time', 'number', 'position', 'source', 'mode'] for e in self.libinput_events('TABLET_PAD_STRIP'): self.dict_key_crosscheck(e, keys) n = e['number'] self.assertTrue(isinstance(n, int)) self.assertGreaterEqual(n, 0) self.assertLessEqual(n, 4) p = e['position'] self.assertTrue(isinstance(p, float)) if p != -1.0: # special 'end' case self.assertGreaterEqual(p, 0.0) self.assertLessEqual(p, 1.0) m = e['mode'] self.assertTrue(isinstance(m, int)) self.assertGreaterEqual(m, 0) self.assertLessEqual(m, 3) s = e['source'] self.assertIn(s, ['finger', 'unknown']) def test_events_libinput_tablet_tool_proximity(self): keys = ['type', 'time', 'proximity', 'tool-type', 'serial', 'axes'] for e in self.libinput_events('TABLET_TOOL_PROXIMITY'): for k in keys: self.assertIn(k, e) p = e['proximity'] self.assertIn(p, ['in', 'out']) p = e['tool-type'] self.assertIn(p, ['pen', 'eraser', 'brush', 'airbrush', 'mouse', 'lens', 'unknown']) s = e['serial'] self.assertTrue(isinstance(s, int)) self.assertGreaterEqual(s, 0) a = e['axes'] for ax in e['axes']: self.assertIn(a, 'pdtrsw') def test_events_libinput_tablet_tool(self): keys = ['type', 'time', 'tip'] for e in self.libinput_events(['TABLET_TOOL_AXIS', 'TABLET_TOOL_TIP']): for k in keys: self.assertIn(k, e) t = e['tip'] self.assertIn(t, ['down', 'up']) def test_events_libinput_tablet_tool_button(self): keys = ['type', 'time', 'button', 'state'] for e in self.libinput_events('TABLET_TOOL_BUTTON'): self.dict_key_crosscheck(e, keys) b = e['button'] # STYLUS, STYLUS2, STYLUS3 self.assertIn(b, [0x14b, 0x14c, 0x139]) s = e['state'] self.assertIn(s, ['pressed', 'released']) def test_events_libinput_tablet_tool_axes(self): for e in self.libinput_events(['TABLET_TOOL_PROXIMITY', 'TABLET_TOOL_AXIS', 'TABLET_TOOL_TIP']): point = e['point'] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: self.assertTrue(isinstance(p, float)) self.assertGreater(p, 0.0) try: tilt = e['tilt'] self.assertTrue(isinstance(tilt, list)) self.assertEqual(len(tilt), 2) for t in tilt: self.assertTrue(isinstance(t, float)) except KeyError: pass try: d = e['distance'] self.assertTrue(isinstance(d, float)) self.assertGreaterEqual(d, 0.0) self.assertNotIn('pressure', e) except KeyError: pass try: p = e['pressure'] self.assertTrue(isinstance(p, float)) self.assertGreaterEqual(p, 0.0) self.assertNotIn('distance', e) except KeyError: pass try: r = e['rotation'] self.assertTrue(isinstance(r, float)) self.assertGreaterEqual(r, 0.0) except KeyError: pass try: s = e['slider'] self.assertTrue(isinstance(s, float)) self.assertGreaterEqual(s, 0.0) except KeyError: pass try: w = e['wheel'] self.assertTrue(isinstance(w, float)) self.assertGreaterEqual(w, 0.0) self.assertIn('wheel-discrete', e) wd = e['wheel-discrete'] self.assertTrue(isinstance(wd, 1)) self.assertGreaterEqual(wd, 0.0) def sign(x): (1, -1)[x < 0] self.assertTrue(sign(w), sign(wd)) except KeyError: pass def test_events_libinput_switch(self): keys = ['type', 'time', 'switch', 'state'] for e in self.libinput_events('SWITCH_TOGGLE'): self.dict_key_crosscheck(e, keys) s = e['switch'] self.assertTrue(isinstance(s, int)) self.assertIn(s, [0x00, 0x01]) # yaml converts on/off to true/false state = e['state'] self.assertTrue(isinstance(state, bool)) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Verify a YAML recording') parser.add_argument('recording', metavar='recorded-file.yaml', type=str, help='Path to device recording') parser.add_argument('--verbose', action='store_true') args, remainder = parser.parse_known_args() TestYaml.filename = args.recording verbosity = 1 if args.verbose: verbosity = 3 argv = [sys.argv[0], *remainder] unittest.main(argv=argv, verbosity=verbosity)