[master 1/1] Use libbytesize's Size class as a base for our Size class

vpodzime installerbot-noreply at redhat.com
Thu Oct 22 09:08:42 UTC 2015


From: Vratislav Podzimek <vpodzime at redhat.com>

libbytesize provides an implementation of what we need for manipulations and
calculations with sizes in bytes. It's a little bit faster and more importantly
it can be shared with other projects because it's written in C. Thus we should
use it.

At some point it would be nice if we could drop our own Size class and the
blivet/size.py module altogether because they don't bring any extra value, but
for now we need this thin wrapper to keep the API (almost*) stable. The only
real changes are:

1) Strings like "100 kibibytes" are no longer accepted, I doubt anybody has ever
used this functionality. If the opposite turns out to be true such support will
be added to libbytesize and no change will be needed in Blivet.

2) humanReadable() now ignores the 'skip' and 'min_value' attributes. The former
one is always replaced by 'True' (i.e. trailing zeroes are always stripped), the
latter one is ignored because it made things really cryptic and weird. There's
the new human_readable() method which should be used, at least by the
newly-written code.

* see the commit, mainly the part modifying/removing the tests for more details
---
 blivet/size.py     | 397 ++++++++---------------------------------------------
 python-blivet.spec |   2 +
 tests/size_test.py | 156 +++++++--------------
 3 files changed, 109 insertions(+), 446 deletions(-)

diff --git a/blivet/size.py b/blivet/size.py
index ba66dc2..4c81907 100644
--- a/blivet/size.py
+++ b/blivet/size.py
@@ -19,229 +19,20 @@
 #
 # Red Hat Author(s): David Cantrell <dcantrell at redhat.com>
 
-import re
-import string           # pylint: disable=deprecated-module
-import locale
-import sys
-from collections import namedtuple
-
-from decimal import Decimal
-from decimal import InvalidOperation
-from decimal import ROUND_DOWN, ROUND_UP, ROUND_HALF_UP
 import six
 
-from .errors import SizePlacesError
-from .i18n import _, N_
-from .util import stringize, unicodeize
-
-ROUND_DEFAULT = ROUND_HALF_UP
-
-# Container for size unit prefix information
-_Prefix = namedtuple("Prefix", ["factor", "prefix", "abbr"])
-
-_DECIMAL_FACTOR = 10 ** 3
-_BINARY_FACTOR = 2 ** 10
-
-_BYTES_SYMBOL = N_("B")
-_BYTES_WORDS = (N_("bytes"), N_("byte"))
-
-# Symbolic constants for units
-B = _Prefix(1, "", "")
-
-KB = _Prefix(_DECIMAL_FACTOR ** 1, N_("kilo"), N_("k"))
-MB = _Prefix(_DECIMAL_FACTOR ** 2, N_("mega"), N_("M"))
-GB = _Prefix(_DECIMAL_FACTOR ** 3, N_("giga"), N_("G"))
-TB = _Prefix(_DECIMAL_FACTOR ** 4, N_("tera"), N_("T"))
-PB = _Prefix(_DECIMAL_FACTOR ** 5, N_("peta"), N_("P"))
-EB = _Prefix(_DECIMAL_FACTOR ** 6, N_("exa"), N_("E"))
-ZB = _Prefix(_DECIMAL_FACTOR ** 7, N_("zetta"), N_("Z"))
-YB = _Prefix(_DECIMAL_FACTOR ** 8, N_("yotta"), N_("Y"))
-
-KiB = _Prefix(_BINARY_FACTOR ** 1, N_("kibi"), N_("Ki"))
-MiB = _Prefix(_BINARY_FACTOR ** 2, N_("mebi"), N_("Mi"))
-GiB = _Prefix(_BINARY_FACTOR ** 3, N_("gibi"), N_("Gi"))
-TiB = _Prefix(_BINARY_FACTOR ** 4, N_("tebi"), N_("Ti"))
-PiB = _Prefix(_BINARY_FACTOR ** 5, N_("pebi"), N_("Pi"))
-EiB = _Prefix(_BINARY_FACTOR ** 6, N_("exbi"), N_("Ei"))
-ZiB = _Prefix(_BINARY_FACTOR ** 7, N_("zebi"), N_("Zi"))
-YiB = _Prefix(_BINARY_FACTOR ** 8, N_("yobi"), N_("Yi"))
-
-# Categories of symbolic constants
-_DECIMAL_PREFIXES = [KB, MB, GB, TB, PB, EB, ZB, YB]
-_BINARY_PREFIXES = [KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB]
-_EMPTY_PREFIX = B
-
-if six.PY2:
-    _ASCIIlower_table = string.maketrans(string.ascii_uppercase, string.ascii_lowercase) # pylint: disable=no-member
-else:
-    _ASCIIlower_table = str.maketrans(string.ascii_uppercase, string.ascii_lowercase) # pylint: disable=no-member
-
-def _lowerASCII(s):
-    """Convert a string to lowercase using only ASCII character definitions.
-
-       :param s: string instance to convert
-       :type s: str or bytes
-       :returns: lower-cased string
-       :rtype: str
-    """
-
-    # XXX: Python 3 has str.maketrans() and bytes.maketrans() so we should
-    # ideally use one or the other depending on the type of 's'. But it turns
-    # out we expect this function to always return string even if given bytes.
-    if not six.PY2 and isinstance(s, bytes):
-        s = s.decode(sys.getdefaultencoding())
-    return s.translate(_ASCIIlower_table) # pylint: disable=no-member
-
-def _makeSpec(prefix, suffix, xlate, lowercase=True):
-    """ Synthesizes a whole word from prefix and suffix.
-
-        :param str prefix: a prefix
-        :param str suffixes: a suffix
-        :param bool xlate: if True, treat as locale specific
-        :param bool lowercase: if True, make all lowercase
-
-        :returns:  whole word
-        :rtype: str
-    """
-    if xlate:
-        word = (_(prefix) + _(suffix))
-        return word.lower() if lowercase else word
-    else:
-        word = prefix + suffix
-        return _lowerASCII(word) if lowercase else word
-
-def unitStr(unit, xlate=False):
-    """ Return a string representation of unit.
-
-        :param unit: a named unit, e.g., KiB
-        :param bool xlate: if True, translate to current locale
-        :rtype: some kind of string type
-        :returns: string representation of unit
-    """
-    return _makeSpec(unit.abbr, _BYTES_SYMBOL, xlate, lowercase=False)
-
-def parseUnits(spec, xlate):
-    """ Parse a unit specification and return corresponding factor.
-
-        :param spec: a units specifier
-        :type spec: any type of string like object
-        :param bool xlate: if True, assume locale specific
-
-        :returns: a named constant corresponding to spec, if found
-        :rtype: _Prefix or NoneType
-
-        Looks first for exact matches for a specifier, but, failing that,
-        searches for partial matches for abbreviations.
-
-        Normalizes units to lowercase, e.g., MiB and mib are treated the same.
-    """
-    if spec == "":
-        return B
-
-    if xlate:
-        spec = spec.lower()
-    else:
-        spec = _lowerASCII(spec)
-
-    # Search for complete matches
-    for unit in [_EMPTY_PREFIX] + _BINARY_PREFIXES + _DECIMAL_PREFIXES:
-        if spec == _makeSpec(unit.abbr, _BYTES_SYMBOL, xlate) or \
-           spec in (_makeSpec(unit.prefix, s, xlate) for s in _BYTES_WORDS):
-            return unit
-
-    # Search for unambiguous partial match among binary abbreviations
-    matches = [p for p in _BINARY_PREFIXES if _makeSpec(p.abbr, "", xlate).startswith(spec)]
-    if len(matches) == 1:
-        return matches[0]
-
-    return None
-
-def parseSpec(spec):
-    """ Parse string representation of size.
+from gi.repository import ByteSize
+from gi.repository.ByteSize import ROUND_DOWN, ROUND_UP
 
-        :param spec: the specification of a size with, optionally, units
-        :type spec: any type of string like object
-        :returns: numeric value of the specification in bytes
-        :rtype: Decimal
+# we just need to make these objects available here
+# pylint: disable=unused-import
+from gi.repository.ByteSize import B, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB, KB, MB, GB, TB, PB, EB, ZB, YB
 
-        :raises ValueError: if spec is unparseable
+from .errors import SizePlacesError
 
-        Tries to parse the spec first as English, if that fails, as
-        a locale specific string.
-    """
+ROUND_DEFAULT = ROUND_UP
 
-    if not spec:
-        raise ValueError("invalid size specification", spec)
-
-    # Replace the localized radix character with a .
-    radix = locale.nl_langinfo(locale.RADIXCHAR)
-    if radix != '.':
-        spec = spec.replace(radix, '.')
-
-    # The purpose of this regular expression is to distinguish
-    # between the numeric part and the part that specifies the units.
-    # The regular expression that matches the numeric part of the spec
-    # should recognize all valid numbers and should not include any part
-    # of the unit specifier in the string that it recognizes. It may
-    # recognize strings that are not valid numbers as well, if it does
-    # some other part of the implementation is expected to recognize that
-    # the number is invalid and raise an exception.
-    # Specifically, the string "0.9.9 KiB" will be matched, and the numeric
-    # part will match "0.9.9". This is not a valid number, but that will
-    # be detected when an exception is raised during conversion of the numeric
-    # part to a numeric value.
-    spec_re = re.compile(
-       r"""(?P<numeric> # the numeric part consists of three parts, below
-           (-|\+)? # optional sign character
-           (?P<base>([0-9\.]+)) # the base
-           (?P<exp>(e|E)(-|\+)[0-9]+)?) # optional exponent
-           \s* # whitespace
-           (?P<rest>[^\s]*$) # the units specification
-        """,
-        re.VERBOSE
-    )
-    m = re.match(spec_re, spec.strip())
-    if not m:
-        raise ValueError("invalid size specification", spec)
-
-    try:
-        size = Decimal(m.group('numeric'))
-    except InvalidOperation:
-        raise ValueError("invalid size specification", spec)
-
-    specifier = m.group('rest')
-
-    # First try to parse as English.
-    try:
-        if six.PY2:
-            spec_ascii = str(specifier.decode("ascii"))
-        else:
-            spec_ascii = bytes(specifier, 'ascii')
-    except (UnicodeDecodeError, UnicodeEncodeError):
-        # String contains non-ascii characters, so can not be English.
-        pass
-    else:
-        unit = parseUnits(spec_ascii, False)
-        if unit is not None:
-            return size * unit.factor
-
-    # No English match found, try localized size specs.
-    if six.PY2:
-        # pylint: disable=undefined-variable
-        if isinstance(specifier, unicode):
-            spec_local = specifier
-        else:
-            spec_local = specifier.decode("utf-8")
-    else:
-        spec_local = specifier
-
-    unit = parseUnits(spec_local, True)
-    if unit is not None:
-        return size * unit.factor
-
-    raise ValueError("invalid size specification", spec)
-
-class Size(Decimal):
+class Size(ByteSize.Size):
     """ Common class to represent storage device and filesystem sizes.
         Can handle parsing strings such as 45MB or 6.7GB to initialize
         itself, or can be initialized with a numerical size in bytes.
@@ -249,7 +40,7 @@ class Size(Decimal):
         decimal places.
     """
 
-    def __new__(cls, value=0, context=None):
+    def __new__(cls, spec=None):
         """ Initialize a new Size object.  Must pass a bytes or a spec value
             for size. The bytes value is a numerical value for the size
             this object represents, in bytes.  The spec value is a string
@@ -262,85 +53,40 @@ def __new__(cls, value=0, context=None):
                 "640 kb"
                 "640KB"
                 "640 KB"
-                "640 kilobytes"
 
             If you want to use a spec value to represent a bytes value,
             you can use the letter 'b' or 'B' or omit the size specifier.
         """
-        if isinstance(value, (six.string_types, bytes)):
-            size = parseSpec(value)
-        elif isinstance(value, (six.integer_types, float, Decimal)):
-            size = Decimal(value)
-        elif isinstance(value, Size):
-            size = Decimal(value.convertTo())
-        else:
-            raise ValueError("invalid value %s for size" % value)
-
-        # drop any partial byte
-        size = size.to_integral_value(rounding=ROUND_DOWN)
-        self = Decimal.__new__(cls, value=size, context=context)
-        return self
-
-    # Force str and unicode types since the translated sizespec may be unicode
-    def _toString(self):
-        return self.humanReadable()
-
-    def __str__(self, eng=False, context=None):
-        return stringize(self._toString())
-
-    def __unicode__(self):
-        return unicodeize(self._toString())
-
-    def __repr__(self):
-        return "Size('%s')" % self
-
-    def __deepcopy__(self, memo):
-        return Size(self.convertTo())
-
-    # pickling support for Size
-    # see https://docs.python.org/3/library/pickle.html#object.__reduce__
-    def __reduce__(self):
-        return (self.__class__, (self.convertTo(),))
+        return ByteSize.Size.__new__(cls, spec)
 
     def __add__(self, other, context=None):
-        # because float is not automatically converted to Decimal type
-        if isinstance(other, float):
-            other = Decimal(str(other))
-        return Size(Decimal.__add__(self, other))
+        return Size(ByteSize.Size.__add__(self, other))
 
     # needed to make sum() work with Size arguments
     def __radd__(self, other, context=None):
-        if isinstance(other, float):
-            other = Decimal(str(other))
-        return Size(Decimal.__radd__(self, other))
+        return Size(ByteSize.Size.__radd__(self, other))
 
     def __sub__(self, other, context=None):
-        if isinstance(other, float):
-            other = Decimal(str(other))
-        return Size(Decimal.__sub__(self, other))
+        return Size(ByteSize.Size.__sub__(self, other))
+
+    def __rsub__(self, other, context=None):
+        return Size(ByteSize.Size.__rsub__(self, other))
 
     def __mul__(self, other, context=None):
-        if isinstance(other, float):
-            other = Decimal(str(other))
-        return Size(Decimal.__mul__(self, other))
+        return Size(ByteSize.Size.__mul__(self, other))
     __rmul__ = __mul__
 
     def __div__(self, other, context=None):
-        if six.PY2:
-            # This still needs to be ignored by pylint, because it will get
-            # through the above guard.
-            return Size(Decimal.__div__(self, other))   # pylint: disable=no-member
-        else:
-            raise AttributeError
+        return Size(ByteSize.Size.__div__(self, other))   # pylint: disable=no-member
 
     def __truediv__(self, other, context=None):
-        return Size(Decimal.__truediv__(self, other))
+        return Size(ByteSize.Size.__truediv__(self, other))
 
     def __floordiv__(self, other, context=None):
-        return Size(Decimal.__floordiv__(self, other))
+        return Size(ByteSize.Size.__floordiv__(self, other))
 
     def __mod__(self, other, context=None):
-        return Size(Decimal.__mod__(self, other))
+        return Size(ByteSize.Size.__mod__(self, other))
 
     def convertTo(self, spec=None):
         """ Return the size in the units indicated by the specifier.
@@ -354,13 +100,12 @@ def convertTo(self, spec=None):
             .. versionadded:: 1.6
                spec parameter may be Size as well as units specifier.
         """
+        if isinstance(spec, Size):
+            if spec == Size(0):
+                raise ValueError("cannot convert to 0 size")
+            return ByteSize.Size.__truediv__(self, spec)
         spec = B if spec is None else spec
-        factor = Decimal(getattr(spec, "factor", spec))
-
-        if factor <= 0:
-            raise ValueError("invalid conversion unit: %s" % factor)
-
-        return Decimal(self) / factor
+        return ByteSize.Size.convert_to(self, spec)
 
     def humanReadable(self, max_places=2, strip=True, min_value=1, xlate=True):
         """ Return a string representation of this size with appropriate
@@ -372,8 +117,8 @@ def humanReadable(self, max_places=2, strip=True, min_value=1, xlate=True):
 
             :param max_places: number of decimal places to use, default is 2
             :type max_places: an integer type or NoneType
-            :param bool strip: True if trailing zeros are to be stripped.
-            :param min_value: Lower bound for value, default is 1.
+            :param bool strip: True if trailing zeros are to be stripped (see warning below).
+            :param min_value: Lower bound for value, default is 1 (see warning below).
             :type min_value: A precise numeric type: int, long, or Decimal
             :param bool xlate: If True, translate for current locale
             :returns: a representation of the size
@@ -382,78 +127,56 @@ def humanReadable(self, max_places=2, strip=True, min_value=1, xlate=True):
             If max_places is set to None, all non-zero digits will be shown.
             Otherwise, max_places digits will be shown.
 
-            If strip is True and there is a fractional quantity, trailing
-            zeros are removed up to the decimal point.
-
-            min_value sets the smallest value allowed.
-            If min_value is 10, then single digits on the lhs of
-            the decimal will be avoided if possible. In that case,
-            9216 KiB is preferred to 9 MiB. However, 1 B has no alternative.
-            If min_value is 1, however, 9 MiB is preferred.
-            If min_value is 0.1, then 0.75 GiB is preferred to 768 MiB,
-            but 0.05 GiB is still displayed as 51.2 MiB.
-
-            humanReadable() is a function that evaluates to a number which
-            represents a range of values. For a constant choice of max_places,
-            all ranges are of equal size, and are bisected by the result. So,
-            if n.humanReadable() == x U and b is the number of bytes in 1 U,
-            and e = 1/2 * 1/(10^max_places) * b, then x - e < n < x + e.
+            .. warning::
+                Since the :class:`Size` class now (since the version 1.15) uses
+                a different implementation, some of the parameters are
+                ignored. :param:`strip` is always ``True`` with trailing zeroes
+                being always stripped and :param:`min_value` is ignored
+                altogether. If you want a better control use the new
+                :method:`human_readable` method.
         """
-        if max_places is not None and (max_places < 0 or not isinstance(max_places, six.integer_types)):
-            raise SizePlacesError("max_places must be None or an non-negative integer value")
 
-        if min_value < 0 or not isinstance(min_value, (six.integer_types, Decimal)):
-            raise ValueError("min_value must be a precise positive numeric value.")
-
-        # Find the smallest prefix which will allow a number less than
-        # _BINARY_FACTOR * min_value to the left of the decimal point.
-        # If the number is so large that no prefix will satisfy this
-        # requirement use the largest prefix.
-        limit = _BINARY_FACTOR * min_value
-        for unit in [_EMPTY_PREFIX] + _BINARY_PREFIXES:
-            newcheck = self.convertTo(unit)
+        if max_places is not None and (max_places < -1 or not isinstance(max_places, six.integer_types)):
+            raise SizePlacesError("max_places must be None or an non-negative integer value")
 
-            if abs(newcheck) < limit:
-                break
+        if max_places is None:
+            max_places = -1
 
-        if max_places is not None:
-            newcheck = newcheck.quantize(Decimal(10) ** -max_places)
+        return self.human_readable(max_places=max_places, xlate=xlate)
 
-        retval_str = str(newcheck)
+    def human_readable(self, min_unit=ByteSize.Bunit.B, max_places=2, xlate=True):
+        """ Get a human-readable representation of @size.
 
-        if '.' in retval_str and strip:
-            retval_str = retval_str.rstrip("0").rstrip(".")
+            :param min_unit: the smallest unit the returned representation should use
+            :type min_unit: unit specification (B, KiB, KB, MiB, MB,...)
+            :param int max_places: maximum number of decimal places the representation should use
+            :param bool xlate: whether to try to translate the representation or not
+            :returns: a string which is human-readable representation of this size
+            :rtype: str
 
-        if xlate:
-            radix = locale.nl_langinfo(locale.RADIXCHAR)
-            if radix != '.':
-                retval_str = retval_str.replace('.', radix)
+        """
 
-        # pylint: disable=undefined-loop-variable
-        return retval_str + " " + _makeSpec(unit.abbr, _BYTES_SYMBOL, xlate, lowercase=False)
+        return ByteSize.Size.human_readable(self, min_unit, max_places, xlate)
 
-    def roundToNearest(self, unit, rounding=ROUND_DEFAULT):
+    def roundToNearest(self, size, rounding=ROUND_DEFAULT):
         """ Rounds to nearest unit specified as a named constant or a Size.
 
-            :param unit: a unit specifier
-            :type unit: a named constant like KiB, or any non-negative Size
+            :param size: a size specifier
+            :type size: a named constant like KiB, or any non-negative Size
             :keyword rounding: which direction to round
             :type rounding: one of ROUND_UP, ROUND_DOWN, or ROUND_DEFAULT
             :returns: Size rounded to nearest whole specified unit
             :rtype: :class:`Size`
 
-            If unit is Size(0), returns Size(0).
+            If size is Size(0), returns Size(0).
         """
         if rounding not in (ROUND_UP, ROUND_DOWN, ROUND_DEFAULT):
             raise ValueError("invalid rounding specifier")
 
-        factor = Decimal(getattr(unit, "factor", unit))
-
-        if factor == 0:
-            return Size(0)
-
-        if factor < 0:
-            raise ValueError("invalid rounding unit: %s" % factor)
+        if isinstance(size, Size):
+            if size.get_bytes() == 0:
+                return Size(0)
+            elif size < Size(0):
+                raise ValueError("invalid rounding size: %s" % size)
 
-        rounded = (Decimal(self) / factor).to_integral_value(rounding=rounding)
-        return Size(rounded * factor)
+        return Size(ByteSize.Size.round_to_nearest(self, size, rounding))
diff --git a/python-blivet.spec b/python-blivet.spec
index 7a2f4e3..8519740 100644
--- a/python-blivet.spec
+++ b/python-blivet.spec
@@ -41,6 +41,7 @@ Requires: libblockdev-plugins-all >= %{libblockdevver}
 Requires: libselinux-python
 Requires: python-hawkey
 Requires: pygobject3-base
+Requires: python-bytesize
 Requires: %{realname}-data = %{epoch}:%{version}-%{release}
 
 %description
@@ -71,6 +72,7 @@ Requires: e2fsprogs >= %{e2fsver}
 Requires: lsof
 Requires: python3-hawkey
 Requires: python3-gobject-base
+Requires: python3-bytesize
 Requires: %{realname}-data = %{epoch}:%{version}-%{release}
 
 %description -n python3-%{realname}
diff --git a/tests/size_test.py b/tests/size_test.py
index cf076d2..db3518f 100644
--- a/tests/size_test.py
+++ b/tests/size_test.py
@@ -35,7 +35,7 @@
 from blivet.i18n import _
 from blivet.errors import SizePlacesError
 from blivet import size
-from blivet.size import Size, _EMPTY_PREFIX, _BINARY_PREFIXES, _DECIMAL_PREFIXES
+from blivet.size import Size
 from blivet.size import B, KiB, MiB, GiB, TiB
 
 class SizeTestCase(unittest.TestCase):
@@ -46,37 +46,10 @@ def testExceptions(self):
 
         s = Size(500)
         with self.assertRaises(SizePlacesError):
-            s.humanReadable(max_places=-1)
+            s.humanReadable(max_places=-2)
 
         self.assertEqual(s.humanReadable(max_places=0), "500 B")
 
-    def _prefixTestHelper(self, numunits, unit):
-        """ Test that units and prefix or abbreviation agree.
-
-            :param int numunits: this value times factor yields number of bytes
-            :param unit: a unit specifier
-        """
-        c = numunits * unit.factor
-
-        s = Size(c)
-        self.assertEqual(s, Size(c))
-
-        u = size._makeSpec(unit.prefix, size._BYTES_WORDS[0], False)
-        s = Size("%ld %s" % (numunits, u))
-        self.assertEqual(s, c)
-        self.assertEqual(s.convertTo(unit), numunits)
-
-        u = size._makeSpec(unit.abbr, size._BYTES_SYMBOL, False)
-        s = Size("%ld %s" % (numunits, u))
-        self.assertEqual(s, c)
-        self.assertEqual(s.convertTo(unit), numunits)
-
-    def testPrefixes(self):
-        numbytes = 47
-
-        for unit in [_EMPTY_PREFIX] + _BINARY_PREFIXES + _DECIMAL_PREFIXES:
-            self._prefixTestHelper(numbytes, unit)
-
     def testHumanReadable(self):
         s = Size(58929971)
         self.assertEqual(s.humanReadable(), "56.2 MiB")
@@ -93,15 +66,6 @@ def testHumanReadable(self):
         s = Size("300 MiB")
         self.assertEqual(s.humanReadable(max_places=2), "300 MiB")
 
-        # when min_value is 10 and single digit on left of decimal, display
-        # with smaller unit.
-        s = Size("9.68 TiB")
-        self.assertEqual(s.humanReadable(max_places=2, min_value=10), "9912.32 GiB")
-        s = Size("4.29 MiB")
-        self.assertEqual(s.humanReadable(max_places=2, min_value=10), "4392.96 KiB")
-        s = Size("7.18 KiB")
-        self.assertEqual(s.humanReadable(max_places=2, min_value=10), "7352 B")
-
         # rounding should work with max_places limitted
         s = Size("12.687 TiB")
         self.assertEqual(s.humanReadable(max_places=2), "12.69 TiB")
@@ -113,8 +77,6 @@ def testHumanReadable(self):
         # byte values close to multiples of 2 are shown without trailing zeros
         s = Size(0xff)
         self.assertEqual(s.humanReadable(max_places=2), "255 B")
-        s = Size(8193)
-        self.assertEqual(s.humanReadable(max_places=2, min_value=10), "8193 B")
 
         # a fractional quantity is shown if the value deviates
         # from the whole number of units by more than 1%
@@ -123,7 +85,7 @@ def testHumanReadable(self):
 
         # if max_places is set to None, all digits are displayed
         s = Size(0xfffffffffffff)
-        self.assertEqual(s.humanReadable(max_places=None), "3.9999999999999991118215803 PiB")
+        self.assertEqual(s.humanReadable(max_places=None), "3.99999999999999911182158029987476766109466552734375 PiB")
         s = Size(0x10000)
         self.assertEqual(s.humanReadable(max_places=None), "64 KiB")
         s = Size(0x10001)
@@ -134,16 +96,12 @@ def testHumanReadable(self):
         self.assertEqual(s.humanReadable(max_places=2), "1024 YiB")
         s = Size(1024**9 - 1)
         self.assertEqual(s.humanReadable(max_places=2), "1024 YiB")
-        s = Size(1024**9 + 1)
-        self.assertEqual(s.humanReadable(max_places=2, strip=False), "1024.00 YiB")
         s = Size(1024**10)
         self.assertEqual(s.humanReadable(max_places=2), "1048576 YiB")
 
     def testHumanReadableFractionalQuantities(self):
         s = Size(0xfffffffffffff)
         self.assertEqual(s.humanReadable(max_places=2), "4 PiB")
-        s = Size(0xfffff)
-        self.assertEqual(s.humanReadable(max_places=2, strip=False), "1024.00 KiB")
         s = Size(0xffff)
         # value is not exactly 64 KiB, but w/ 2 places, value is 64.00 KiB
         # so the trailing 0s are stripped.
@@ -162,16 +120,6 @@ def testHumanReadableFractionalQuantities(self):
         self.assertEqual(s.humanReadable(max_places=2), "4 PiB")
 
 
-    def testMinValue(self):
-        s = Size("9 MiB")
-        self.assertEqual(s.humanReadable(), "9 MiB")
-        self.assertEqual(s.humanReadable(min_value=10), "9216 KiB")
-
-        s = Size("0.5 GiB")
-        self.assertEqual(s.humanReadable(max_places=2, min_value=1), "512 MiB")
-        self.assertEqual(s.humanReadable(max_places=2, min_value=Decimal("0.1")), "0.5 GiB")
-        self.assertEqual(s.humanReadable(max_places=2, min_value=Decimal(1)), "512 MiB")
-
     def testConvertToPrecision(self):
         s = Size(1835008)
         self.assertEqual(s.convertTo(None), 1835008)
@@ -203,79 +151,81 @@ def testNoUnitsInString(self):
         self.assertEqual(Size("1024"), Size("1 KiB"))
 
     def testScientificNotation(self):
-        self.assertEqual(size.parseSpec("1e+0 KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec("1e-0 KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec("1e-1 KB"), Decimal(100))
-        self.assertEqual(size.parseSpec("1E-4KB"), Decimal("0.1"))
+        self.assertEqual(Size("1e+0 KiB"), Decimal(1024))
+        self.assertEqual(Size("1e-0 KiB"), Decimal(1024))
+        self.assertEqual(Size("1e-1 KB"), Decimal(100))
+        self.assertEqual(Size("1E-4KB"), Decimal("0.1"))
         self.assertEqual(Size("1E-10KB"), Size(0))
 
         with self.assertRaises(ValueError):
             # this is an exponent w/out a base
-            size.parseSpec("e+0")
+            Size("e+0")
 
     def testFloatingPointStr(self):
-        self.assertEqual(size.parseSpec("1.5e+0 KiB"), Decimal(1536))
-        self.assertEqual(size.parseSpec("0.0"), Decimal(0))
-        self.assertEqual(size.parseSpec("0.9 KiB"), Decimal("921.6"))
-        self.assertEqual(size.parseSpec("1.5 KiB"), Decimal(1536))
-        self.assertEqual(size.parseSpec("0.5 KiB"), Decimal(512))
-        self.assertEqual(size.parseSpec(".5 KiB"), Decimal(512))
-        self.assertEqual(size.parseSpec("1. KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec("-1. KiB"), Decimal(-1024))
-        self.assertEqual(size.parseSpec("+1. KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec("+1.0000000e+0 KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec("+.5 KiB"), Decimal(512))
+        self.assertEqual(Size("1.5e+0 KiB"), Decimal(1536))
+        self.assertEqual(Size("0.0"), Decimal(0))
+        self.assertEqual(Size("0.9 KiB"), Decimal("921.6"))
+        self.assertEqual(Size("1.5 KiB"), Decimal(1536))
+        self.assertEqual(Size("0.5 KiB"), Decimal(512))
+        self.assertEqual(Size(".5 KiB"), Decimal(512))
+        self.assertEqual(Size("1. KiB"), Decimal(1024))
+        self.assertEqual(Size("-1. KiB"), Decimal(-1024))
+        self.assertEqual(Size("+1. KiB"), Decimal(1024))
+        self.assertEqual(Size("+1.0000000e+0 KiB"), Decimal(1024))
+        self.assertEqual(Size("+.5 KiB"), Decimal(512))
 
         with self.assertRaises(ValueError):
             # this is a fragment of an arithmetic expression
-            size.parseSpec("+ 1 KiB")
+            Size("+ 1 KiB")
 
         with self.assertRaises(ValueError):
             # this is a fragment of an arithmetic expression
-            size.parseSpec("- 1 KiB")
+            Size("- 1 KiB")
 
         with self.assertRaises(ValueError):
             # this is a lonely .
-            size.parseSpec(". KiB")
+            Size(". KiB")
 
         with self.assertRaises(ValueError):
             # this has a fragmentary exponent
-            size.parseSpec("1.0e+ KiB")
+            Size("1.0e+ KiB")
 
         with self.assertRaises(ValueError):
             # this is a version string, not a number
-            size.parseSpec("1.0.0")
+            Size("1.0.0")
 
     def testWhiteSpace(self):
-        self.assertEqual(size.parseSpec("1 KiB "), Decimal(1024))
-        self.assertEqual(size.parseSpec(" 1 KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec(" 1KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec(" 1e+0KiB"), Decimal(1024))
+        self.assertEqual(Size("1 KiB "), Decimal(1024))
+        self.assertEqual(Size(" 1 KiB"), Decimal(1024))
+        self.assertEqual(Size(" 1KiB"), Decimal(1024))
+        self.assertEqual(Size(" 1e+0KiB"), Decimal(1024))
         with self.assertRaises(ValueError):
-            size.parseSpec("1 KiB just a lot of stray characters")
+            Size("1 KiB just a lot of stray characters")
         with self.assertRaises(ValueError):
-            size.parseSpec("just 1 KiB")
+            Size("just 1 KiB")
 
     def testLeadingZero(self):
-        self.assertEqual(size.parseSpec("001 KiB"), Decimal(1024))
-        self.assertEqual(size.parseSpec("1e+01"), Decimal(10))
+        self.assertEqual(Size("001 KiB"), Decimal(1024))
+        self.assertEqual(Size("1e+01"), Decimal(10))
 
     def testPickling(self):
         s = Size("10 MiB")
         self.assertEqual(s, cPickle.loads(cPickle.dumps(s)))
 
+
+# es_ES uses latin-characters but a comma as the radix separator
+# kk_KZ uses non-latin characters and is case-sensitive
+# ml_IN uses a lot of non-letter modifier characters
+# fa_IR uses non-ascii digits, or would if python supported that, but
+#       you know, just in case
+TEST_LANGS = ["es_ES.UTF-8", "kk_KZ.UTF-8", "ml_IN.UTF-8", "fa_IR.UTF-8"]
+LANGS_AVAILABLE = all(os.path.exists("/usr/share/locale/%s/LC_MESSAGES/libbytesize.mo" % lang.split("_")[0]) for lang in TEST_LANGS)
+ at unittest.skipUnless(LANGS_AVAILABLE, "libbytesize's translations are not available, cannot test now")
 class TranslationTestCase(unittest.TestCase):
 
     def __init__(self, methodName='runTest'):
         super(TranslationTestCase, self).__init__(methodName=methodName)
 
-        # es_ES uses latin-characters but a comma as the radix separator
-        # kk_KZ uses non-latin characters and is case-sensitive
-        # ml_IN uses a lot of non-letter modifier characters
-        # fa_IR uses non-ascii digits, or would if python supported that, but
-        #       you know, just in case
-        self.TEST_LANGS = ["es_ES.UTF-8", "kk_KZ.UTF-8", "ml_IN.UTF-8", "fa_IR.UTF-8"]
-
     def setUp(self):
         self.saved_lang = os.environ.get('LANG', None)
 
@@ -285,7 +235,7 @@ def tearDown(self):
 
     def testMakeSpec(self):
         """ Tests for _makeSpecs(). """
-        for lang in  self.TEST_LANGS:
+        for lang in  TEST_LANGS:
             os.environ['LANG'] = lang
             locale.setlocale(locale.LC_ALL, '')
 
@@ -307,7 +257,7 @@ def testMakeSpec(self):
 
     def testParseSpec(self):
         """ Tests for parseSpec(). """
-        for lang in  self.TEST_LANGS:
+        for lang in  TEST_LANGS:
             os.environ['LANG'] = lang
             locale.setlocale(locale.LC_ALL, '')
 
@@ -337,7 +287,7 @@ def testParseSpec(self):
 
     def testTranslated(self):
         s = Size("56.19 MiB")
-        for lang in  self.TEST_LANGS:
+        for lang in  TEST_LANGS:
             os.environ['LANG'] = lang
             locale.setlocale(locale.LC_ALL, '')
 
@@ -365,7 +315,7 @@ def testTranslated(self):
     def testHumanReadableTranslation(self):
         s = Size("56.19 MiB")
         size_str = s.humanReadable()
-        for lang in self.TEST_LANGS:
+        for lang in TEST_LANGS:
 
             os.environ['LANG'] = lang
             locale.setlocale(locale.LC_ALL, '')
@@ -425,20 +375,13 @@ def testRoundToNearest(self):
 
 class UtilityMethodsTestCase(unittest.TestCase):
 
-    def testLowerASCII(self):
-        """ Tests for _lowerASCII. """
-        self.assertEqual(size._lowerASCII(""), "")
-        self.assertEqual(size._lowerASCII("B"), "b")
-
     def testArithmetic(self):
         s = Size("2GiB")
 
         # Make sure arithmatic operations with Size always result in the expected type
         self.assertIsInstance(s+s, Size)
         self.assertIsInstance(s-s, Size)
-        self.assertIsInstance(s*s, Size)
         self.assertIsInstance(s/s, Size)
-        self.assertIsInstance(s**Size(2), Decimal)
         self.assertIsInstance(s % Size(7), Size)
 
 
@@ -448,13 +391,8 @@ def testArithmetic(self):
         self.assertIsInstance(s*2, Size)
         self.assertIsInstance(s/2, Size)
         self.assertIsInstance(s//2, Size)
-        self.assertIsInstance(s**2, Decimal)
-        self.assertIsInstance(s % 127, Size)
+        self.assertIsInstance(s % Size(127), Size)
 
         # Make sure operations with non-Size on the left result in the expected type
         self.assertIsInstance(2+s, Size)
-        self.assertIsInstance(2-s, Decimal)
-        self.assertIsInstance(2*s, Size)
-        self.assertIsInstance(2/s, Decimal)
-        self.assertIsInstance(2**Size(2), Decimal)
-        self.assertIsInstance(1024 % Size(127), Decimal)
+        self.assertIsInstance(2-s, Size)


-- 
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/6d293b5615a1b58671a8b958dde0a33d1ec00b21


More information about the anaconda-patches mailing list