diff --git a/bits/main.py b/bits/main.py index 93cd046..0630708 100644 --- a/bits/main.py +++ b/bits/main.py @@ -7,6 +7,9 @@ class Bits(int): def __bytes__(self): return bytes([self.__value]) + def __bool__(self): + return bool(int(self)) + def __int__(self): return self.__value @@ -24,6 +27,24 @@ class Bits(int): def __len__(self): return 8 + def __eq__(self, compare): + return int(self) == int(Bits(compare)) + + def __ne__(self, compare): + return int(self) != int(Bits(compare)) + + def __lt__(self, compare): + return int(self) < int(Bits(compare)) + + def __le__(self, compare): + return int(self) <= int(Bits(compare)) + + def __gt__(self, compare): + return int(self) > int(Bits(compare)) + + def __ge__(self, compare): + return int(self) >= int(Bits(compare)) + def __getitem__(self, index): return bool(int(self.bin()[index])) #self.bool(key) @@ -37,13 +58,25 @@ class Bits(int): def __get__(self, instance, owner): return self.bin() + def __hash__(self): + return None + def __setvalue(self, value): self.__value = 0 if isinstance(value, int): if value < 0 or value > 255: raise ValueError("Integer must be between 0 and 255") self.__value = value + elif isinstance(value, bytes): + val = 0 + for _byte in value: + val += _byte + if val > 255 or val < 0: + raise ValueError("Sum value of bytes must be between 0" + " and 255") + self.__value = val elif (len(value) > 0) and (len(value) <=8): + # TODO: apparently, can't use a list when inheriting from 'int' for bit in range(0, len(value)): self.__value += (int(bool(int(value[bit]))) * (2 ** (len(value) - 1 - bit))) @@ -52,6 +85,7 @@ class Bits(int): def bin(self, pad=True): bitcount = self.__value.bit_length() + ret = "" if pad and ((bitcount % 8) > 0): ret = "0" * (8 - (bitcount % 8)) return ret + bin(self.__value)[2:] diff --git a/tests/context.py b/tests/context.py index 8c1f6a1..c25a4b8 100644 --- a/tests/context.py +++ b/tests/context.py @@ -4,4 +4,5 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from bits import Bits +from bits import Bytes diff --git a/tests/test_bits.py b/tests/test_bits.py index 16b0e98..91b57d1 100644 --- a/tests/test_bits.py +++ b/tests/test_bits.py @@ -1,76 +1,150 @@ from unittest import TestCase -from .context import Bits +from .context import Bits, Bytes -class TestInt(TestCase): +class TestBits(TestCase): def setUp(self): - self.bitsObject = \ - Bits(b'Z\x00\xa2\xd5\xff\xff\xff\xff\xff\xff\x00\x00') self.testObjects = [ - {"hash": -3172158928563556465, - "data": b'Z\x00\xa2\xd5\xff\xff\xff\xff\xff\xff\x00\x00', - "databits": "1011010" + \ - "00000000" + \ - "10100010" + \ - "11010101" + \ - "11111111" + \ - "11111111" + \ - "11111111" + \ - "11111111" + \ - "11111111" + \ - "11111111" + \ - "00000000" + \ - "00000000", - "data_int": 27854419854894512841598894080, - "bitsObject": Bits( - b'Z\x00\xa2\xd5\xff\xff\xff\xff\xff\xff\x00\x00' - ) + {"bytes": b'\x7f', + "bits": "01111111", + "int": 127, + "reverse": 254, + "bitsObject": Bits(127) + }, + {"bytes": b'\xcf', + "bits": "11001111", + "int": 207, + "reverse": 243, + "bitsObject": Bits(207) + }, + {"bytes": b'{', + "bits": "01111011", + "int": 123, + "reverse": 222, + "bitsObject": Bits(123) + }, + {"bytes": b'<', + "bits": "00111100", + "int": 60, + "reverse": 60, + "bitsObject": Bits(60) + }, + {"bytes": b'>', + "bits": "00111110", + "int": 62, + "reverse": 124, + "bitsObject": Bits(62) } ] + def test_class(self): + """ + Test various class features + """ + with self.subTest("Reject values > 255"): + self.assertRaises(ValueError, Bits, 256) + with self.subTest("Reject values < 0"): + self.assertRaises(ValueError, Bits, -1) + with self.subTest("Reject binary strings longer than 8 characters"): + self.assertRaises(TypeError, Bits, '1100110011') + with self.subTest("Index errors"): + with self.assertRaises(IndexError): + Bits(127)[9] + with self.subTest("Test length"): + self.assertEqual(len(Bits(0)), 8) + def test_bytes(self): - # b = bytes(self.bitsObject) - # self.assertTrue(isinstance(b, bytes)) - #self.assertEquals(b, - # b'Z\x00\xa2\xd5\xff\xff\xff\xff\xff\xff\x00\x00', - # "Bytes object returned is :" + b) + """ + Test conversion to bytes object + """ for testcase in self.testObjects: - b = bytes(testcase["bitsObject"]) - self.assertTrue(isinstance(b, bytes)) - self.assertEqual(b, testcase["data"]) + with self.subTest("testcase[\"int\"]: " + str(testcase["int"])): + self.assertEqual(bytes(testcase["bitsObject"]), + testcase["bytes"]) def test_int(self): - #i = int(thebits.bits(b'Z\x00\xa2\xd5\xff\xff\xff\xff\xff\xff\x00\x00')) - #i = int(self.bitsObject) - #self.assertTrue(isinstance(i, int)) - #self.assertEqual(i, - # 27854419854894512841598894080, - # "Type is " + type(i) - # ) + """ + Test integer conversion + """ for testcase in self.testObjects: - i = int(testcase["bitsObject"]) - self.assertTrue(isinstance(i, int)) - self.assertEqual(i, testcase["data_int"]) + with self.subTest("testcase[\"int\"]: " + str(testcase["int"])): + self.assertEqual(int(testcase["bitsObject"]), testcase["int"]) def test_str(self): - #s = str(self.bitsObject) - #self.assertTrue(isinstance(s, str)) - #self.assertEqual(s, - # "10110100000000010100010110101011111111111111111111111111111111111111111111111110000000000000000", - # "String returned is " + s) + """ + Test string representation + """ for testcase in self.testObjects: - s = str(testcase["bitsObject"]) - self.assertTrue(isinstance(s, str)) - self.assertEqual(s, testcase["databits"]) + with self.subTest("testcase[\"int\"]: " + str(testcase["int"])): + s = str(testcase["bitsObject"]) + self.assertEqual(s, testcase["bits"]) - def test_hash(self): - #h = hash(self.bitsObject) - #self.assertTrue(isinstance(h, int)) - #self.assertEqual(h, - # 3156473132868910681, - # "Hashed value is " + str(h)) + def test_bits(self): + """ + Test bit representation + """ for testcase in self.testObjects: - h = hash(testcase["bitsObject"]) - self.assertTrue(isinstance(h, int)) - self.assertEqual(h, testcase["hash"]) + with self.subTest("testcase[\"int\"]: " + str(testcase["int"])): + self.assertEqual(testcase["bitsObject"].bin(), + testcase["bits"]) + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [without leading zeros]"): + self.assertEqual(testcase["bitsObject"].bin(pad=False), + testcase["bits"].strip("0")) + + def test_reverse(self): + """ + Test the reverse function changes the object bitorder and value + """ + for testcase in self.testObjects: + with self.subTest("testcase[\"int\"]: " + str(testcase["int"])): + testcase["bitsObject"].reverse() + self.assertEqual(testcase["bitsObject"].bin(), + testcase["bits"][::-1]) + + def test_comparisons(self): + """ + Test the comparison operators + """ + for testcase in self.testObjects: + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [==]"): + self.assertEqual(testcase["bitsObject"], + Bits(testcase["int"])) + + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [!=]"): + # Toggle bit 3 + testobj = Bits(testcase["int"]) + testobj[3] = not testobj[3] + self.assertNotEqual(testobj, testcase["bitsObject"]) + + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [<]"): + if testcase["int"] < 255: + self.assertLess(testcase["bitsObject"], + Bits(255)) + else: + self.skipTest("value is MAX") + + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [<=]"): + if testcase["int"] < 255: + self.assertLessEqual(testcase["bitsObject"], Bits(255)) + self.assertLessEqual(testcase["bitsObject"], + Bits(testcase["int"])) + + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [>]"): + if testcase["int"] > 0: + self.assertGreater(testcase["bitsObject"], Bits(0)) + else: + self.skipTest("value is MIN") + + with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \ + + " [>=]"): + if testcase["int"] > 0: + self.assertGreaterEqual(testcase["bitsObject"], Bits(0)) + self.assertLessEqual(testcase["bitsObject"], + Bits(testcase["int"]))