From: Philippe Proulx Date: Fri, 12 Jul 2019 18:04:54 +0000 (-0400) Subject: bt2: add integer range set support X-Git-Url: https://git.efficios.com/?a=commitdiff_plain;h=19bb8b5a08e1ee2aadd42bf11f7ed8a62f36bd5b;p=babeltrace.git bt2: add integer range set support This patch adds `bt2` wrappers for the Babeltrace library integer range and integer range set library objects. The new `test_integer_range_set.py` file tests all the new objects. Signed-off-by: Philippe Proulx Change-Id: I9e6852ca2ec6b04bcbc3b6622c03c3abcfa6e32f Reviewed-on: https://review.lttng.org/c/babeltrace/+/1716 --- diff --git a/src/bindings/python/bt2/Makefile.am b/src/bindings/python/bt2/Makefile.am index 799670cd..0324ed24 100644 --- a/src/bindings/python/bt2/Makefile.am +++ b/src/bindings/python/bt2/Makefile.am @@ -18,6 +18,8 @@ SWIG_INTERFACE_FILES = \ bt2/native_bt_field_path.i \ bt2/native_bt_field.i \ bt2/native_bt_graph.i \ + bt2/native_bt_integer_range_set.i \ + bt2/native_bt.i \ bt2/native_bt_logging.i \ bt2/native_bt_message.i \ bt2/native_bt_message_iterator.i \ @@ -48,6 +50,7 @@ STATIC_BINDINGS_DEPS = \ bt2/field_class.py \ bt2/field_path.py \ bt2/graph.py \ + bt2/integer_range_set.py \ bt2/logging.py \ bt2/message_iterator.py \ bt2/message.py \ diff --git a/src/bindings/python/bt2/bt2/__init__.py.in b/src/bindings/python/bt2/bt2/__init__.py.in index 37c7e4be..1e5b8ee3 100644 --- a/src/bindings/python/bt2/bt2/__init__.py.in +++ b/src/bindings/python/bt2/bt2/__init__.py.in @@ -43,6 +43,7 @@ from bt2.field_class import * from bt2.field_path import * from bt2.field import * from bt2.graph import * +from bt2.integer_range_set import * from bt2.logging import * from bt2.message import * from bt2.message import _DiscardedEventsMessage diff --git a/src/bindings/python/bt2/bt2/integer_range_set.py b/src/bindings/python/bt2/bt2/integer_range_set.py new file mode 100644 index 00000000..2e79da04 --- /dev/null +++ b/src/bindings/python/bt2/bt2/integer_range_set.py @@ -0,0 +1,150 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Philippe Proulx +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from bt2 import native_bt, object, utils +import collections.abc +import bt2 + + +class _IntegerRange: + def __init__(self, lower, upper): + self._check_type(lower) + self._check_type(upper) + + if lower > upper: + raise ValueError("range's lower bound ({}) is greater than its upper bound ({})".format(lower, upper)) + + self._lower = lower + self._upper = upper + + @property + def lower(self): + return self._lower + + @property + def upper(self): + return self._upper + + def contains(self, value): + self._check_type(value) + return value >= self._lower and value <= self._upper + + def __eq__(self, other): + if type(other) is not type(self): + return False + + return self.lower == other.lower and self.upper == other.upper + + +class SignedIntegerRange(_IntegerRange): + _check_type = staticmethod(utils._check_int64) + + +class UnsignedIntegerRange(_IntegerRange): + _check_type = staticmethod(utils._check_uint64) + + +class _IntegerRangeSet(object._SharedObject, collections.abc.MutableSet): + def __init__(self, ranges=None): + ptr = self._create_range_set() + + if ptr is None: + raise bt2.CreationError('cannot create range set object') + + super().__init__(ptr) + + if ranges is not None: + # will raise if not iterable + for rg in ranges: + self.add(rg) + + def __len__(self): + range_set_ptr = self._as_range_set_ptr(self._ptr) + count = native_bt.integer_range_set_get_range_count(range_set_ptr) + assert count >= 0 + return count + + def __contains__(self, other_range): + for rg in self: + if rg == other_range: + return True + + return False + + def __iter__(self): + for idx in range(len(self)): + rg_ptr = self._borrow_range_by_index_ptr(self._ptr, idx) + assert rg_ptr is not None + lower = self._range_get_lower(rg_ptr) + upper = self._range_get_upper(rg_ptr) + yield self._range_type(lower, upper) + + def __eq__(self, other): + if type(other) is not type(self): + return False + + return self._compare(self._ptr, other._ptr) + + def contains_value(self, value): + for rg in self: + if rg.contains(value): + return True + + return False + + def add(self, rg): + if type(rg) is not self._range_type: + # assume it's a simple pair (will raise if it's not) + rg = self._range_type(rg[0], rg[1]) + + status = self._add_range(self._ptr, rg.lower, rg.upper) + utils._handle_func_status(status, + 'cannot add range to range set object') + + def discard(self, rg): + raise NotImplementedError + + +class SignedIntegerRangeSet(_IntegerRangeSet): + _get_ref = staticmethod(native_bt.integer_range_set_signed_get_ref) + _put_ref = staticmethod(native_bt.integer_range_set_signed_put_ref) + _as_range_set_ptr = staticmethod(native_bt.integer_range_set_signed_as_range_set_const) + _create_range_set = staticmethod(native_bt.integer_range_set_signed_create) + _borrow_range_by_index_ptr = staticmethod(native_bt.integer_range_set_signed_borrow_range_by_index_const) + _range_get_lower = staticmethod(native_bt.integer_range_signed_get_lower) + _range_get_upper = staticmethod(native_bt.integer_range_signed_get_upper) + _add_range = staticmethod(native_bt.integer_range_set_signed_add_range) + _compare = staticmethod(native_bt.integer_range_set_signed_compare) + _range_type = SignedIntegerRange + + +class UnsignedIntegerRangeSet(_IntegerRangeSet): + _get_ref = staticmethod(native_bt.integer_range_set_unsigned_get_ref) + _put_ref = staticmethod(native_bt.integer_range_set_unsigned_put_ref) + _as_range_set_ptr = staticmethod(native_bt.integer_range_set_unsigned_as_range_set_const) + _create_range_set = staticmethod(native_bt.integer_range_set_unsigned_create) + _borrow_range_by_index_ptr = staticmethod(native_bt.integer_range_set_unsigned_borrow_range_by_index_const) + _range_get_lower = staticmethod(native_bt.integer_range_unsigned_get_lower) + _range_get_upper = staticmethod(native_bt.integer_range_unsigned_get_upper) + _add_range = staticmethod(native_bt.integer_range_set_unsigned_add_range) + _compare = staticmethod(native_bt.integer_range_set_unsigned_compare) + _range_type = UnsignedIntegerRange diff --git a/src/bindings/python/bt2/bt2/native_bt.i b/src/bindings/python/bt2/bt2/native_bt.i index c87a5e72..49adfbfd 100644 --- a/src/bindings/python/bt2/bt2/native_bt.i +++ b/src/bindings/python/bt2/bt2/native_bt.i @@ -216,6 +216,7 @@ typedef int bt_bool; %include "native_bt_plugin.i" %include "native_bt_port.i" %include "native_bt_query_exec.i" +%include "native_bt_integer_range_set.i" %include "native_bt_stream.i" %include "native_bt_stream_class.i" %include "native_bt_trace.i" diff --git a/src/bindings/python/bt2/bt2/native_bt_integer_range_set.i b/src/bindings/python/bt2/bt2/native_bt_integer_range_set.i new file mode 100644 index 00000000..f3066e16 --- /dev/null +++ b/src/bindings/python/bt2/bt2/native_bt_integer_range_set.i @@ -0,0 +1,34 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Typemap for the user data attached to (and owned by) a self component port. + * The pointer saved as the port's user data is directly the PyObject *. + * + * As per the CPython calling convention, we need to return a new reference to + * the returned object, which will be transferred to the caller. + */ + +%include +%include diff --git a/tests/Makefile.am b/tests/Makefile.am index db0ecaef..e5f36bac 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,6 +15,7 @@ dist_check_SCRIPTS = \ bindings/python/bt2/test_field_class.py \ bindings/python/bt2/test_field.py \ bindings/python/bt2/test_graph.py \ + bindings/python/bt2/test_integer_range_set.py \ bindings/python/bt2/test_message_iterator.py \ bindings/python/bt2/test_message.py \ bindings/python/bt2/test_packet.py \ diff --git a/tests/bindings/python/bt2/test_integer_range_set.py b/tests/bindings/python/bt2/test_integer_range_set.py new file mode 100644 index 00000000..2a9c5f61 --- /dev/null +++ b/tests/bindings/python/bt2/test_integer_range_set.py @@ -0,0 +1,213 @@ +# +# Copyright (C) 2019 EfficiOS Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; only version 2 +# of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +import bt2 +import unittest + + +class _IntegerRangeTestCase: + def setUp(self): + self._rg = self._CLS(self._def_lower, self._def_upper) + + def test_create(self): + self.assertEqual(self._rg.lower, self._def_lower) + self.assertEqual(self._rg.upper, self._def_upper) + + def test_create_same(self): + rg = self._CLS(self._def_lower, self._def_lower) + self.assertEqual(rg.lower, self._def_lower) + self.assertEqual(rg.upper, self._def_lower) + + def test_create_wrong_type_lower(self): + with self.assertRaises(TypeError): + rg = self._CLS(19.3, self._def_upper) + + def test_create_wrong_type_lower(self): + with self.assertRaises(TypeError): + rg = self._CLS(self._def_lower, 19.3) + + def test_create_out_of_bound_lower(self): + with self.assertRaises(ValueError): + rg = self._CLS(self._oob_lower, self._def_upper) + + def test_create_out_of_bound_upper(self): + with self.assertRaises(ValueError): + rg = self._CLS(self._def_lower, self._oob_upper) + + def test_create_lower_gt_upper(self): + with self.assertRaises(ValueError): + rg = self._CLS(self._def_lower, self._def_lower - 1) + + def test_contains_lower(self): + self.assertTrue(self._rg.contains(self._def_lower)) + + def test_contains_upper(self): + self.assertTrue(self._rg.contains(self._def_upper)) + + def test_contains_avg(self): + avg = (self._def_lower + self._def_upper) // 2 + self.assertTrue(self._rg.contains(avg)) + + def test_contains_wrong_type(self): + with self.assertRaises(TypeError): + self._rg.contains('allo') + + def test_contains_out_of_bound(self): + with self.assertRaises(ValueError): + self._rg.contains(self._oob_upper) + + def test_eq(self): + rg = self._CLS(self._def_lower, self._def_upper) + self.assertEqual(rg, self._rg) + + def test_ne(self): + rg = self._CLS(self._def_lower, self._def_upper - 1) + self.assertNotEqual(rg, self._rg) + + def test_ne_other_type(self): + self.assertNotEqual(self._rg, 48) + + +class UnsignedIntegerRangeTestCase(_IntegerRangeTestCase, unittest.TestCase): + _CLS = bt2.UnsignedIntegerRange + _def_lower = 23 + _def_upper = 18293 + _oob_lower = -1 + _oob_upper = 1 << 64 + + +class SignedIntegerRangeTestCase(_IntegerRangeTestCase, unittest.TestCase): + _CLS = bt2.SignedIntegerRange + _def_lower = -184 + _def_upper = 11547 + _oob_lower = -(1 << 63) - 1 + _oob_upper = 1 << 63 + + +class _IntegerRangeSetTestCase: + def setUp(self): + self._rs = self._CLS((self._range1, self._range2, self._range3)) + + def test_create(self): + self.assertEqual(len(self._rs), 3) + self.assertIn(self._range1, self._rs) + self.assertIn(self._range2, self._rs) + self.assertIn(self._range3, self._rs) + + def test_create_tuples(self): + rs = self._CLS(( + (self._range1.lower, self._range1.upper), + (self._range2.lower, self._range2.upper), + (self._range3.lower, self._range3.upper), + )) + self.assertEqual(len(rs), 3) + self.assertIn(self._range1, rs) + self.assertIn(self._range2, rs) + self.assertIn(self._range3, rs) + + def test_create_non_iter(self): + with self.assertRaises(TypeError): + self._rs = self._CLS(23) + + def test_create_wrong_elem_type(self): + with self.assertRaises(TypeError): + self._rs = self._CLS((self._range1, self._range2, 17)) + + def test_len(self): + self.assertEqual(len(self._rs), 3) + + def test_contains(self): + rs = self._CLS((self._range1, self._range2)) + self.assertIn(self._range1, rs) + self.assertIn(self._range2, rs) + self.assertNotIn(self._range3, rs) + + def test_contains_value(self): + rs = self._CLS((self._range1, self._range2)) + self.assertTrue(rs.contains_value(self._range1.upper)) + self.assertTrue(rs.contains_value(self._range2.lower)) + self.assertFalse(rs.contains_value(self._range3.upper)) + + def test_contains_value_wrong_type(self): + with self.assertRaises(TypeError): + self._rs.contains_value('meow') + + def test_iter(self): + range_list = list(self._rs) + self.assertIn(self._range1, range_list) + self.assertIn(self._range2, range_list) + self.assertIn(self._range3, range_list) + + def test_empty(self): + rs = self._CLS() + self.assertEqual(len(rs), 0) + self.assertEqual(len(list(rs)), 0) + + def test_add_range_obj(self): + rs = self._CLS((self._range1,)) + self.assertEqual(len(rs), 1) + rs.add(self._range2) + self.assertEqual(len(rs), 2) + self.assertIn(self._range2, rs) + + def test_discard_not_implemented(self): + with self.assertRaises(NotImplementedError): + self._rs.discard(self._range2) + + def test_eq_same_order(self): + rs = self._CLS((self._range1, self._range2, self._range3)) + self.assertEqual(self._rs, rs) + + def test_eq_diff_order(self): + rs = self._CLS((self._range1, self._range3, self._range2)) + self.assertEqual(self._rs, rs) + + def test_eq_same_addr(self): + self.assertEqual(self._rs, self._rs) + + def test_ne_diff_len(self): + rs = self._CLS((self._range1, self._range2)) + self.assertNotEqual(self._rs, rs) + + def test_ne_diff_values(self): + rs1 = self._CLS((self._range1, self._range2)) + rs2 = self._CLS((self._range1, self._range3)) + self.assertNotEqual(rs1, rs2) + + def test_ne_incompatible_type(self): + self.assertNotEqual(self._rs, object()) + + +class UnsignedIntegerRangeSetTestCase(_IntegerRangeSetTestCase, unittest.TestCase): + _CLS = bt2.UnsignedIntegerRangeSet + + def setUp(self): + self._range1 = bt2.UnsignedIntegerRange(4, 192) + self._range2 = bt2.UnsignedIntegerRange(17, 228) + self._range3 = bt2.UnsignedIntegerRange(1000, 2000) + super().setUp() + + +class SignedIntegerRangeSetTestCase(_IntegerRangeSetTestCase, unittest.TestCase): + _CLS = bt2.SignedIntegerRangeSet + + def setUp(self): + self._range1 = bt2.SignedIntegerRange(-1484, -17) + self._range2 = bt2.SignedIntegerRange(-101, 1500) + self._range3 = bt2.SignedIntegerRange(1948, 2019) + super().setUp()