From: Philippe Proulx Date: Tue, 13 Aug 2019 15:58:10 +0000 (-0400) Subject: bt2: add option field class and field support X-Git-Url: http://git.efficios.com/?p=babeltrace.git;a=commitdiff_plain;h=cec0261d56a42e810f56b39fcefbe33987c8aab8 bt2: add option field class and field support 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 Change-Id: I90aa9ef2dbcb7422ee25aaeb191a18fd9fa4998b Reviewed-on: https://review.lttng.org/c/babeltrace/+/1902 Tested-by: jenkins --- diff --git a/src/bindings/python/bt2/bt2/__init__.py b/src/bindings/python/bt2/bt2/__init__.py index 9b23828e..410ad445 100644 --- a/src/bindings/python/bt2/bt2/__init__.py +++ b/src/bindings/python/bt2/bt2/__init__.py @@ -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 diff --git a/src/bindings/python/bt2/bt2/field.py b/src/bindings/python/bt2/bt2/field.py index 2406b90e..f674a465 100644 --- a/src/bindings/python/bt2/bt2/field.py +++ b/src/bindings/python/bt2/bt2/field.py @@ -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, diff --git a/src/bindings/python/bt2/bt2/field_class.py b/src/bindings/python/bt2/bt2/field_class.py index 85ba78bb..9974a292 100644 --- a/src/bindings/python/bt2/bt2/field_class.py +++ b/src/bindings/python/bt2/bt2/field_class.py @@ -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, diff --git a/src/bindings/python/bt2/bt2/field_path.py b/src/bindings/python/bt2/bt2/field_path.py index bb644d05..51f87709 100644 --- a/src/bindings/python/bt2/bt2/field_path.py +++ b/src/bindings/python/bt2/bt2/field_path.py @@ -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 diff --git a/src/bindings/python/bt2/bt2/trace_class.py b/src/bindings/python/bt2/bt2/trace_class.py index db871783..3ae3adde 100644 --- a/src/bindings/python/bt2/bt2/trace_class.py +++ b/src/bindings/python/bt2/bt2/trace_class.py @@ -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 diff --git a/src/lib/trace-ir/resolve-field-path.c b/src/lib/trace-ir/resolve-field-path.c index c49b0245..c61d6a6c 100644 --- a/src/lib/trace-ir/resolve-field-path.c +++ b/src/lib/trace-ir/resolve-field-path.c @@ -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), diff --git a/tests/bindings/python/bt2/test_field.py b/tests/bindings/python/bt2/test_field.py index 174ae2d9..429fe2ee 100644 --- a/tests/bindings/python/bt2/test_field.py +++ b/tests/bindings/python/bt2/test_field.py @@ -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) diff --git a/tests/bindings/python/bt2/test_field_class.py b/tests/bindings/python/bt2/test_field_class.py index a25d96ea..af956507 100644 --- a/tests/bindings/python/bt2/test_field_class.py +++ b/tests/bindings/python/bt2/test_field_class.py @@ -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 ): diff --git a/tests/bindings/python/bt2/test_package.py b/tests/bindings/python/bt2/test_package.py index 836aa0a4..bcf30fed 100644 --- a/tests/bindings/python/bt2/test_package.py +++ b/tests/bindings/python/bt2/test_package.py @@ -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')