Merge pull request #210 from CanonicalLtd/mwhudson/dehumanize_size-sanity

support lower case suffixes in dehumanize_size
This commit is contained in:
Michael Hudson-Doyle 2017-04-04 10:58:37 +12:00 committed by GitHub
commit eea4240bac
9 changed files with 121 additions and 47 deletions

View File

@ -23,7 +23,7 @@ from subiquitycore.ui.error import ErrorView
from subiquity.curtin import (curtin_write_storage_actions,
curtin_write_preserved_actions)
from subiquity.models import (FilesystemModel, RaidModel)
from subiquity.models.filesystem import _humanize_size
from subiquity.models.filesystem import humanize_size
from subiquity.ui.views import (DiskPartitionView, AddPartitionView,
AddFormatView, FilesystemView,
DiskInfoView, RaidView, BcacheView,
@ -301,7 +301,7 @@ class FilesystemController(BaseController):
'model': disk.model,
'serial': disk.serial,
'size': disk.size,
'humansize': _humanize_size(disk.size),
'humansize': humanize_size(disk.size),
'vendor': disk._info.vendor,
'rotational': 'true' if rotational == '1' else 'false',
}

View File

@ -33,7 +33,7 @@ class FS:
is_mounted = attr.ib()
def _humanize_size(size):
def humanize_size(size):
size = abs(size)
if size == 0:
return "0B"
@ -41,33 +41,44 @@ def _humanize_size(size):
return "%.3f%s" % (size / math.pow(1024, p), HUMAN_UNITS[int(p)])
def _dehumanize_size(size):
def dehumanize_size(size):
# convert human 'size' to integer
size_in = size
if size.endswith("B"):
if not size:
raise ValueError("input cannot be empty")
if not size[-1].isdigit():
suffix = size[-1].upper()
size = size[:-1]
else:
suffix = None
# build mpliers based on HUMAN_UNITS
mpliers = {}
for (unit, exponent) in zip(HUMAN_UNITS, range(0, len(HUMAN_UNITS))):
mpliers[unit] = 2 ** (exponent * 10)
num = size
mplier = 'B'
for m in mpliers:
if size.endswith(m):
mplier = m
num = size[0:-len(m)]
parts = size.split('.')
if len(parts) > 2:
raise ValueError("{!r} is not valid input".format(size_in))
elif len(parts) == 2:
div = 10**len(parts[1])
size = parts[0] + parts[1]
else:
div = 1
try:
num = float(num)
num = int(size)
except ValueError:
raise ValueError("'{}' is not valid input.".format(size_in))
raise ValueError("{!r} is not valid input".format(size_in))
if suffix is not None:
if suffix not in HUMAN_UNITS:
raise ValueError("unrecognized suffix {!r} in {!r}".format(size_in[-1], size_in))
mult = 2**(10*HUMAN_UNITS.index(suffix))
else:
mult = 1
if num < 0:
raise ValueError("'{}': cannot be negative".format(size_in))
raise ValueError("{!r}: cannot be negative".format(size_in))
return int(num * mpliers[mplier])
return num * mult // div
def id_factory(name):

View File

@ -0,0 +1,64 @@
import unittest
from subiquity.models.filesystem import dehumanize_size
class TestDehumanizeSize(unittest.TestCase):
basics = [
('1', 1),
('134', 134),
('0.5B', 0), # Does it make sense to allow this?
('1B', 1),
('1K', 2**10),
('1k', 2**10),
('0.5K', 2**9),
('2.125K', 2**11 + 2**7),
('1M', 2**20),
('1m', 2**20),
('0.5M', 2**19),
('2.125M', 2**21 + 2**17),
('1G', 2**30),
('1g', 2**30),
('0.25G', 2**28),
('2.5G', 2**31 + 2**29),
('1T', 2**40),
('1t', 2**40),
('4T', 2**42),
('4.125T', 2**42 + 2**37),
('1P', 2**50),
('1P', 2**50),
('0.5P', 2**49),
('1.5P', 2**50 + 2**49),
]
def test_basics(self):
for input, expected_output in self.basics:
with self.subTest(input=input):
self.assertEqual(expected_output, dehumanize_size(input))
errors = [
('', "input cannot be empty"),
('1u', "unrecognized suffix 'u' in '1u'"),
('-1', "'-1': cannot be negative"),
('1.1.1', "'1.1.1' is not valid input"),
('1rm', "'1rm' is not valid input"),
('1e6M', "'1e6M' is not valid input"),
]
def test_errors(self):
for input, expected_error in self.errors:
with self.subTest(input=input):
try:
dehumanize_size(input)
except ValueError as e:
actual_error = str(e)
else:
self.fail("dehumanize_size({!r}) did not error".format(input))
self.assertEqual(expected_error, actual_error)

View File

@ -22,7 +22,7 @@ from subiquitycore.ui.container import ListBox, Pile
from subiquitycore.ui.interactive import Selector
from subiquitycore.ui.utils import Color, Padding
from subiquity.models.filesystem import _humanize_size
from subiquity.models.filesystem import humanize_size
log = logging.getLogger('subiquity.ui.bcache')
@ -103,7 +103,7 @@ class BcacheView(BaseView):
else:
bcachedev = device
disk_sz = _humanize_size(bcachedev.size)
disk_sz = humanize_size(bcachedev.size)
disk_string = "{} {}, {}".format(dname,
disk_sz,
device.model)

View File

@ -36,8 +36,8 @@ from subiquitycore.ui.interactive import Selector
from subiquitycore.view import BaseView
from subiquity.models.filesystem import (
_humanize_size,
_dehumanize_size,
humanize_size,
dehumanize_size,
HUMAN_UNITS,
)
from subiquity.ui.mount import MountField
@ -56,7 +56,7 @@ class AddPartitionForm(Form):
def __init__(self, model, disk):
self.model = model
self.disk = disk
self.size_str = _humanize_size(disk.free)
self.size_str = humanize_size(disk.free)
super().__init__()
self.size.caption = "Size (max {})".format(self.size_str)
self.partnum.value = self.disk.next_partnum
@ -74,16 +74,15 @@ class AddPartitionForm(Form):
v = self.size.value
if not v:
return
r = '(\d+[\.]?\d*)([{}])?$'.format(''.join(HUMAN_UNITS))
match = re.match(r, v)
if not match:
return "Invalid partition size"
unit = match.group(2)
if unit is None:
suffixes = ''.join(HUMAN_UNITS) + ''.join(HUMAN_UNITS).lower()
if v[-1] not in suffixes:
unit = self.size_str[-1]
v += unit
self.size.value = v
sz = _dehumanize_size(v)
try:
sz = dehumanize_size(v)
except ValueError as v:
return str(v)
if sz > self.disk.free:
self.size.value = self.size_str
self.size.show_extra(Color.info_minor(Text("Capped partition size at %s"%(self.size_str,), align="center")))
@ -128,7 +127,7 @@ class AddPartitionView(BaseView):
mount = None
if self.form.size.value:
size = _dehumanize_size(self.form.size.value)
size = dehumanize_size(self.form.size.value)
if size > self.disk.free:
size = self.disk.free
else:

View File

@ -22,7 +22,7 @@ from subiquitycore.ui.container import Columns, ListBox, Pile
from subiquitycore.ui.utils import Padding, Color
from subiquitycore.view import BaseView
from subiquity.models.filesystem import _humanize_size
from subiquity.models.filesystem import humanize_size
log = logging.getLogger('subiquity.ui.filesystem.disk_partition')
@ -58,7 +58,7 @@ class DiskPartitionView(BaseView):
def format_volume(part):
path = part.path
size = _humanize_size(part.size)
size = humanize_size(part.size)
if part.fs() is None:
fstype = '-'
mountpoint = '-'
@ -80,7 +80,7 @@ class DiskPartitionView(BaseView):
for part in self.disk.partitions():
partitioned_disks.append(format_volume(part))
if self.disk.free > 0:
free_space = _humanize_size(self.disk.free)
free_space = humanize_size(self.disk.free)
partitioned_disks.append(Columns([
(15, Text("FREE SPACE")),
Text(free_space),
@ -134,7 +134,7 @@ class DiskPartitionView(BaseView):
text = "Add first partition"
if len(self.disk.partitions()) > 0:
text = "Add partition (max size {})".format(
_humanize_size(self.disk.free))
humanize_size(self.disk.free))
return Color.menu_button(
menu_btn(label=text, on_press=self.add_partition))

View File

@ -39,7 +39,7 @@ from subiquitycore.ui.container import Columns, ListBox, Pile
from subiquitycore.ui.utils import Padding, Color
from subiquitycore.view import BaseView
from subiquity.models.filesystem import _humanize_size
from subiquity.models.filesystem import humanize_size
log = logging.getLogger('subiquity.ui.filesystem.filesystem')
@ -105,10 +105,10 @@ class FilesystemView(BaseView):
log.debug('FileSystemView: building part list')
cols = []
for m in self.model._mounts:
cols.append((m.device.volume.path, _humanize_size(m.device.volume.size), m.device.fstype, m.path))
cols.append((m.device.volume.path, humanize_size(m.device.volume.size), m.device.fstype, m.path))
for fs in self.model._filesystems:
if fs.fstype == 'swap':
cols.append((fs.volume.path, _humanize_size(fs.volume.size), fs.fstype, 'SWAP'))
cols.append((fs.volume.path, humanize_size(fs.volume.size), fs.fstype, 'SWAP'))
if len(cols) == 0:
return Pile([Color.info_minor(
@ -146,14 +146,14 @@ class FilesystemView(BaseView):
disk_btn = menu_btn(label=disk.path)
connect_signal(disk_btn, 'click', self.click_disk, disk)
col1 = Color.menu_button(disk_btn)
col2 = Text(_humanize_size(disk.size))
col2 = Text(humanize_size(disk.size))
if disk.used > 0:
size = disk.size
free = disk.free
percent = int(100*free/size)
if percent == 0:
continue
col3 = Text("local disk, {} ({}%) free".format(_humanize_size(free), percent))
col3 = Text("local disk, {} ({}%) free".format(humanize_size(free), percent))
else:
col3 = Text("local disk")
col(col1, col2, col3)
@ -166,7 +166,7 @@ class FilesystemView(BaseView):
fs = partition.fs().fstype
else:
fs = "unformatted"
col2 = Text(_humanize_size(partition.size))
col2 = Text(humanize_size(partition.size))
col3 = Text("{} partition on local disk".format(fs))
col(col1, col2, col3)

View File

@ -22,7 +22,7 @@ from subiquitycore.ui.container import Columns, ListBox, Pile
from subiquitycore.ui.interactive import UsernameEditor
from subiquitycore.ui.utils import Color, Padding
from subiquity.models.filesystem import _humanize_size
from subiquity.models.filesystem import humanize_size
log = logging.getLogger('subiquitycore.ui.lvm')
@ -65,7 +65,7 @@ class LVMVolumeGroupView(BaseView):
else:
lvmdev = device
disk_sz = _humanize_size(lvmdev.size)
disk_sz = humanize_size(lvmdev.size)
disk_string = "{} {}, {}".format(dname,
disk_sz,
device.model)

View File

@ -23,7 +23,7 @@ from subiquitycore.ui.interactive import (StringEditor, IntegerEditor,
Selector)
from subiquitycore.ui.utils import Color, Padding
from subiquity.models.filesystem import _humanize_size
from subiquity.models.filesystem import humanize_size
log = logging.getLogger('subiquity.ui.raid')
@ -67,7 +67,7 @@ class RaidView(BaseView):
else:
raiddev = device
disk_sz = _humanize_size(raiddev.size)
disk_sz = humanize_size(raiddev.size)
disk_string = "{} {}, {}".format(dname,
disk_sz,
device.model)