diff --git a/meson.build b/meson.build index 56ba31d0..c93dd115 100644 --- a/meson.build +++ b/meson.build @@ -235,7 +235,6 @@ endif ############# tests ############################ if get_option('tests').enabled() - dep_libxml = dependency('libxml-2.0', required : false) dep_dl = cc.find_library('dl') tests_cflags = ['-DTOPSRCDIR="@0@"'.format(meson.current_source_dir())] @@ -272,16 +271,6 @@ if get_option('tests').enabled() install: false) test('test-stylus-validity', test_stylus_validity, suite: ['all', 'valgrind']) - if dep_libxml.found() - test_svg_validity = executable('test-svg-validity', - 'test/test-tablet-svg-validity.c', - dependencies: [dep_libwacom, dep_libxml, dep_glib], - include_directories: [includes_src], - c_args: tests_cflags, - install: false) - test('test-svg-validity', test_svg_validity, suite: ['all', 'valgrind']) - endif - valgrind = find_program('valgrind', required : false) if valgrind.found() valgrind_suppressions_file = dir_test / 'valgrind.suppressions' diff --git a/test/test-tablet-svg-validity.c b/test/test-tablet-svg-validity.c deleted file mode 100644 index b4a0d0a9..00000000 --- a/test/test-tablet-svg-validity.c +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright ?? 2012 Red Hat, Inc. - * - * Permission to use, copy, modify, distribute, and sell this software - * and its documentation for any purpose is hereby granted without - * fee, provided that the above copyright notice appear in all copies - * and that both that copyright notice and this permission notice - * appear in supporting documentation, and that the name of Red Hat - * not be used in advertising or publicity pertaining to distribution - * of the software without specific, written prior permission. Red - * Hat makes no representations about the suitability of this software - * for any purpose. It is provided "as is" without express or implied - * warranty. - * - * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN - * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, - * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * Authors: - * Olivier Fourdan - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include -#include -#include "libwacom.h" - -#define trace(fmt, ...) \ - fprintf(stderr, "%s: checking " fmt "\n", __func__, __VA_ARGS__) - -static xmlNodePtr -verify_has_sub (xmlNodePtr cur, char *sub) -{ - cur = cur->xmlChildrenNode; - while (cur != NULL) { - xmlChar *prop; - - /* Descend the tree if dealing with a group */ - if (xmlStrcmp(cur->name, (const xmlChar *) "g") == 0) { - xmlNodePtr sub_node; - sub_node = verify_has_sub (cur, sub); - if (sub_node != NULL) - return sub_node; - } - - prop = xmlGetProp(cur, (xmlChar *) "id"); - if (prop) { - int status = xmlStrcmp(prop, (const xmlChar *) sub); - xmlFree(prop); - if (status == 0) - return cur; - } - cur = cur->next; - } - - return NULL; -} - -static gboolean -class_found (gchar **classes, gchar *value) -{ - gchar **ptr = classes; - while (*ptr) { - if (g_str_equal (*ptr++, value)) - return TRUE; - } - - return FALSE; -} - -static void -verify_has_class (xmlNodePtr cur, const gchar *expected) -{ - xmlChar *prop; - gchar **classes_present; - gchar **classes_expected; - gchar **ptr; - - trace("%s", expected); - - prop = xmlGetProp (cur, (xmlChar *) "class"); - g_assert (prop != NULL); - g_assert (strlen((const char *) prop) > 0); - - classes_present = g_strsplit ((const gchar *) prop, " ", -1); - classes_expected = g_strsplit (expected, " ", -1); - ptr = classes_expected; - - while (*ptr) - g_assert (class_found (classes_present, *ptr++)); - - g_strfreev (classes_present); - g_strfreev (classes_expected); - xmlFree (prop); -} - -static void -check_button (xmlNodePtr cur, const WacomDevice *device, char button, gchar *type) -{ - char *sub; - char *class; - xmlNodePtr node; - WacomButtonFlags flags; - - trace("%c %s", button, type); - - /* Check ID */ - sub = g_strdup_printf ("%s%c", type, button); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - /* Check class */ - flags = libwacom_get_button_flag(device, button); - if (flags & WACOM_BUTTON_MODESWITCH) - class = g_strdup_printf ("%c ModeSwitch %s", button, type); - else - class = g_strdup_printf ("%c %s", button, type); - verify_has_class (node, class); - g_free (class); -} - -static void -check_touchstrip (xmlNodePtr cur, gchar *id) -{ - char *sub; - char *class; - xmlNodePtr node; - - trace("%s", id); - - node = verify_has_sub (cur, id); - g_assert (node != NULL); - - class = g_strdup_printf ("%s %s", id, "TouchStrip"); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Label%sUp", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sUp %s Label", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Label%sDown", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sDown %s Label", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Leader%sUp", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sUp %s Leader", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Leader%sDown", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sDown %s Leader", id, id); - verify_has_class (node, class); - g_free (class); -} - -static void -check_dial (xmlNodePtr cur, gchar *id) -{ - char *sub; - char *class; - xmlNodePtr node; - - node = verify_has_sub (cur, id); - g_assert (node != NULL); - - class = g_strdup_printf ("%s %s", id, "Dial"); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Label%sCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCW %s Label", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Label%sCCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCCW %s Label", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Leader%sCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCW %s Leader", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Leader%sCCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCCW %s Leader", id, id); - verify_has_class (node, class); - g_free (class); -} - -static void -check_touchring (xmlNodePtr cur, gchar *id) -{ - char *sub; - char *class; - xmlNodePtr node; - - trace("%s", id); - - node = verify_has_sub (cur, id); - g_assert (node != NULL); - - class = g_strdup_printf ("%s %s", id, "TouchRing"); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Label%sCCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCCW %s Label", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Label%sCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCW %s Label", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Leader%sCCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCCW %s Leader", id, id); - verify_has_class (node, class); - g_free (class); - - sub = g_strdup_printf ("Leader%sCW", id); - node = verify_has_sub (cur, sub); - g_assert (node != NULL); - g_free (sub); - - class = g_strdup_printf ("%sCW %s Leader", id, id); - verify_has_class (node, class); - g_free (class); -} - -struct fixture { - xmlDocPtr doc; - xmlNodePtr root; -}; - -static void -test_svg(struct fixture *f, gconstpointer data) -{ - g_assert_nonnull(f->doc); - g_assert_nonnull(f->root); - g_assert_cmpint(xmlStrcmp(f->root->name, (const xmlChar*) "svg"), ==, 0); -} - -static void -test_dimensions(struct fixture *f, gconstpointer data) -{ - xmlChar *prop; - - /* width is provided */ - prop = xmlGetProp(f->root, (xmlChar *) "width") ; - g_assert_nonnull(prop); - xmlFree(prop); - - /* height is provided */ - prop = xmlGetProp(f->root, (xmlChar *) "height") ; - g_assert_nonnull(prop); - xmlFree(prop); -} - -static void -test_rings(struct fixture *f, gconstpointer data) -{ - const WacomDevice *device = data; - - if (libwacom_get_num_rings(device) >= 1) - check_touchring(f->root, "Ring"); - if (libwacom_get_num_rings(device) >= 2) - check_touchring(f->root, "Ring2"); -} - -static void -test_strips(struct fixture *f, gconstpointer data) -{ - const WacomDevice *device = data; - - if (libwacom_get_num_strips(device) > 0) - check_touchstrip(f->root, "Strip"); - if (libwacom_get_num_strips(device) > 1) - check_touchstrip(f->root, "Strip2"); -} - -static void -test_dials(struct fixture *f, gconstpointer data) -{ - const WacomDevice *device = data; - - if (libwacom_get_num_dials(device) > 0) - check_dial(f->root, "Dial"); - if (libwacom_get_num_dials(device) > 1) - check_dial(f->root, "Dial2"); -} - -static void -test_buttons(struct fixture *f, gconstpointer data) -{ - const WacomDevice *device = data; - int num_buttons = libwacom_get_num_buttons (device); - - for (char button = 'A'; button < 'A' + num_buttons; button++) { - check_button(f->root, device, button, "Button"); - check_button(f->root, device, button, "Label"); - check_button(f->root, device, button, "Leader"); - } -} - -static void -setup_svg(struct fixture *f, gconstpointer data) -{ - const WacomDevice *device = data; - const char *filename = libwacom_get_layout_filename(device); - xmlDocPtr doc; - - if (!filename) - return; - - doc = xmlParseFile(filename); - f->doc = doc; - f->root = doc ? xmlDocGetRootElement(doc) : NULL; -} - -static void -teardown_svg(struct fixture *f, gconstpointer data) -{ - if (f->doc) - xmlFreeDoc(f->doc); -} - -typedef void (*testfunc)(struct fixture *f, gconstpointer d); - -/* Wrapper function to make adding tests simpler. g_test requires - * a unique test case name so we assemble that from the test function and - * the tablet data. - */ -static inline void -_add_test(WacomDevice *device, testfunc func, const char *funcname) -{ - char buf[128]; - static int count; /* guarantee unique test case names */ - const char *prefix; - - /* tests must be test_foobar */ - g_assert(strncmp(funcname, "test_", 5) == 0); - prefix = &funcname[5]; - - snprintf(buf, 128, "/svg/%s/%03d/%04x:%04x-%s", - prefix, - ++count, - libwacom_get_vendor_id(device), - libwacom_get_product_id(device), - libwacom_get_name(device)); - - g_test_add(buf, struct fixture, device, - setup_svg, func, teardown_svg); -} -#define add_test(device_, func_) \ - _add_test(device_, func_, #func_) - -#define add_test(device_, func_) \ - _add_test(device_, func_, #func_) - -static void setup_tests(WacomDevice *device) -{ - const char *name; - - name = libwacom_get_name(device); - if (g_str_equal(name, "Generic")) - return; - - if (!libwacom_get_layout_filename(device)) - return; - - add_test(device, test_svg); - add_test(device, test_dimensions); - if (libwacom_get_num_buttons(device) > 0) - add_test(device, test_buttons); - if (libwacom_get_num_rings(device) > 0) - add_test(device, test_rings); - if (libwacom_get_num_strips(device) > 0) - add_test(device, test_strips); - if (libwacom_get_num_dials(device) > 0) - add_test(device, test_dials); -} - -static WacomDeviceDatabase * -load_database(void) -{ - WacomDeviceDatabase *db; - const char *datadir; - - datadir = getenv("LIBWACOM_DATA_DIR"); - if (!datadir) - datadir = TOPSRCDIR"/data"; - - db = libwacom_database_new_for_path(datadir); - if (!db) - printf("Failed to load data from %s", datadir); - - g_assert(db); - return db; -} - -int main(int argc, char **argv) -{ - WacomDeviceDatabase *db; - WacomDevice **devices; - int rc; - - g_test_init(&argc, &argv, NULL); - - db = load_database(); - - devices = libwacom_list_devices_from_database(db, NULL); - g_assert(devices); - g_assert(*devices); - - for (WacomDevice **device = devices; *device; device++) - setup_tests(*device); - - rc = g_test_run(); - - free(devices); - libwacom_database_destroy (db); - - return rc; -} -/* vim: set noexpandtab tabstop=8 shiftwidth=8: */ diff --git a/test/test_svg.py b/test/test_svg.py new file mode 100644 index 00000000..d6f48d12 --- /dev/null +++ b/test/test_svg.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +# +# Run with pytest + +from typing import Optional, List +from pathlib import Path +from dataclasses import dataclass +import xml.etree + +import os +import logging +import string + +from . import WacomDatabase, WacomDevice +from .conftest import load_test_db + +logger = logging.getLogger(__name__) + + +def datadir(): + return Path(os.getenv("MESON_SOURCE_ROOT") or ".") / "data" + + +def layoutsdir(): + return datadir() / "layouts" + + +def load_svg(layoutfile: Path) -> xml.etree.ElementTree: + try: + f = layoutsdir() / layoutfile + assert f.exists() + tree = xml.etree.ElementTree.parse(f) + assert tree is not None + return tree + except KeyError: + pass + return None + + +@dataclass +class SvgDevice: + _db: WacomDatabase + device: WacomDevice + svg: xml.etree.ElementTree + + def has_item(self, id: str, classes: Optional[List[str]] = None): + root = self.svg.getroot() + nodes = root.findall(f".//*[@id='{id}']") + assert nodes, f"Failed to find required element with id {id}" + assert len(nodes) == 1, f"Expected one element with id {id}, have {len(nodes)}" + node = nodes[0] + for klass in classes or []: + assert klass in node.get("class").split( + " " + ), f"Missing class '{klass}' for {id}. Have: {node.get('class')}" + + +def pytest_generate_tests(metafunc): + # We need to keep the db inside each SvgDevice, otherwise python may clean it up + # and thus invalidate all our devices from db.list_devices() + db = load_test_db() + devices = db.list_devices() + devices = [ + SvgDevice(db, d, load_svg(d.layout_filename)) + for d in devices + if d.layout_filename + ] + + def filenames(devices: List[SvgDevice]) -> List[str]: + return [Path(d.device.layout_filename).name for d in devices] + + + if "svgdevice" in metafunc.fixturenames: + metafunc.parametrize( + "svgdevice", devices, ids=filenames(devices) + ) + + if "ringdevice" in metafunc.fixturenames: + devices = list(filter(lambda d: d.device.num_rings > 0, devices)) + metafunc.parametrize( + "ringdevice", devices, ids=filenames(devices) + ) + + if "stripdevice" in metafunc.fixturenames: + devices = list(filter(lambda d: d.device.num_strips > 0, devices)) + metafunc.parametrize( + "stripdevice", devices, ids=filenames(devices) + ) + + if "dialdevice" in metafunc.fixturenames: + devices = list(filter(lambda d: d.device.num_dials > 0, devices)) + metafunc.parametrize( + "dialdevice", devices, ids=filenames(devices) + ) + + if "buttondevice" in metafunc.fixturenames: + devices = list(filter(lambda d: d.device.num_buttons > 0, devices)) + metafunc.parametrize( + "buttondevice", devices, ids=filenames(devices) + ) + + +def test_svg(svgdevice): + root = svgdevice.svg.getroot() + assert root.tag in ["svg", "{http://www.w3.org/2000/svg}svg"] + assert root.get("width") is not None + assert root.get("height") is not None + + +def has_item(root, id: str, classes: Optional[List[str]] = None): + nodes = root.findall(f".//*[@id='{id}']") + assert nodes, f"Failed to find required element with id {id}" + assert len(nodes) == 1, f"Expected on element with id {id}, have {len(nodes)}" + node = nodes[0] + for klass in classes or []: + assert klass in node.get("class").split( + " " + ), f"Missing class '{klass}' for {id}. Have: {node.get('class')}" + + +def test_svg_rings(ringdevice): + if ringdevice.device.num_rings >= 1: + ringdevice.has_item(id="Ring", classes=["Ring", "TouchRing"]) + ringdevice.has_item(id="LabelRingCW", classes=["RingCW", "Ring", "Label"]) + ringdevice.has_item(id="LabelRingCCW", classes=["RingCCW", "Ring", "Label"]) + ringdevice.has_item(id="LeaderRingCW", classes=["RingCW", "Ring", "Leader"]) + ringdevice.has_item(id="LeaderRingCCW", classes=["RingCCW", "Ring", "Leader"]) + + if ringdevice.device.num_rings >= 2: + ringdevice.has_item(id="Ring2", classes=["Ring2", "TouchRing"]) + ringdevice.has_item(id="LabelRing2CW", classes=["Ring2CW", "Ring2", "Label"]) + ringdevice.has_item(id="LabelRing2CCW", classes=["Ring2CCW", "Ring2", "Label"]) + ringdevice.has_item(id="LeaderRing2CW", classes=["Ring2CW", "Ring2", "Leader"]) + ringdevice.has_item( + id="LeaderRing2CCW", classes=["Ring2CCW", "Ring2", "Leader"] + ) + + +def test_svg_strips(stripdevice): + if stripdevice.device.num_strips >= 1: + stripdevice.has_item(id="Strip", classes=["Strip", "TouchStrip"]) + stripdevice.has_item(id="LabelStripUp", classes=["StripUp", "Strip", "Label"]) + stripdevice.has_item( + id="LabelStripDown", classes=["StripDown", "Strip", "Label"] + ) + stripdevice.has_item(id="LeaderStripUp", classes=["StripUp", "Strip", "Leader"]) + stripdevice.has_item( + id="LeaderStripDown", classes=["StripDown", "Strip", "Leader"] + ) + + if stripdevice.device.num_strips >= 2: + stripdevice.has_item(id="Strip2", classes=["Strip2", "TouchStrip"]) + stripdevice.has_item( + id="LabelStrip2Up", classes=["Strip2Up", "Strip2", "Label"] + ) + stripdevice.has_item( + id="LabelStrip2Down", classes=["Strip2Down", "Strip2", "Label"] + ) + stripdevice.has_item( + id="LeaderStrip2Up", classes=["Strip2Up", "Strip2", "Leader"] + ) + stripdevice.has_item( + id="LeaderStrip2Down", classes=["Strip2Down", "Strip2", "Leader"] + ) + + +def test_svg_dials(dialdevice): + if dialdevice.device.num_dials >= 1: + dialdevice.has_item(id="Dial", classes=["Dial", "TouchDial"]) + dialdevice.has_item(id="LabelDialCW", classes=["DialCW", "Dial", "Label"]) + dialdevice.has_item(id="LabelDialCCW", classes=["DialCCW", "Dial", "Label"]) + dialdevice.has_item(id="LeaderDialCW", classes=["DialCW", "Dial", "Leader"]) + dialdevice.has_item(id="LeaderDialCCW", classes=["DialCCW", "Dial", "Leader"]) + + if dialdevice.device.num_dials >= 2: + dialdevice.has_item(id="Dial2", classes=["Dial2", "TouchDial"]) + dialdevice.has_item(id="LabelDial2CW", classes=["Dial2CW", "Dial2", "Label"]) + dialdevice.has_item(id="LabelDial2CCW", classes=["Dial2CCW", "Dial2", "Label"]) + dialdevice.has_item(id="LeaderDial2CW", classes=["Dial2CW", "Dial2", "Leader"]) + dialdevice.has_item( + id="LeaderDial2CCW", classes=["Dial2CCW", "Dial2", "Leader"] + ) + + +def test_svg_button(buttondevice): + for button in list(string.ascii_uppercase)[: buttondevice.device.num_buttons]: + modeswitch_label = [] + flags = buttondevice.device.button_flags(button) + if any(f in flags for f in WacomDevice.ButtonFlags.modeswitch_flags()): + modeswitch_label = ["ModeSwitch"] + buttondevice.has_item( + id=f"Button{button}", classes=["Button", button] + modeswitch_label + ) + buttondevice.has_item( + id=f"Label{button}", classes=["Label", button] + modeswitch_label + ) + buttondevice.has_item( + id=f"Leader{button}", classes=["Leader", button] + modeswitch_label + )