QPIDIT-88: Add per-client test skip to handle unsupported types

Skip tests instead of failing; show reason for test being skipped.
Avoid long receiver timeouts when sender has exited and will never send.
diff --git a/src/python/qpid_interop_test/amqp_types_test.py b/src/python/qpid_interop_test/amqp_types_test.py
index c6c8287..c54c977 100755
--- a/src/python/qpid_interop_test/amqp_types_test.py
+++ b/src/python/qpid_interop_test/amqp_types_test.py
@@ -279,6 +279,12 @@
         'double': {'apache-activemq-artemis': '-NaN is stripped of its sign: ENTMQ-1686',},
         }
 
+    CLIENT_SKIP = {
+        'decimal32': {'AmqpNetLite': 'Decimal types not supported: https://github.com/Azure/amqpnetlite/issues/223', },
+        'decimal64': {'AmqpNetLite': 'Decimal types not supported: https://github.com/Azure/amqpnetlite/issues/223', },
+        'decimal128': {'AmqpNetLite': 'Decimal types not supported: https://github.com/Azure/amqpnetlite/issues/223', },
+    }
+
     def __init__(self):
         super(AmqpPrimitiveTypes, self).__init__()
 
@@ -378,6 +384,10 @@
 
         @unittest.skipIf(TYPES.skip_test(amqp_type, BROKER),
                          TYPES.skip_test_message(amqp_type, BROKER))
+        @unittest.skipIf(TYPES.skip_client_test(amqp_type, send_shim.NAME),
+                         TYPES.skip_client_test_message(amqp_type, send_shim.NAME, "SENDER"))
+        @unittest.skipIf(TYPES.skip_client_test(amqp_type, receive_shim.NAME),
+                         TYPES.skip_client_test_message(amqp_type, receive_shim.NAME, "RECEIVER"))
         def inner_test_method(self):
             self.run_test(self.sender_addr,
                           self.receiver_addr,
diff --git a/src/python/qpid_interop_test/test_type_map.py b/src/python/qpid_interop_test/test_type_map.py
index 3e5367d..378f0e2 100644
--- a/src/python/qpid_interop_test/test_type_map.py
+++ b/src/python/qpid_interop_test/test_type_map.py
@@ -36,7 +36,7 @@
     #         }
     TYPE_MAP = {}
 
-    # BROKER_SKIP: For know broker issues where a type would cause a test to fail or hang,
+    # BROKER_SKIP: For known broker issues where a type would cause a test to fail or hang,
     # entries in BROKER_SKIP will cause the test to be skipped with a message.
     # This is a map containing AMQP types as a key, and a list of brokers for which this
     # type should be skipped.
@@ -54,6 +54,24 @@
     # connection property string it returns.
     BROKER_SKIP = {}
 
+    # CLIENT_SKIP: For known client issues where a type would cause a test to fail or hang,
+    # entries in CLIENT_SKIP will cause the test to be skipped with a message.
+    # This is a map containing AMQP types as a key, and a list of clients for which this
+    # type should be skipped.
+    # Format: {'jms_msg_type_1' : {'client_1' : 'skip msg for client_1',
+    #                              'client_2' : 'skip msg for client_2',
+    #                               ...
+    #                             },
+    #          'jms_msg_type_2' : {'client_1' : 'skip msg for client_1',
+    #                              'client_2' : 'skip msg for client_2',
+    #                              ...
+    #                             },
+    #          ...
+    #         }
+    # where client_1, client_2, ... are client product names as defined by the
+    # test shim NAME.
+    CLIENT_SKIP = {}
+
     def __init__(self):
         pass
 
@@ -90,7 +108,7 @@
         """Return the message to use if a test is skipped"""
         if test_type in self.BROKER_SKIP.keys():
             if broker_name in self.BROKER_SKIP[test_type]:
-                return str(self.BROKER_SKIP[test_type][broker_name])
+                return str("BROKER: " + self.BROKER_SKIP[test_type][broker_name])
         return None
 
     def skip_test(self, test_type, broker_name):
@@ -98,6 +116,18 @@
         return test_type in self.BROKER_SKIP.keys() and \
             broker_name in self.BROKER_SKIP[test_type]
 
+    def skip_client_test_message(self, test_type, client_name, role):
+        """Return the message to use if a test is skipped"""
+        if test_type in self.CLIENT_SKIP.keys():
+            if client_name in self.CLIENT_SKIP[test_type]:
+                return str(role + ": " + self.CLIENT_SKIP[test_type][client_name])
+        return None
+
+    def skip_client_test(self, test_type, client_name):
+        """Return boolean True if test should be skipped"""
+        return test_type in self.CLIENT_SKIP.keys() and \
+              client_name in self.CLIENT_SKIP[test_type]
+
     @staticmethod
     def merge_dicts(*dict_args):
         """Static method to merge two or more dictionaries"""