[Bf-blender-cvs] [167c4c6962c] master: Tests: add duplicate key-map test, also test multiple configurations

Campbell Barton noreply at git.blender.org
Mon Feb 21 02:24:03 CET 2022


Commit: 167c4c6962c96b281f40ea1b540b1bf1223a5277
Author: Campbell Barton
Date:   Mon Feb 21 11:53:23 2022 +1100
Branches: master
https://developer.blender.org/rB167c4c6962c96b281f40ea1b540b1bf1223a5277

Tests: add duplicate key-map test, also test multiple configurations

Duplicate key-map items (while harmless in most cases) may cause
unexpected behavior or point to mistakes in the key-map,
so best avoid these.

===================================================================

M	tests/python/bl_keymap_validate.py

===================================================================

diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py
index ce2022f6ae7..9d613fc90f4 100644
--- a/tests/python/bl_keymap_validate.py
+++ b/tests/python/bl_keymap_validate.py
@@ -16,6 +16,7 @@ This catches the following kinds of issues:
 - Unused keymaps (keymaps which are defined but not used anywhere).
 - Event values that don't make sense for the event type, e.g.
   An escape key could have the value "NORTH" instead of "PRESS".
+- Identical key-map items.
 
 This works by taking the keymap data (before it's loaded into Blender),
 then comparing it with that same keymap after exporting and importing.
@@ -29,14 +30,28 @@ NOTE:
 import types
 import typing
 
+import os
 import contextlib
 
-import bpy
+import bpy  # type: ignore
 
 # Useful for diffing the output to see what changed in context.
 # this writes keymaps into the current directory with `.orig.py` & `.rewrite.py` extensions.
-WRITE_OUTPUT_DIR = None  # "/tmp", defaults to the systems temp directory.
+WRITE_OUTPUT_DIR = "/tmp/test"  # "/tmp", defaults to the systems temp directory.
 
+# For each preset, test all of these options.
+# The key is the preset name, containing a sequence of (attribute, value) pairs to test.
+#
+# NOTE(@campbellbarton): only add these for preferences which impact multiple keys as exposing all preferences
+# this way would create too many combinations making the tests take too long to complete.
+PRESET_PREFS = {
+    "Blender": (
+        (("select_mouse", 'LEFT'), ("use_alt_tool", False)),
+        (("select_mouse", 'LEFT'), ("use_alt_tool", True)),
+        (("select_mouse", 'RIGHT'), ("rmb_action", 'TWEAK')),
+        (("select_mouse", 'RIGHT'), ("rmb_action", 'FALLBACK_TOOL')),
+    ),
+}
 
 # -----------------------------------------------------------------------------
 # Generic Utilities
@@ -153,14 +168,49 @@ def keymap_data_clean(keyconfig_data: typing.List, *, relaxed: bool) -> None:
                 items[i] = item_op, item_event, None
 
 
-def keyconfig_activate_and_extract_data(filepath: str, *, relaxed: bool) -> typing.List:
+def keyconfig_config_as_filename_component(values: typing.Sequence[typing.Tuple[str, typing.Any]]):
+    """
+    Takes a configuration, eg:
+
+        [("select_mouse", 'LEFT'), ("rmb_action", 'TWEAK')]
+
+    And returns a filename compatible path:
+    """
+    from urllib.parse import quote
+    if not values:
+        return ""
+
+    return "(" + quote(
+        ".".join([
+            "-".join((str(key), str(val)))
+                for key, val in values
+        ]),
+        # Needed so forward slashes aren't included in the resulting name.
+        safe="",
+    ) + ")"
+
+
+def keyconfig_activate_and_extract_data(
+        filepath: str,
+        *,
+        relaxed: bool,
+        config: typing.Sequence[typing.Tuple[str, typing.Any]],
+) -> typing.List:
     """
     Activate the key-map by filepath,
     return the key-config data (cleaned for comparison).
     """
-    import bl_keymap_utils.io
+    import bl_keymap_utils.io  # type: ignore
+
+    if config:
+        bpy.ops.preferences.keyconfig_activate(filepath=filepath)
+        km_prefs = bpy.context.window_manager.keyconfigs.active.preferences
+        for attr, value in config:
+            setattr(km_prefs, attr, value)
+
     with temp_fn_argument_extractor(bl_keymap_utils.io, "keyconfig_init_from_data") as args_collected:
         bpy.ops.preferences.keyconfig_activate(filepath=filepath)
+
         # If called multiple times, something strange is happening.
         assert(len(args_collected) == 1)
         args, _kw = args_collected[0]
@@ -169,6 +219,29 @@ def keyconfig_activate_and_extract_data(filepath: str, *, relaxed: bool) -> typi
         return keyconfig_data
 
 
+def keyconfig_report_duplicates(keyconfig_data: typing.List) -> str:
+    """
+    Return true if any of the key-maps have duplicate items.
+
+    Duplicate items are reported so they can be resolved.
+    """
+    error_text = []
+    for km_idname, km_args, km_items_data in keyconfig_data:
+        items = tuple(km_items_data["items"])
+        unique: typing.Dict[str, typing.List[int]] = {}
+        for i, (item_op, item_event, item_prop) in enumerate(items):
+            # Ensure stable order as `repr` will use order of definition.
+            item_event = {key: item_event[key] for key in sorted(item_event.keys())}
+            if item_prop is not None:
+                item_prop = {key: item_prop[key] for key in sorted(item_prop.keys())}
+            item_repr = repr((item_op, item_event, item_prop))
+            unique.setdefault(item_repr, []).append(i)
+        for key, value in unique.items():
+            if len(value) > 1:
+                error_text.append("\"%s\" %r indices %r for item %r" % (km_idname, km_args, value, key))
+    return "\n".join(error_text)
+
+
 def main() -> None:
     import os
     import sys
@@ -186,37 +259,61 @@ def main() -> None:
     for filepath in presets:
         name_only = os.path.splitext(os.path.basename(filepath))[0]
 
-        print("KeyMap Validate:", name_only, end=" ... ")
-
-        data_orig = keyconfig_activate_and_extract_data(filepath, relaxed=relaxed)
-
-        with tempfile.TemporaryDirectory() as dir_temp:
-            filepath_temp = os.path.join(dir_temp, name_only + ".test.py")
-            bpy.ops.preferences.keyconfig_export(filepath=filepath_temp, all=True)
-            data_reimport = keyconfig_activate_and_extract_data(filepath_temp, relaxed=relaxed)
-
-        # Comparing a pretty printed string tends to give more useful
-        # text output compared to the data-structure. Both will work.
-        if (cmp_message := report_humanly_readable_difference(
-                pprint.pformat(data_orig, indent=0, width=120),
-                pprint.pformat(data_reimport, indent=0, width=120),
-        )):
-            print("FAILED!")
-            sys.stdout.write((
-                "Keymap %s has inconsistency on re-importing:\n"
-                "  %r"
-            ) % (filepath, cmp_message))
-            has_error = True
-        else:
-            print("OK!")
-
-        if WRITE_OUTPUT_DIR:
-            name_only_temp = os.path.join(WRITE_OUTPUT_DIR, name_only)
-            print("Writing data to:", name_only_temp + ".*.py")
-            with open(name_only_temp + ".orig.py", 'w') as fh:
-                fh.write(pprint.pformat(data_orig, indent=0, width=120))
-            with open(name_only_temp + ".rewrite.py", 'w') as fh:
-                fh.write(pprint.pformat(data_reimport, indent=0, width=120))
+        for config in PRESET_PREFS.get(name_only, ((),)):
+            name_only_with_config = name_only + keyconfig_config_as_filename_component(config)
+            print("KeyMap Validate:", name_only_with_config, end=" ... ")
+            data_orig = keyconfig_activate_and_extract_data(
+                filepath,
+                relaxed=relaxed,
+                config=config,
+            )
+
+            with tempfile.TemporaryDirectory() as dir_temp:
+                filepath_temp = os.path.join(
+                    dir_temp,
+                    name_only_with_config + ".test" + ".py",
+                )
+
+                bpy.ops.preferences.keyconfig_export(filepath=filepath_temp, all=True)
+                data_reimport = keyconfig_activate_and_extract_data(
+                    filepath_temp,
+                    relaxed=relaxed,
+                    # No configuration supported when loading exported key-maps.
+                    config=(),
+                )
+
+            # Comparing a pretty printed string tends to give more useful
+            # text output compared to the data-structure. Both will work.
+            if (cmp_message := report_humanly_readable_difference(
+                    pprint.pformat(data_orig, indent=0, width=120),
+                    pprint.pformat(data_reimport, indent=0, width=120),
+            )):
+                error_text_consistency = "Keymap %s has inconsistency on re-importing." % cmp_message
+            else:
+                error_text_consistency = ""
+
+            # Perform an additional sanity check:
+            # That there are no identical key-map items.
+            error_text_duplicates = keyconfig_report_duplicates(data_orig)
+
+            if error_text_consistency or error_text_duplicates:
+                print("FAILED!")
+                print("%r has errors!" % filepath)
+                if error_text_consistency:
+                    print(error_text_consistency)
+                if error_text_duplicates:
+                    print(error_text_duplicates)
+            else:
+                print("OK!")
+
+            if WRITE_OUTPUT_DIR:
+                os.makedirs(WRITE_OUTPUT_DIR, exist_ok=True)
+                name_only_temp = os.path.join(WRITE_OUTPUT_DIR, name_only_with_config)
+                print("Writing data to:", name_only_temp + ".*.py")
+                with open(name_only_temp + ".orig.py", 'w') as fh:
+                    fh.write(pprint.pformat(data_orig, indent=0, width=120))
+                with open(name_only_temp + ".rewrite.py", 'w') as fh:
+                    fh.write(pprint.pformat(data_reimport, indent=0, width=120))
     if has_error:
         sys.exit(1)



More information about the Bf-blender-cvs mailing list