bt2: add option field class and field support
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Tue, 13 Aug 2019 15:58:10 +0000 (11:58 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 15 Aug 2019 15:41:44 +0000 (11:41 -0400)
This patch adds option field class and field support to the Python
bindings.

You can create an option field class with
_TraceClass.create_option_field_class(). This method requires the
optional field class and also accepts an optional selector field class
(just like the library's equivalent bt_field_class_option_create()
function).

An option field class only has the `field_class` (the optional field
class) and `selector_field_path` (can be `None`) properties.

An option field is similar to a variant field:

* You can use the `has_field` property to set whether or not the option
  field has an optional field.

* You can access the optional field with the `field` property, which
  returns `None` when the option field has no field.

* The __bool__() operator of an option field is the equivalent of the
  `has_field` property.

* You can use the `value` property (write only, like
  `_VariantField.value`) to assign a value to the optional field. This
  property also sets `has_field` to `True` so it's always safe to use.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: I90aa9ef2dbcb7422ee25aaeb191a18fd9fa4998b
Reviewed-on: https://review.lttng.org/c/babeltrace/+/1902
Tested-by: jenkins <jenkins@lttng.org>
src/bindings/python/bt2/bt2/__init__.py
src/bindings/python/bt2/bt2/field.py
src/bindings/python/bt2/bt2/field_class.py
src/bindings/python/bt2/bt2/field_path.py
src/bindings/python/bt2/bt2/trace_class.py
src/lib/trace-ir/resolve-field-path.c
tests/bindings/python/bt2/test_field.py
tests/bindings/python/bt2/test_field_class.py
tests/bindings/python/bt2/test_package.py

index 9b23828eb8b3739fd7a874a3f47753a0e5de0a08..410ad4459a81c2b99faf2169cf541feb0ada7562 100644 (file)
@@ -51,6 +51,7 @@ from bt2.field import _UnsignedEnumerationField
 from bt2.field import _SignedEnumerationField
 from bt2.field import _StringField
 from bt2.field import _StructureField
+from bt2.field import _OptionField
 from bt2.field import _VariantField
 from bt2.field import _ArrayField
 from bt2.field import _StaticArrayField
@@ -66,6 +67,7 @@ from bt2.field_class import _UnsignedEnumerationFieldClass
 from bt2.field_class import _SignedEnumerationFieldClass
 from bt2.field_class import _StringFieldClass
 from bt2.field_class import _StructureFieldClass
+from bt2.field_class import _OptionFieldClass
 from bt2.field_class import _VariantFieldClass
 from bt2.field_class import _VariantFieldClassWithoutSelector
 from bt2.field_class import _VariantFieldClassWithSelector
@@ -77,6 +79,7 @@ from bt2.field_class import _DynamicArrayFieldClass
 from bt2.field_path import FieldPathScope
 from bt2.field_path import _IndexFieldPathItem
 from bt2.field_path import _CurrentArrayElementFieldPathItem
+from bt2.field_path import _CurrentOptionContentFieldPathItem
 from bt2.graph import Graph
 from bt2.integer_range_set import SignedIntegerRange
 from bt2.integer_range_set import UnsignedIntegerRange
index 2406b90e89e432c4898b195c0da7eeaa67a7fb3e..f674a465d5d773a4770d5037a77f8fcccc5dc108 100644 (file)
@@ -37,16 +37,20 @@ def _create_field_from_ptr(ptr, owner_ptr, owner_get_ref, owner_put_ref):
     return field
 
 
-# Get the "effective" field of `field`.  If `field` is a variant, return the
-# currently selected field.  If `field` is of any other type, return `field`
+# Get the "effective" field of `field`.  If `field` is a variant, return
+# the currently selected field.  If `field` is an option, return the
+# content field.  If `field` is of any other type, return `field`
 # directly.
 
 
 def _get_leaf_field(field):
-    if not isinstance(field, _VariantField):
-        return field
+    if isinstance(field, _VariantField):
+        return _get_leaf_field(field.selected_option)
 
-    return _get_leaf_field(field.selected_option)
+    if isinstance(field, _OptionField):
+        return _get_leaf_field(field.field)
+
+    return field
 
 
 class _Field(object._UniqueObject):
@@ -480,6 +484,50 @@ class _StructureField(_ContainerField, collections.abc.MutableMapping):
         )
 
 
+class _OptionField(_Field):
+    _NAME = 'Option'
+
+    @property
+    def field(self):
+        field_ptr = native_bt.field_option_borrow_field_const(self._ptr)
+
+        if field_ptr is None:
+            return
+
+        return _create_field_from_ptr(
+            field_ptr, self._owner_ptr, self._owner_get_ref, self._owner_put_ref
+        )
+
+    @property
+    def has_field(self):
+        return self.field is not None
+
+    @has_field.setter
+    def has_field(self, value):
+        utils._check_bool(value)
+        native_bt.field_option_set_has_field(self._ptr, value)
+
+    def _spec_eq(self, other):
+        return _get_leaf_field(self) == other
+
+    def __bool__(self):
+        return self.has_field
+
+    def __str__(self):
+        return str(self.field)
+
+    def _repr(self):
+        return repr(self.field)
+
+    def _set_value(self, value):
+        self.has_field = True
+        field = self.field
+        assert field is not None
+        field.value = value
+
+    value = property(fset=_set_value)
+
+
 class _VariantField(_ContainerField, _Field):
     _NAME = 'Variant'
 
@@ -631,6 +679,7 @@ _TYPE_ID_TO_OBJ = {
     native_bt.FIELD_CLASS_TYPE_STRUCTURE: _StructureField,
     native_bt.FIELD_CLASS_TYPE_STATIC_ARRAY: _StaticArrayField,
     native_bt.FIELD_CLASS_TYPE_DYNAMIC_ARRAY: _DynamicArrayField,
+    native_bt.FIELD_CLASS_TYPE_OPTION: _OptionField,
     native_bt.FIELD_CLASS_TYPE_VARIANT_WITHOUT_SELECTOR: _VariantField,
     native_bt.FIELD_CLASS_TYPE_VARIANT_WITH_UNSIGNED_SELECTOR: _VariantField,
     native_bt.FIELD_CLASS_TYPE_VARIANT_WITH_SIGNED_SELECTOR: _VariantField,
index 85ba78bb1cfa62f999d4b4f2d3419428b64abfc4..9974a29203c96146108c134bf0f3177c9c59b2f9 100644 (file)
@@ -361,6 +361,21 @@ class _StructureFieldClass(_FieldClass, collections.abc.Mapping):
         return self._create_member_from_ptr(member_ptr)
 
 
+class _OptionFieldClass(_FieldClass):
+    @property
+    def field_class(self):
+        elem_fc_ptr = native_bt.field_class_option_borrow_field_class_const(self._ptr)
+        return _create_field_class_from_ptr_and_get_ref(elem_fc_ptr)
+
+    @property
+    def selector_field_path(self):
+        ptr = native_bt.field_class_option_borrow_selector_field_path_const(self._ptr)
+        if ptr is None:
+            return
+
+        return bt2_field_path._FieldPath._create_from_ptr_and_get_ref(ptr)
+
+
 class _VariantFieldClassOption:
     def __init__(self, name, field_class):
         self._name = name
@@ -597,6 +612,7 @@ _FIELD_CLASS_TYPE_TO_OBJ = {
     native_bt.FIELD_CLASS_TYPE_STRUCTURE: _StructureFieldClass,
     native_bt.FIELD_CLASS_TYPE_STATIC_ARRAY: _StaticArrayFieldClass,
     native_bt.FIELD_CLASS_TYPE_DYNAMIC_ARRAY: _DynamicArrayFieldClass,
+    native_bt.FIELD_CLASS_TYPE_OPTION: _OptionFieldClass,
     native_bt.FIELD_CLASS_TYPE_VARIANT_WITHOUT_SELECTOR: _VariantFieldClassWithoutSelector,
     native_bt.FIELD_CLASS_TYPE_VARIANT_WITH_UNSIGNED_SELECTOR: _VariantFieldClassWithUnsignedSelector,
     native_bt.FIELD_CLASS_TYPE_VARIANT_WITH_SIGNED_SELECTOR: _VariantFieldClassWithSignedSelector,
index bb644d05302c43c818b4d24c7dfd04df1c0f27f4..51f87709f981573c68dde05ac26d2e8de80f3e42 100644 (file)
@@ -48,6 +48,10 @@ class _CurrentArrayElementFieldPathItem(_FieldPathItem):
     pass
 
 
+class _CurrentOptionContentFieldPathItem(_FieldPathItem):
+    pass
+
+
 class _FieldPath(object._SharedObject, collections.abc.Iterable):
     _get_ref = staticmethod(native_bt.field_path_get_ref)
     _put_ref = staticmethod(native_bt.field_path_put_ref)
@@ -70,6 +74,8 @@ class _FieldPath(object._SharedObject, collections.abc.Iterable):
                 yield _IndexFieldPathItem(idx)
             elif item_type == native_bt.FIELD_PATH_ITEM_TYPE_CURRENT_ARRAY_ELEMENT:
                 yield _CurrentArrayElementFieldPathItem()
+            elif item_type == native_bt.FIELD_PATH_ITEM_TYPE_CURRENT_OPTION_CONTENT:
+                yield _CurrentOptionContentFieldPathItem()
             else:
                 assert False
 
index db871783590a76ca6c66ffc117a262214b5468f0..3ae3addefbcb3126ee21d4baed643632d791e0f7 100644 (file)
@@ -298,6 +298,21 @@ class _TraceClass(object._SharedObject, collections.abc.Mapping):
         self._check_field_class_create_status(ptr, 'dynamic array')
         return bt2_field_class._DynamicArrayFieldClass._create_from_ptr(ptr)
 
+    def create_option_field_class(self, content_fc, selector_fc=None):
+        utils._check_type(content_fc, bt2_field_class._FieldClass)
+
+        selector_fc_ptr = None
+
+        if selector_fc is not None:
+            utils._check_type(selector_fc, bt2_field_class._BoolFieldClass)
+            selector_fc_ptr = selector_fc._ptr
+
+        ptr = native_bt.field_class_option_create(
+            self._ptr, content_fc._ptr, selector_fc_ptr
+        )
+        self._check_field_class_create_status(ptr, 'option')
+        return bt2_field_class._create_field_class_from_ptr_and_get_ref(ptr)
+
     def create_variant_field_class(self, selector_fc=None):
         selector_fc_ptr = None
 
index c49b0245de156ff7502eef14d26e0c37f3de07c8..c61d6a6c4e57308d65e3fbe9a3e7fc196fa41074 100644 (file)
@@ -50,7 +50,6 @@ bool find_field_class_recursive(struct bt_field_class *fc,
        case BT_FIELD_CLASS_TYPE_OPTION:
        {
                struct bt_field_class_option *opt_fc = (void *) fc;
-
                struct bt_field_path_item item = {
                        .type = BT_FIELD_PATH_ITEM_TYPE_CURRENT_OPTION_CONTENT,
                        .index = UINT64_C(-1),
index 174ae2d9ca9d44839099d795dd2d43fd3d34e892..429fe2eea37089444eefd1e38b8e4778f99f25a1 100644 (file)
@@ -1823,6 +1823,77 @@ class StructureFieldTestCase(unittest.TestCase):
         self.assertTrue(expected_string_found)
 
 
+class OptionFieldTestCase(unittest.TestCase):
+    def _create_fc(self, tc):
+        fc = tc.create_option_field_class(tc.create_string_field_class())
+        top_fc = tc.create_structure_field_class()
+        top_fc.append_member('opt_field', fc)
+        return top_fc
+
+    def setUp(self):
+        self._tc = get_default_trace_class()
+        fld = _create_field(self._tc, self._create_fc(self._tc))
+        self._def = fld['opt_field']
+
+    def test_value_prop(self):
+        self._def.value = 'hiboux'
+        self.assertEqual(self._def.field, 'hiboux')
+        self.assertTrue(self._def.has_field)
+
+    def test_has_field_prop_true(self):
+        self._def.has_field = True
+        self.assertTrue(self._def.has_field)
+
+    def test_has_field_prop_true(self):
+        self._def.has_field = False
+        self.assertFalse(self._def.has_field)
+
+    def test_bool_op_true(self):
+        self._def.value = 'allo'
+        self.assertTrue(self._def)
+
+    def test_bool_op_true(self):
+        self._def.has_field = False
+        self.assertFalse(self._def)
+
+    def test_field_prop_existing(self):
+        self._def.value = 'meow'
+        field = self._def.field
+        self.assertEqual(field, 'meow')
+
+    def test_field_prop_none(self):
+        self._def.has_field = False
+        field = self._def.field
+        self.assertIsNone(field)
+
+    def test_field_prop_existing_then_none(self):
+        self._def.value = 'meow'
+        field = self._def.field
+        self.assertEqual(field, 'meow')
+        self._def.has_field = False
+        field = self._def.field
+        self.assertIsNone(field)
+
+    def test_eq(self):
+        field = _create_field(self._tc, self._create_fc(self._tc))
+        field = field['opt_field']
+        field.value = 'walk'
+        self._def.value = 'walk'
+        self.assertEqual(self._def, field)
+
+    def test_eq_invalid_type(self):
+        self._def.value = 'gerry'
+        self.assertNotEqual(self._def, 23)
+
+    def test_str_op(self):
+        self._def.value = 'marcel'
+        self.assertEqual(str(self._def), str(self._def.field))
+
+    def test_repr_op(self):
+        self._def.value = 'mireille'
+        self.assertEqual(repr(self._def), repr(self._def.field))
+
+
 class VariantFieldTestCase(unittest.TestCase):
     def _create_fc(self, tc):
         ft0 = tc.create_signed_integer_field_class(32)
index a25d96ea8942745acd8d3e23e5146470b3b12575..af9565070405e54644919ca4478bff1bff34d1eb 100644 (file)
@@ -385,6 +385,78 @@ class StructureFieldClassTestCase(_TestElementContainer, unittest.TestCase):
         return self._tc.create_structure_field_class()
 
 
+class OptionFieldClassTestCase(unittest.TestCase):
+    def setUp(self):
+        self._tc = get_default_trace_class()
+        self._content_fc = self._tc.create_signed_integer_field_class(23)
+        self._tag_fc = self._tc.create_bool_field_class()
+
+    def test_create_default(self):
+        fc = self._tc.create_option_field_class(self._content_fc)
+        self.assertEqual(fc.field_class.addr, self._content_fc.addr)
+        self.assertIsNone(fc.selector_field_path, None)
+
+    def _create_field_class_for_field_path_test(self):
+        fc = self._tc.create_option_field_class(self._content_fc, self._tag_fc)
+
+        foo_fc = self._tc.create_real_field_class()
+        bar_fc = self._tc.create_string_field_class()
+        baz_fc = self._tc.create_string_field_class()
+
+        inner_struct_fc = self._tc.create_structure_field_class()
+        inner_struct_fc.append_member('bar', bar_fc)
+        inner_struct_fc.append_member('baz', baz_fc)
+        inner_struct_fc.append_member('tag', self._tag_fc)
+        inner_struct_fc.append_member('opt', fc)
+
+        opt_struct_array_fc = self._tc.create_option_field_class(inner_struct_fc)
+
+        outer_struct_fc = self._tc.create_structure_field_class()
+        outer_struct_fc.append_member('foo', foo_fc)
+        outer_struct_fc.append_member('inner_opt', opt_struct_array_fc)
+
+        # The path to the selector field class is resolved when the
+        # option field class is actually used, for example in a packet
+        # context.
+        self._tc.create_stream_class(
+            packet_context_field_class=outer_struct_fc, supports_packets=True
+        )
+
+        return fc
+
+    def test_field_path_len(self):
+        fc = self._create_field_class_for_field_path_test()
+        self.assertEqual(len(fc.selector_field_path), 3)
+
+    def test_field_path_iter(self):
+        fc = self._create_field_class_for_field_path_test()
+        path_items = list(fc.selector_field_path)
+
+        self.assertEqual(len(path_items), 3)
+
+        self.assertIsInstance(path_items[0], bt2._IndexFieldPathItem)
+        self.assertEqual(path_items[0].index, 1)
+
+        self.assertIsInstance(path_items[1], bt2._CurrentOptionContentFieldPathItem)
+
+        self.assertIsInstance(path_items[2], bt2._IndexFieldPathItem)
+        self.assertEqual(path_items[2].index, 2)
+
+    def test_field_path_root_scope(self):
+        fc = self._create_field_class_for_field_path_test()
+        self.assertEqual(
+            fc.selector_field_path.root_scope, bt2.FieldPathScope.PACKET_CONTEXT
+        )
+
+    def test_create_invalid_field_class(self):
+        with self.assertRaises(TypeError):
+            self._tc.create_option_field_class(object())
+
+    def test_create_invalid_selector_type(self):
+        with self.assertRaises(TypeError):
+            self._tc.create_option_field_class(self._content_fc, 17)
+
+
 class VariantFieldClassWithoutSelectorTestCase(
     _TestElementContainer, unittest.TestCase
 ):
index 836aa0a484a83937a6688df3c8926abd2950d1ab..bcf30fed5808e0beab311d43d933153644c6631e 100644 (file)
@@ -111,6 +111,9 @@ class PackageTestCase(unittest.TestCase):
     def test_has__StructureField(self):
         self._assert_in_bt2('_StructureField')
 
+    def test_has__OptionField(self):
+        self._assert_in_bt2('_VariantField')
+
     def test_has__VariantField(self):
         self._assert_in_bt2('_VariantField')
 
@@ -156,6 +159,9 @@ class PackageTestCase(unittest.TestCase):
     def test_has__StructureFieldClass(self):
         self._assert_in_bt2('_StructureFieldClass')
 
+    def test_has__OptionFieldClass(self):
+        self._assert_in_bt2('_OptionFieldClass')
+
     def test_has__VariantFieldClass(self):
         self._assert_in_bt2('_VariantFieldClass')
 
@@ -189,6 +195,9 @@ class PackageTestCase(unittest.TestCase):
     def test_has__CurrentArrayElementFieldPathItem(self):
         self._assert_in_bt2('_CurrentArrayElementFieldPathItem')
 
+    def test_has__CurrentOptionContentFieldPathItem(self):
+        self._assert_in_bt2('_CurrentOptionContentFieldPathItem')
+
     def test_has_ComponentDescriptor(self):
         self._assert_in_bt2('ComponentDescriptor')
 
This page took 0.030933 seconds and 4 git commands to generate.