Compare commits

...

17 Commits

Author SHA1 Message Date
S Groesz 49ef87f8c9 add bit_len to Bytes 2021-04-17 05:01:32 +00:00
S Groesz ecc1b3bd8d update source code location 2021-04-14 23:34:23 +00:00
S Groesz 55ae3544d5 add dev notes 2021-04-14 03:27:53 +00:00
S Groesz de7c42fe8f fix typo 2021-04-14 03:17:42 +00:00
S Groesz 34fa1b4502 update readme 2021-04-14 03:06:08 +00:00
S Groesz 90ff3fe077 update publish scripts 2021-04-14 02:57:57 +00:00
S Groesz bafb8bc4b7 update package version 2021-04-14 02:07:16 +00:00
S Groesz d111d402db compatibility update for Python 3.6 2021-04-14 00:59:02 +00:00
S Groesz c63d2dd4bb added hex methods 2021-04-13 06:41:39 +00:00
S Groesz 66aa7ee915 tests passing; improve doc 2020-11-23 07:51:45 +00:00
S Groesz 97f7d58dd7 Improve doc; add Bit.set and unset methods 2020-11-22 06:30:47 +00:00
S Groesz 1226400c2d scripts to publish to pypi 2020-11-19 04:56:22 +00:00
S Groesz 3ad4435b3f update for pypi 2020-11-19 04:23:42 +00:00
S Groesz 1533f7ade6 remove egg-info from repo 2020-10-28 06:50:31 +00:00
S Groesz 42bb3f13e6 package 2020-10-28 06:45:49 +00:00
S Groesz 63929e6f5b ref py-flags 2020-10-28 04:44:12 +00:00
S Groesz e94a824fa0 refactor code structure. tests passing. 2020-10-28 03:20:38 +00:00
14 changed files with 802 additions and 90 deletions

4
.gitignore vendored
View File

@ -1,3 +1,7 @@
__pycache__
*.bak
*.swp
build/
dist/
*.egg-info
*.token

View File

@ -1,10 +1,10 @@
init:
pip install -r requirements.txt
pip3 install --user -r requirements.txt
test:
python -m unittest -v tests/test_bit.py
python -m unittest -v tests/test_bits.py
python -m unittest -v tests/test_bytes.py
python3 -m unittest -v tests/test_bit.py
python3 -m unittest -v tests/test_bits.py
python3 -m unittest -v tests/test_bytes.py
.PHONY: init test

View File

@ -1,3 +1,27 @@
# Bits
Helps manage your bits!
Intended to be compatible with Python 3.6+. Written and tested against Python
3.8.5 Ubuntu 20.04.1 LTS.
If something doesn't work in Python 3.6, it's a bug. Please report any such
bugs if they are encountered.
# PyPI
View on PyPI at [https://pypi.org/project/binary-bits/](https://pypi.org/project/binary-bits/)
Install from PyPI:
python3 -m pip install binary-bits --upgrade
Use in your project:
```python
import bits
my_bytes = bits.Bytes(bytes.fromhex('deadbeef'))
```
# Similar projects
## [py-flags](https://pypi.org/project/py-flags/)

View File

@ -1 +0,0 @@
from .main import Bit, Bits, Bytes

2
build.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh -
python3 -m build

15
dev.md Normal file
View File

@ -0,0 +1,15 @@
Steps to publish to PyPI:
1) run `make test`
2) update version in setup.py
3) Tag the current release in git to match the version in setup.py
git tag -a 1.2.0 -m 'Release 1.2.0'
4) commit any pending changes to git
git add .
git commit -m 'commit changes'
git push origin 1.2.0
5) test publish
./publish.sh test
6) publish to PyPI
./publish.sh pypi

5
docs/Makefile Normal file
View File

@ -0,0 +1,5 @@
# Makefile for Sphinx documentation
#
# This is currently a stub. TODO: documentation
#

84
publish.sh Executable file
View File

@ -0,0 +1,84 @@
#!/bin/sh -
_FALSE=0
_TRUE=1
__ScriptName="publish.sh"
username="__token__"
test_token=`cat test.token`
pypi_token=`cat pypi.token`
#--------------------------------------------------------------------------------------------------
# Handle command line arguments
#--------------------------------------------------------------------------------------------------
_USERNAME="__token__"
_TOKEN=""
_TESTREPO=$_FALSE
PTYPE="test"
#--- FUNCTION -----------------------------------------------------------------------------------
# NAME: __usage
# DESCRIPTION: Display usage information.
#--------------------------------------------------------------------------------------------------
__usage() {
cat << EOT
Usage : ${__ScriptName} [options] <publish-type>
Options:
-h Display this help
Publish types:
- pypi Publish to pypi
- test Publish to test.pypi.org
EOT
} # ---------- end of function __usage ----------
while getopts ':h' opt
do
case "${opt}" in
h ) __usage; exit 0 ;;
\?) echo "Invalid option : $OPTARG"
__usage
exit 1
;;
esac
done
shift $((OPTIND-1))
# Define publish type
if [ "$#" -gt 0 ]; then
PTYPE=$1
shift
fi
# Check publish type
if [ "$(echo "$PTYPE" | grep -E '(pypi|test)')" = "" ]; then
echo "Publish type \"$PTYPE\" is invalid..."
exit 1
fi
# Set the token
_TOKEN=$test_token # Default
_REPO="testpypi"
if [ "$(echo "$PTYPE" | grep -E '(pypi)')" = "pypi" ]; then
_TOKEN=$pypi_token
_REPO="pypi"
echo "Publishing to PYPI official..."
fi
# Upgrade pip packages
python3 -m pip install pip --upgrade
python3 -m pip install setuptools --upgrade
python3 -m pip install wheel --upgrade
python3 -m pip install twine --upgrade
python3 -m pip install build --upgrade
# build current version
# note: mkae sure to update setup.py first
python3 -m build
# command: python3 -m twine upload --username $_USERNAME --password $_TOKEN --non-interactive --repository $_REPO dist/*
python3 -m twine upload --verbose --username $_USERNAME --password $_TOKEN --non-interactive --repository $_REPO dist/*

View File

@ -4,18 +4,30 @@ with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="bits",
version="1.0.1",
name="binary-bits",
version="1.1.1",
author="S Groesz",
description="Make your bits easier to handle",
description="Provide additional methods for working with binary data",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/wolfpackmars2/bits",
packages=setuptools.find_packages(),
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python :: 3",
"License :: OSI Approved "" MIT License",
"Programming Language :: Python :: Implementation :: CPython",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
py_modules=['bits'],
package_dir={'': 'src'},
test_suite='tests',
extras_require={
'test': ['coverage'],
},
project_urls={
'Source': 'http://git.groesz.org/Groesz.org/bits/',
},
)

View File

@ -1,16 +1,218 @@
# -*- coding: utf-8 -*-
"""
============================
Binary manipulation classes
============================
Provides classes that assist in managing, comparing, transforming and working
with binary data in general.
-----------------
Provided Classes
-----------------
Bit
===
The Bit is the smallest representation of binary data and represents True or
False. This class provides methods to perform binary comparisons and
conversions between different types that can be used to represent a single
binary value.
Bits
====
A Byte is the representation of 8 Bits. The Byte class provides methods to
compare a single binary Byte to other data types. The Class also provides
binary math functions and allows getting and setting the state of individual
Bits.
Bytes
=====
The Bytes class allows an arbitrary amount of binary data to be represented.
The Bytes class provides access to each byte using the Byte class, which
further allows all the methods to be used on the binary data provided by the
Byte and Bit classes. Bytes can be compared and converted between several
different data types.
"""
class Bit:
"""
Provide a flexible bit representation
====
Bit
====
A single binary bit representation of True or False
A Bit object can be compared and converted to int, str, bool and other Bit
objects. The Bit class is backed by a single bool variable. This backing
bool object is converted as necessary to provide conversions and
comparisons with other data types. Keep this in mind as converting an
object to Bit and then attempting to convert back to the original object
may not yield the exact same value.
In the case of comparisons, the object being compared is cast as a Bit
object, and the two Bit objects are then compared with each other and the
result of the comparison is returned.
----------------
Type Conversion
----------------
The objects that can be converted to and from a Bit include *bool*, *int*,
*str* and other *Bit* objects.
Convert *bool* to *Bit*:
.. code-block:: python
/>/>/> a_bit = Bit(True)
/>/>/> a_bit
Bit(1)
/>/>/>
Convert *Bit* to *bool*:
.. code-block:: python
/>/>/> a_bool = bool(Bit(True))
/>/>/> a_bool
True
/>/>/> type(a_bool)
<class 'bool'>
/>/>/>
Convert *int* to *Bit*:
.. code-block:: python
/>/>/> unset_bit = Bit(0)
/>/>/> unset_bit
Bit(0)
/>/>/> set_bit = Bit(1)
/>/>/> set_bit
Bit(1)
/>/>/>
Convert *Bit* to *int*:
.. code-block:: python
/>/>/> my_int = int(Bit(1))
/>/>/> my_int
1
/>/>/> type(my_int)
<class 'int'>
/>/>/>
.. note:: The only values allowed for *int* are **0** and **1**
Convert *str* to *Bit*:
.. code-block:: python
/>/>/> unset_bit = Bit("0")
/>/>/> unset_bit
Bit(0)
/>/>/> set_bit = Bit("1")
/>/>/> set_bit
Bit(1)
/>/>/>
Convert *Bit* to *str*:
.. code-block: python
/>/>/> my_str = str(Bit(False))
/>/>/> my_str
'0'
/>/>/> type(my_str)
<class 'str'>
/>/>/>
.. note:: The only *str* characters allowed are a single **0** or **1**
------------
Comparisons
------------
A Bit object can be compared to any object which can be cast to a Bit
.. code-block: python
/>/>/> 0 == Bit(False) == Bit("0") == Bit(0) == False
True
/>/>/>
.. note:: Comparing an object with a value that cannot be cast to Bit will
return a ValueError.
-----------------
Rich Comparisons
-----------------
Bit objects also support "rich comparisons". For example:
.. code-block: python
/>/>/> Bit(False) < 1
True
/>/>/> Bit(True) <= 1
True
/>/>/> Bit(False) <= "1"
True
/>/>/> Bit(False) != True
True
/>/>/> Bit(True) >= False
False
/>/>/>
--------
Methods
--------
The action method `toggle` is provided for convenience.
.. code-block: python
/>/>/> Bit(False).toggle()
Bit(1)
/>/>/> Bit(True).toggle() == False
True
/>/>/> int(Bit(False).toggle())
1
/>/>/>
The `set` and `unset` methods are also provided to explicitely set the
value of Bit.
.. code-block: python
/>/>/> a_bit = Bit(0)
/>/>/> a_bit
Bit(0)
/>/>/> a_bit.set()
/>/>/> a_bit
Bit(1)
/>/>/> a_bit.unset()
/>/>/> a_bit
Bit(0)
/>/>/>
-----------
Properties
-----------
The `lie` property returns the opposite value of Bit as a *bool*. Unlike
the `toggle` method, `lie` does not change the value of the Bit.
.. code-block: python
/>/>/> Bit(1).lie
False
/>/>/>
"""
def __init__(self, var):
"""
var: Any supported type
Supported types: (bool, int, str, Bit)
bool: True or False
int: 0 = False, anything else = True
str: "0" = False, anything else = True
int: 0 = False, 1 = True
str: "0" = False, "1" = True
Bit: Bit = Bit
"""
if not isinstance(var, (bool, int, str, Bit)):
@ -28,25 +230,22 @@ class Bit:
self.__bit = var
def __str__(self):
"""
Return "0" or "1"
"""
"""Return '0' or '1'"""
return str(int(self.__bit))
def __int__(self):
"""Return 0 or 1"""
return int(self.__bit)
def __bool__(self):
"""Returns True or False"""
return self.__bit
def __repr__(self):
return f'{self.__class__.__name__}({int(self.__bit)})'
def __eq__(self, compare):
"""
compare (self) to any supported object
"""
# Massage compare to bool
"""compare (self) to any supported object"""
return bool(self) == bool(Bit(compare))
def __ne__(self, compare):
@ -68,28 +267,257 @@ class Bit:
return self.__bit
def __hash__(self):
"""
Not very useful, but provided nonetheless
"""
"""Not very useful, but provided nonetheless"""
return hash(self.__bit)
def toggle(self):
"""
(self) = Not (Self)
"""
"""Change the state of (self) to Not (self)"""
self.__bit = not self.__bit
def set(self):
"""Set Bit to True"""
self.__bit = True
def unset(self):
"""Set Bit to False"""
self.__bit = False
@property
def lie(self):
"""
Return the opposite of bit, without changing the state of (self)
"""
"""Return the opposite of bit, without changing the state of (self)"""
return not self.__bit
class Bits:
"""
Provide bit operation helpers.
=====
Bits
=====
A single byte of data with helper methods and properties.
A Bits object provides many helpful methods for working with a byte of
information. The backing object is a single int, from which all operations
and conversions are performed. A Bits object can be converted and compared
to *bytes*, *int*, *str*, *list(of Bit)*, *bool* and other *Bits* objects.
The custom object *Bytes* can also be cast to a Bits object, as long as the
*Bytes* object has an *int* value >= **0** and <= **255**.
When comparing or operating on *Bits* objects with objects of a different
type, the *operand* object will first be cast to a Bits object. In this
way, any object which can be used to create a *Bits* object can also be
used to perform operations with *Bits*.
----------------
Type Conversion
----------------
The objects that can be converted to and from *Bits* include *bytes*,
*int*, *str*, *list*, *bool* and other *Bits* objects. When using a list,
the length of the list must be 8 or less, each list item must be castable
to *int*, and each item will be evaluated as either *True* or *False*.
Convert *bool* to *Bits*:
.. code-block: python
/>/>/> my_bits = Bits(True)
/>/>/> my_bits
Bits(1)
/>/>/>
Convert *bytes* to *Bits*:
.. code-block: python
/>/>/> my_bits = Bits(b'\\xab')
/>/>/> my_bits
Bits(171)
/>/>/> my_bits = Bits(bytes([210]))
/>/>/> my_bits
Bits(210)
/>/>/>
Convert *int* to *Bits*:
.. code-block: python
/>/>/> my_bits = Bits(234)
/>/>/> my_bits
Bits(234)
/>/>/>
Convert *str* to *Bits*:
.. code-block: python
/>/>/> my_bits = Bits("01010101")
/>/>/> my_bits
Bits(85)
/>/>/> my_bits = Bits("11")
/>/>/> my_bits
Bits(3)
/>/>/>
Convert *list* to *Bits*:
.. code-block: python
/>/>/> my_bits = Bits(["0", "1", "0", "1", "0", "1", "0", "1"])
/>/>/> my_bits
Bits(85)
/>/>/> my_bits = Bits([0, 1, 0, 1, 0, 1, 0, 1])
/>/>/> my_bits
Bits(85)
/>/>/> my_bits = Bits([False, True, False, True, False, True])
/>/>/> my_bits
Bits(21)
/>/>/> my_bits = Bits([True, True])
/>/>/> my_bits
Bits(3)
/>/>/> my_bits = Bits([0, "1", False, True, "0", Bit(1), Bit(0), 127])
/>/>/> my_bits
Bits(85)
.. note:: Each item in a list will be evaluated to either True or False.
-------------
Type Casting
-------------
Bits objects support casting to multiple different types.
.. code-block: python
/>/>/> my_bits = Bits(16)
/>/>/> str(my_bits)
'00010000'
/>/>/> int(my_bits)
16
/>/>/> bin(my_bits)
'0b10000'
/>/>/> bool(my_bits)
True
/>/>/> list(my_bits)
[Bit(0), Bit(0), Bit(0), Bit(1), Bit(0), Bit(0), Bit(0), Bit(0)]
/>/>/> bytes(my_bits)
b'\x10'
/>/>/> bytearray([my_bits])
bytearray(b'\x10')
/>/>/>
-------------
Subscripting
-------------
Subscripting can be used to query or modify each Bit.
.. code-block: python
/>/>/> my_bits = Bits('01101110')
/>/>/> my_bits[3]
Bit(0)
/>/>/> my_bits[3] = True
/>/>/> my_bits[3]
Bit(1)
/>/>/> str(my_bits)
'01111110')
/>/>/> list(my_bits)
[Bit(0), Bit(1), Bit(1), Bit(1), Bit(1), Bit(1), Bit(1), Bit(0)]
/>/>/>
------------
Comparisons
------------
Bits objects can be compared to any object which can be cast to a Bits
object. This goes both ways.
.. code-block: python
/>/>/> my_bits = Bits('01101110')
/>/>/> my_bits
Bits(110)
/>/>/> 110 == my_bits
True
/>/>/> 111 == my_bits
False
/>/>/> 111 > my_bits
True
/>/>/> my_bits > 112
False
/>/>/> b'n' == my_bits
True
/>/>/> my_bits >= b'#'
True
/>/>/> my_bits >= '10000000'
False
/>/>/> '10000000' >= my_bits
True
/>/>/>
--------------------
Bit-Masks and Flags
--------------------
Bits objects support bitmask and flag operations.
.. code-block: python
/>/>/> my_bits = Bits('01101110')
/>/>/> my_bits
Bits(110)
/>/>/> 110 in my_bits
True
/>/>/> Bits('00000010') in my_bits
True
/>/>/> pow_2 = [0, 1, 2, 4, 8, 16, 32, 64, 128]
/>/>/> for p2 in pow_2:
... print(str(Bits(p2)))
...
00000000
00000001
00000010
00000100
00001000
00010000
00100000
01000000
10000000
/>/>/> for p2 in pow_2:
... print(str(p2) + ' in Bits(110): ' + p2 in my_bits)
...
0 in Bits(110): True
1 in Bits(110): False
2 in Bits(110): True
4 in Bits(110): True
8 in Bits(110): True
16 in Bits(110): False
32 in Bits(110): True
64 in Bits(110): True
128 in Bits(110): False
/>/>/> str(Bits(14))
'00001110'
/>/>/> 14 in my_bits
True
/>/>/> (0 + 2 + 4 + 8) in my_bits
True
/>/>/>
-------------------
Bitwise operations
-------------------
Binary bitwise operations are supported.
.. code-block: python
/>/>/> my_bits = Bits('01101110')
/>/>/> my_bits
Bits(110)
/>/>/> str(my_bits & '0011')
'00000010'
/>/>/> str(my_bits >> 3)
'00001101'
/>/>/> str(my_bits << 1)
'11011100'
/>/>/> str(my_bits|'10111110')
'11111110'
/>/>/> str(my_bits^'10101010')
'11000100'
/>/>/> str(~my_bits)
'10010001'
/>/>/>
"""
def __init__(self, var=0, msb_last=False):
@ -229,6 +657,14 @@ class Bits:
def __or__(self, other):
return Bits(int(self) | int(Bits(other)))
def __invert__(self):
# There's probably a better way to do this...
inv = str(self).replace('0', 'x').replace('1', '0').replace('x', '1')
return Bits(inv)
def __neg__(self):
return ~ self
def __radd__(self, other):
return Bits(other) + self
@ -351,27 +787,18 @@ class Bits:
set the value of self to int(var)
"""
self.__value = 0
if isinstance(var, Bits):
self.__value = int(var)
elif isinstance(var, int):
if var < 0 or var > 255:
if isinstance(var, (int, bool, Bits, Bytes, Bit)):
var_i = int(var)
if var_i < 0 or var_i > 255:
raise ValueError("Integer must be between 0 and 255")
self.__value = var
self.__value = var_i
elif isinstance(var, bytes):
if len(var) == 1:
self.__value = ord(var)
self.__setvalue(ord(var))
elif len(var) > 1:
raise ValueError("bytes must be single byte with integer"
" value between 0 and 255")
else:
self.__value = 0
elif isinstance(var, Bytes):
if int(var) < 256:
self.__value = int(var)
else:
raise ValueError("Bytes object must be < 256")
return list(var)
elif (len(var) > 0) and (len(var) <=8):
elif isinstance(var, (list, str)) and (len(var) <=8):
# handles: "01010101"
# ["0", "1", "0", "1", "0", "1", "0", "1"]
# [0, 1, 0, 1, 0, 1, 0, 1]
@ -382,7 +809,7 @@ class Bits:
self.__value += (int(bool(int(var[bit]))) *
(2 ** (len(var) - 1 - bit)))
else:
raise TypeError("Expected object with len <= 8")
raise TypeError("Expected compatible object")
def bin(self, pad=True, reverse=False):
"""
@ -390,6 +817,12 @@ class Bits:
pad: True to include leading zeros, False to strip leading zeroes
reverse: True to return binary string representation of self as stiB.
"""
if self.__value == 0:
if pad:
return "0" * 8
else:
return "0"
from math import ceil
bitcount = self.__value.bit_length()
ret = ""
@ -488,13 +921,21 @@ class Bits:
ret.append(Bit(b))
return ret
def hex(self):
"""
Return the hex-string representation of self
"""
return bytes(self).hex()
class Bytes:
"""
A colletion of Bits with convenient properties for working with binary data
A collection of Bits with convenience methods for working with binary data
"""
def __init__(self, var=None, byteorder="big"):
import sys
def __init__(self, var=None, byteorder="big", bits=None):
"""
var: a supported variant (object)
byteorder: notimplemented, mostly ignored
@ -502,11 +943,17 @@ class Bytes:
self.__raw = bytearray(b'')
self.__small = False
self.__iter = None
self.__list_len = None
self.__bit_len = None
if byteorder.lower() in ["small", "little"]:
self.__small = True
if var is not None:
self.__raw = self.__to_bytearray(var)
if bits is None:
self.__bit_len = len(self.__raw)
else:
if not isinstance(bits, (int, Bytes, Bit)):
raise TypeError(f"bits argument must be int, not {type(bits)}")
self.__bit_len = int(bits)
def __bytes__(self):
"""
@ -692,6 +1139,28 @@ class Bytes:
chop += 1
return ret + bin(i)[chop:]
def hex(self, sep=None, bytes_per_sep=1):
"""
Return the hex-string representation of self
"""
hexvalue = bytes(self).hex()
if sep is None or bytes_per_sep is None or bytes_per_sep == 0:
return hexvalue
else:
if bytes_per_sep > 1:
hexvalue = hexvalue[::-1] # reverse the hex string
sep = sep[::-1]
i = 0
retvalue = ""
while i < len(hexvalue):
if i > 0:
retvalue = retvalue + sep
retvalue = retvalue + hexvalue[i:(abs(bytes_per_sep * 2) + i)]
i += abs(bytes_per_sep * 2)
if bytes_per_sep > 1:
retvalue = retvalue[::-1]
return retvalue
@property
def bytes(self):
return bytes(self)

View File

@ -1,7 +1,7 @@
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
'..')))
'../src')))
from bits import Bit
from bits import Bits

View File

@ -64,6 +64,24 @@ class TestBit(TestCase):
var.toggle()
self.assertTrue(var, "Bit(False).toggle() == True")
def test_set(self):
"""Test the set() method"""
var = Bit(False)
var.set()
self.assertTrue(var, "Bit(False).set() == True")
var = Bit(True)
var.set()
self.assertTrue(var, "Bit(True).set() == True")
def test_unset(self):
"""Test the unset() method"""
var = Bit(True)
var.unset()
self.assertFalse(var, "Bit(True).unset() == False")
var = Bit(False)
var.unset()
self.assertFalse(var, "Bit(False).unset() == False")
def test_errors(self):
"""
Test that errors are raised for invalid values

View File

@ -1,46 +1,87 @@
from unittest import TestCase
from .context import Bits, Bytes
from .context import Bit, Bits, Bytes
class TestBits(TestCase):
def setUp(self):
self.testObjects = [
{"bytes": b'\x7f',
"bits": "01111111",
"str": "01111111",
"int": 127,
"hex": "7f",
"reverse": 254,
"bitsObject": Bits(127)
"bitsObject": Bits(127),
"list": [Bit(0),
Bit(1),
Bit(1),
Bit(1),
Bit(1),
Bit(1),
Bit(1),
Bit(1)
]
},
{"bytes": b'\xcf',
"bits": "11001111",
"str": "11001111",
"int": 207,
"hex": "cf",
"reverse": 243,
"bitsObject": Bits(207)
"bitsObject": Bits(207),
"list": [1, 1, 0, 0, 1, 1, 1, 1]
},
{"bytes": b'{',
"bits": "01111011",
"str": "01111011",
"int": 123,
"hex": "7b",
"reverse": 222,
"bitsObject": Bits(123)
"bitsObject": Bits(123),
"list": ["0", "1", "1", "1", "1", "0", "1", "1"]
},
{"bytes": b'<',
"bits": "00111100",
"str": "00111100",
"int": 60,
"hex": "3c",
"reverse": 60,
"bitsObject": Bits(60)
"bitsObject": Bits(60),
"list": [False,
False,
True,
True,
True,
True,
False,
False
]
},
{"bytes": b'>',
"bits": "00111110",
"str": "00111110",
"int": 62,
"hex": "3e",
"reverse": 124,
"bitsObject": Bits(62)
"bitsObject": Bits(62),
"list": [0,
False,
"1",
1,
Bit("1"),
Bit(True),
Bit(1),
Bit(False)
]
},
{"bytes": b'=',
"str": "00111101",
"int": 61,
"hex": "3d",
"reverse": 188,
"bitsObject": Bits(61),
"list": [True, 1, 1, True, Bit(0), Bit(1)]
}
]
def test_class(self):
"""
Test various class features
"""
"""Test various class features"""
with self.subTest("Reject values > 255"):
self.assertRaises(ValueError, Bits, 256)
with self.subTest("Reject values < 0"):
@ -54,58 +95,59 @@ class TestBits(TestCase):
self.assertEqual(len(Bits(0)), 8)
def test_bytes(self):
"""
Test conversion to bytes object
"""
"""Test conversion to bytes objects"""
for testcase in self.testObjects:
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
self.assertEqual(bytes(testcase["bitsObject"]),
testcase["bytes"])
def test_int(self):
"""
Test integer conversion
"""
"""Test integer conversion"""
for testcase in self.testObjects:
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
self.assertEqual(int(testcase["bitsObject"]), testcase["int"])
def test_hex(self):
"""Test conversion to hex"""
for testcase in self.testObjects:
with self.subTest("testcase[\"hex\"]: " + str(testcase["hex"])):
self.assertEqual(testcase["bitsObject"].hex(), testcase["hex"])
def test_str(self):
"""
Test string representation
"""
"""Test string representation"""
for testcase in self.testObjects:
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
s = str(testcase["bitsObject"])
self.assertEqual(s, testcase["bits"])
self.assertEqual(s, testcase["str"])
def test_list(self):
"""Test list conversion"""
for testcase in self.testObjects:
with self.subTest("testcase[\"list\"]: " + str(testcase["list"])):
self.assertEqual(testcase["bytes"],
bytes(Bits(testcase["list"])))
def test_bits(self):
"""
Test bit representation
"""
"""Test bit representation"""
for testcase in self.testObjects:
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
self.assertEqual(testcase["bitsObject"].bin(),
testcase["bits"])
testcase["str"])
with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \
+ " [without leading zeros]"):
self.assertEqual(testcase["bitsObject"].bin(pad=False),
testcase["bits"].lstrip("0"))
testcase["str"].lstrip("0"))
def test_reverse(self):
"""
Test the reverse function changes the object bitorder and value
"""
"""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])
testcase["str"][::-1])
def test_membership_operators(self):
"""
Test the membership operator (x in y)
"""
"""Test the membership operator (x in y)"""
with self.subTest("should all be True"):
for i in range(1, 256):
self.assertTrue(i in Bits(255), f"Bits({i}) in Bits(255) fail")
@ -145,9 +187,7 @@ class TestBits(TestCase):
"Bits(80) and Bits(64) == Bits(64)")
def test_comparisons(self):
"""
Test the comparison operators
"""
"""Test the comparison operators"""
for testcase in self.testObjects:
with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \
+ " [==]"):

View File

@ -19,6 +19,8 @@ class TestBytes(TestCase):
test_list_of_ints = []
test_list_of_str = []
for i in range(0, 100):
# Generate 100 random test cases, each with max_value between
# 9 and 9,999,999,999,999,999,999,999,999 (25 9's)
max_value = int("9" * randint(1, 25))
test_value = randint(1, max_value)
bitesize = ceil(test_value.bit_length() / 8)
@ -74,7 +76,8 @@ class TestBytes(TestCase):
# print(f"\t\t{testcase}")
try:
self.assertIsInstance(eval(test), eval(compare),
f"{test} is instance of {compare}")
"{test} is instance of {compare}"
)
except:
import pdb
pdb.set_trace()
@ -91,6 +94,43 @@ class TestBytes(TestCase):
with self.assertRaises(TypeError, msg="Bytes(\"1234\")"):
Bytes("1234")
def test_hex(self):
"""
Test conversion to hex string
"""
with self.subTest("Test Bytes.hex() using random values"):
for testcase in self.testcases["bytes"]:
self.assertEqual(Bytes(testcase).hex(), testcase.hex(),
f"Bytes({testcase}).hex() == {testcase}.hex()"
)
with self.subTest("Test hex() function with known values"):
self.assertEqual(Bytes(1234).hex(), "04d2",
f"Bytes(1234).hex() == '04d2'")
self.assertEqual(Bytes(1234).hex(":"), "04:d2",
f"Bytes(1234).hex(':') == '04:d2'")
self.assertEqual(Bytes(1234).hex(":", 1), "04:d2",
f"Bytes(1234).hex(':', 1) == '04:d2'")
with self.subTest("Advanced hex() test"):
testcase = b'UUDDLRLRAB'
self.assertEqual(Bytes(testcase).hex(), testcase.hex(),
f"Bytes({testcase}).hex() == {testcase}.hex()")
tests = [['55:55:44:44:4c:52:4c:52:41:42', ":", 1],
['55:55:44:44:4c:52:4c:52:41:42', ":", -1],
['5555:44444c52:4c524142', ":", 4],
['55554444:4c524c52:4142', ":", -4],
['55 - 55 - 44 - 44 - 4c - 52 - 4c - 52 - 41 - 42', " - ",
1],
['55, 55, 44, 44, 4c, 52, 4c, 52, 41, 42', ", ", 1],
['5555, 44444c52, 4c524142', ", ", 4],
['55554444, 4c524c52, 4142', ", ", -4]
]
for subt in tests:
self.assertEqual(Bytes(testcase).hex(sep=subt[1],
bytes_per_sep=subt[2]),
subt[0],
f"Bytes({testcase}).hex(sep='{subt[1]}', " +
f"bytes_per_sep={subt[2]}) == {subt[0]}")
def test_comparison_operators(self):
"""
Test the comparison operators with Bytes objects