From de390b1ca6e5411f91dd2efb6e88fa5c604236cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Tue, 20 Oct 2015 12:31:06 -0400 Subject: [PATCH 1/5] add file size tests, failing the tests are failing because 1KB gets displayed as 1000 bytes, but also because higher prefixes are missing --- borg/testsuite/helpers.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/borg/testsuite/helpers.py b/borg/testsuite/helpers.py index 9f7400bcd..f0f079e72 100644 --- a/borg/testsuite/helpers.py +++ b/borg/testsuite/helpers.py @@ -8,7 +8,7 @@ import pytest import sys import msgpack -from ..helpers import adjust_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, \ +from ..helpers import adjust_patterns, exclude_path, Location, format_file_size, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, \ prune_within, prune_split, get_cache_dir, Statistics, \ StableDict, int_to_bigint, bigint_to_int, parse_timestamp, CompressionSpec, ChunkerParams from . import BaseTestCase @@ -443,3 +443,28 @@ This archive: 20 B 10 B 10 B"" assert s == "20 B" # kind of redundant, but id is variable so we can't match reliably assert repr(stats) == ''.format(id(stats)) + +def test_size(): + """test the size formatting routines""" + si_size_map = { 0: '0 B', + 1: '1 B', + 142: '142 B', + 999: '999 B', + 1000: '1000 B', # XXX: fail + 1001: '1.00 kB', + 1234: '1.23 kB', + 10**6: '1.00 MB', + 10**6 + 10*10**3: '1.01 MB', + 10**9: '1.00 GB', + 10**9+1: '1.00 GB', + 10**9-1: '1.00 GB', + 10**9-10*10**3: '999.99 MB', + 10**9-10*10**3+1: '1.00 GB', + 10**12+1: '1.00 TB', + 10**15+1: '1.00 PB', + 10**18+1: '1.00 EB', + 10**21+1: '1.00 ZB', + 10**24+1: '1.00 YB', + } + for size, fmt in si_size_map.items(): + assert format_file_size(size) == fmt From c7c1b9222bf50edd78b02d377e85907a68ec2e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Tue, 20 Oct 2015 12:42:54 -0400 Subject: [PATCH 2/5] convert to more flexible size formatters those can now support both file sizes (in SI/decimal format, powers of 10) and memory sizes (in binary format, powers of 2) tests still fail because the result is always displayed as floats --- borg/helpers.py | 24 ++++++++++++++---------- borg/testsuite/helpers.py | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/borg/helpers.py b/borg/helpers.py index f6c042817..6a52ecfb9 100644 --- a/borg/helpers.py +++ b/borg/helpers.py @@ -460,16 +460,20 @@ def format_file_mode(mod): def format_file_size(v): """Format file size into a human friendly format """ - if abs(v) > 10**12: - return '%.2f TB' % (v / 10**12) - elif abs(v) > 10**9: - return '%.2f GB' % (v / 10**9) - elif abs(v) > 10**6: - return '%.2f MB' % (v / 10**6) - elif abs(v) > 10**3: - return '%.2f kB' % (v / 10**3) - else: - return '%d B' % v + return sizeof_fmt_decimal(v, suffix='B', sep=' ') + +def sizeof_fmt(num, suffix='B', units=None, power=None, sep=''): + for unit in units[:-1]: + if abs(round(num, 2)) < power: + return "%3.2f%s%s%s" % (num, sep, unit, suffix) + num /= float(power) + return "%.2f%s%s%s" % (num, sep, units[-1], suffix) + +def sizeof_fmt_iec(num, suffix='B', sep=''): + return sizeof_fmt(num, suffix=suffix, sep=sep, units=['','Ki','Mi','Gi','Ti','Pi','Ei','Zi', 'Yi'], power=1024) + +def sizeof_fmt_decimal(num, suffix='B', sep=''): + return sizeof_fmt(num, suffix=suffix, sep=sep, units=['','k','M','G','T','P','E','Z', 'Y'], power=1000) def format_archive(archive): diff --git a/borg/testsuite/helpers.py b/borg/testsuite/helpers.py index f0f079e72..ce3e060f1 100644 --- a/borg/testsuite/helpers.py +++ b/borg/testsuite/helpers.py @@ -450,7 +450,7 @@ def test_size(): 1: '1 B', 142: '142 B', 999: '999 B', - 1000: '1000 B', # XXX: fail + 1000: '1.00 kB', 1001: '1.00 kB', 1234: '1.23 kB', 10**6: '1.00 MB', @@ -459,7 +459,7 @@ def test_size(): 10**9+1: '1.00 GB', 10**9-1: '1.00 GB', 10**9-10*10**3: '999.99 MB', - 10**9-10*10**3+1: '1.00 GB', + 10**9-10*10**3+5*10**3: '1.00 GB', 10**12+1: '1.00 TB', 10**15+1: '1.00 PB', 10**18+1: '1.00 EB', From 416e42335ce8318e36bee7a986436c558a3314d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Tue, 20 Oct 2015 12:58:50 -0400 Subject: [PATCH 3/5] introduce variable precision --- borg/helpers.py | 23 +++++++++++++---------- borg/testsuite/helpers.py | 6 +++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/borg/helpers.py b/borg/helpers.py index 6a52ecfb9..a7972eed8 100644 --- a/borg/helpers.py +++ b/borg/helpers.py @@ -457,23 +457,26 @@ def format_file_mode(mod): return '%s%s%s' % (x(mod // 64), x(mod // 8), x(mod)) -def format_file_size(v): +def format_file_size(v, precision=2): """Format file size into a human friendly format """ - return sizeof_fmt_decimal(v, suffix='B', sep=' ') + return sizeof_fmt_decimal(v, suffix='B', sep=' ', precision=precision) -def sizeof_fmt(num, suffix='B', units=None, power=None, sep=''): +def sizeof_fmt(num, suffix='B', units=None, power=None, sep='', precision=2): for unit in units[:-1]: - if abs(round(num, 2)) < power: - return "%3.2f%s%s%s" % (num, sep, unit, suffix) + if abs(round(num, precision)) < power: + if type(num) is int: + return "{}{}{}{}".format(num, sep, unit, suffix) + else: + return "{:3.{}f}{}{}{}".format(num, precision, sep, unit, suffix) num /= float(power) - return "%.2f%s%s%s" % (num, sep, units[-1], suffix) + return "{:.{}f}{}{}{}".format(num, precision, sep, units[-1], suffix) -def sizeof_fmt_iec(num, suffix='B', sep=''): - return sizeof_fmt(num, suffix=suffix, sep=sep, units=['','Ki','Mi','Gi','Ti','Pi','Ei','Zi', 'Yi'], power=1024) +def sizeof_fmt_iec(num, suffix='B', sep='', precision=2): + return sizeof_fmt(num, suffix=suffix, sep=sep, precision=precision, units=['','Ki','Mi','Gi','Ti','Pi','Ei','Zi', 'Yi'], power=1024) -def sizeof_fmt_decimal(num, suffix='B', sep=''): - return sizeof_fmt(num, suffix=suffix, sep=sep, units=['','k','M','G','T','P','E','Z', 'Y'], power=1000) +def sizeof_fmt_decimal(num, suffix='B', sep='', precision=2): + return sizeof_fmt(num, suffix=suffix, sep=sep, precision=precision, units=['','k','M','G','T','P','E','Z', 'Y'], power=1000) def format_archive(archive): diff --git a/borg/testsuite/helpers.py b/borg/testsuite/helpers.py index ce3e060f1..a5f540b96 100644 --- a/borg/testsuite/helpers.py +++ b/borg/testsuite/helpers.py @@ -444,7 +444,7 @@ This archive: 20 B 10 B 10 B"" # kind of redundant, but id is variable so we can't match reliably assert repr(stats) == ''.format(id(stats)) -def test_size(): +def test_file_size(): """test the size formatting routines""" si_size_map = { 0: '0 B', 1: '1 B', @@ -468,3 +468,7 @@ def test_size(): } for size, fmt in si_size_map.items(): assert format_file_size(size) == fmt + +def test_file_size_precision(): + assert format_file_size(1254, precision=1) == '1.3 kB' + assert format_file_size(999990000, precision=1) == '1.0 GB' # and not 999.9 MB or 1000.0 MB From 21fdc91995b63ebaec7f4ff6c8b5292212b90d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Tue, 20 Oct 2015 12:58:59 -0400 Subject: [PATCH 4/5] clarify size tests --- borg/testsuite/helpers.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/borg/testsuite/helpers.py b/borg/testsuite/helpers.py index a5f540b96..d868d60e0 100644 --- a/borg/testsuite/helpers.py +++ b/borg/testsuite/helpers.py @@ -446,29 +446,30 @@ This archive: 20 B 10 B 10 B"" def test_file_size(): """test the size formatting routines""" - si_size_map = { 0: '0 B', + si_size_map = { 0: '0 B', # no rounding necessary for those 1: '1 B', 142: '142 B', 999: '999 B', - 1000: '1.00 kB', - 1001: '1.00 kB', - 1234: '1.23 kB', - 10**6: '1.00 MB', - 10**6 + 10*10**3: '1.01 MB', - 10**9: '1.00 GB', - 10**9+1: '1.00 GB', - 10**9-1: '1.00 GB', - 10**9-10*10**3: '999.99 MB', - 10**9-10*10**3+5*10**3: '1.00 GB', - 10**12+1: '1.00 TB', - 10**15+1: '1.00 PB', - 10**18+1: '1.00 EB', - 10**21+1: '1.00 ZB', - 10**24+1: '1.00 YB', + 1000: '1.00 kB', # rounding starts here + 1001: '1.00 kB', # should be rounded away + 1234: '1.23 kB', # should be rounded down + 1235: '1.24 kB', # should be rounded up + 1010: '1.01 kB', # rounded down as well + 999990000: '999.99 MB', # rounded down + 999990001: '999.99 MB', # rounded down + 999995000: '1.00 GB', # rounded up to next unit + 10**6: '1.00 MB', # and all the remaining units, megabytes + 10**9: '1.00 GB', # gigabytes + 10**12: '1.00 TB', # terabytes + 10**15: '1.00 PB', # petabytes + 10**18: '1.00 EB', # exabytes + 10**21: '1.00 ZB', # zottabytes + 10**24: '1.00 YB', # yottabytes } for size, fmt in si_size_map.items(): assert format_file_size(size) == fmt def test_file_size_precision(): - assert format_file_size(1254, precision=1) == '1.3 kB' + assert format_file_size(1234, precision=1) == '1.2 kB' # rounded down + assert format_file_size(1254, precision=1) == '1.3 kB' # rounded up assert format_file_size(999990000, precision=1) == '1.0 GB' # and not 999.9 MB or 1000.0 MB From aad6afb3e057cd111172d4532d409e2eb2024208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Tue, 20 Oct 2015 13:36:13 -0400 Subject: [PATCH 5/5] pep8 the whole darn helpers module --- borg/helpers.py | 16 +++--- borg/testsuite/helpers.py | 109 ++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 57 deletions(-) diff --git a/borg/helpers.py b/borg/helpers.py index a7972eed8..9ba8c8ed7 100644 --- a/borg/helpers.py +++ b/borg/helpers.py @@ -1,5 +1,4 @@ -from .support import argparse # see support/__init__.py docstring - # DEPRECATED - remove after requiring py 3.4 +from .support import argparse # see support/__init__.py docstring, DEPRECATED - remove after requiring py 3.4 import binascii from collections import namedtuple @@ -22,6 +21,7 @@ from datetime import datetime, timezone, timedelta from fnmatch import translate from operator import attrgetter + def have_cython(): """allow for a way to disable Cython includes @@ -165,6 +165,7 @@ class Statistics: summary = """\ Original size Compressed size Deduplicated size {label:15} {stats.osize_fmt:>20s} {stats.csize_fmt:>20s} {stats.usize_fmt:>20s}""" + def __str__(self): return self.summary.format(stats=self, label='This archive:') @@ -261,7 +262,7 @@ def exclude_path(path, patterns): def normalized(func): """ Decorator for the Pattern match methods, returning a wrapper that - normalizes OSX paths to match the normalized pattern on OSX, and + normalizes OSX paths to match the normalized pattern on OSX, and returning the original method on other platforms""" @wraps(func) def normalize_wrapper(self, path): @@ -462,21 +463,24 @@ def format_file_size(v, precision=2): """ return sizeof_fmt_decimal(v, suffix='B', sep=' ', precision=precision) + def sizeof_fmt(num, suffix='B', units=None, power=None, sep='', precision=2): for unit in units[:-1]: if abs(round(num, precision)) < power: - if type(num) is int: + if isinstance(num, int): return "{}{}{}{}".format(num, sep, unit, suffix) else: return "{:3.{}f}{}{}{}".format(num, precision, sep, unit, suffix) num /= float(power) return "{:.{}f}{}{}{}".format(num, precision, sep, units[-1], suffix) + def sizeof_fmt_iec(num, suffix='B', sep='', precision=2): - return sizeof_fmt(num, suffix=suffix, sep=sep, precision=precision, units=['','Ki','Mi','Gi','Ti','Pi','Ei','Zi', 'Yi'], power=1024) + return sizeof_fmt(num, suffix=suffix, sep=sep, precision=precision, units=['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'], power=1024) + def sizeof_fmt_decimal(num, suffix='B', sep='', precision=2): - return sizeof_fmt(num, suffix=suffix, sep=sep, precision=precision, units=['','k','M','G','T','P','E','Z', 'Y'], power=1000) + return sizeof_fmt(num, suffix=suffix, sep=sep, precision=precision, units=['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'], power=1000) def format_archive(archive): diff --git a/borg/testsuite/helpers.py b/borg/testsuite/helpers.py index d868d60e0..a27b22719 100644 --- a/borg/testsuite/helpers.py +++ b/borg/testsuite/helpers.py @@ -30,44 +30,44 @@ class TestLocationWithoutEnv: def test_ssh(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('ssh://user@host:1234/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')" assert repr(Location('ssh://user@host:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" def test_file(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('file:///some/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')" assert repr(Location('file:///some/path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" def test_scp(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('user@host:/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')" assert repr(Location('user@host:/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" def test_folder(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')" assert repr(Location('path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" def test_abspath(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('/some/absolute/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')" assert repr(Location('/some/absolute/path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" def test_relpath(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('some/relative/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')" assert repr(Location('some/relative/path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" def test_underspecified(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) @@ -95,51 +95,51 @@ class TestLocationWithoutEnv: 'ssh://user@host:1234/some/path::archive'] for location in locations: assert Location(location).canonical_path() == \ - Location(Location(location).canonical_path()).canonical_path() + Location(Location(location).canonical_path()).canonical_path() class TestLocationWithEnv: def test_ssh(self, monkeypatch): monkeypatch.setenv('BORG_REPO', 'ssh://user@host:1234/some/path') assert repr(Location('::archive')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')" assert repr(Location()) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" def test_file(self, monkeypatch): monkeypatch.setenv('BORG_REPO', 'file:///some/path') assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')" assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" def test_scp(self, monkeypatch): monkeypatch.setenv('BORG_REPO', 'user@host:/some/path') assert repr(Location('::archive')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')" assert repr(Location()) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" def test_folder(self, monkeypatch): monkeypatch.setenv('BORG_REPO', 'path') assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')" assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" def test_abspath(self, monkeypatch): monkeypatch.setenv('BORG_REPO', '/some/absolute/path') assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')" assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" def test_relpath(self, monkeypatch): monkeypatch.setenv('BORG_REPO', 'some/relative/path') assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')" + "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')" assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" def test_no_slashes(self, monkeypatch): monkeypatch.setenv('BORG_REPO', '/some/absolute/path') @@ -212,7 +212,7 @@ class PatternNonAsciiTestCase(BaseTestCase): assert i.match("ba\N{COMBINING ACUTE ACCENT}/foo") assert not e.match("b\N{LATIN SMALL LETTER A WITH ACUTE}/foo") assert e.match("ba\N{COMBINING ACUTE ACCENT}/foo") - + def testInvalidUnicode(self): pattern = str(b'ba\x80', 'latin1') i = IncludePattern(pattern) @@ -235,7 +235,7 @@ class OSXPatternNormalizationTestCase(BaseTestCase): assert i.match("ba\N{COMBINING ACUTE ACCENT}/foo") assert e.match("b\N{LATIN SMALL LETTER A WITH ACUTE}/foo") assert e.match("ba\N{COMBINING ACUTE ACCENT}/foo") - + def testDecomposedUnicode(self): pattern = 'ba\N{COMBINING ACUTE ACCENT}' i = IncludePattern(pattern) @@ -245,7 +245,7 @@ class OSXPatternNormalizationTestCase(BaseTestCase): assert i.match("ba\N{COMBINING ACUTE ACCENT}/foo") assert e.match("b\N{LATIN SMALL LETTER A WITH ACUTE}/foo") assert e.match("ba\N{COMBINING ACUTE ACCENT}/foo") - + def testInvalidUnicode(self): pattern = str(b'ba\x80', 'latin1') i = IncludePattern(pattern) @@ -401,12 +401,14 @@ def test_get_cache_dir(): if old_env is not None: os.environ['BORG_CACHE_DIR'] = old_env + @pytest.fixture() def stats(): stats = Statistics() stats.update(20, 10, unique=True) return stats + def test_stats_basic(stats): assert stats.osize == 20 assert stats.csize == stats.usize == 10 @@ -415,6 +417,7 @@ def test_stats_basic(stats): assert stats.csize == 20 assert stats.usize == 10 + def tests_stats_progress(stats, columns=80): os.environ['COLUMNS'] = str(columns) out = StringIO() @@ -435,6 +438,7 @@ def tests_stats_progress(stats, columns=80): buf = ' ' * (columns - len(s)) assert out.getvalue() == s + buf + "\r" + def test_stats_format(stats): assert str(stats) == """\ Original size Compressed size Deduplicated size @@ -444,32 +448,35 @@ This archive: 20 B 10 B 10 B"" # kind of redundant, but id is variable so we can't match reliably assert repr(stats) == ''.format(id(stats)) + def test_file_size(): """test the size formatting routines""" - si_size_map = { 0: '0 B', # no rounding necessary for those - 1: '1 B', - 142: '142 B', - 999: '999 B', - 1000: '1.00 kB', # rounding starts here - 1001: '1.00 kB', # should be rounded away - 1234: '1.23 kB', # should be rounded down - 1235: '1.24 kB', # should be rounded up - 1010: '1.01 kB', # rounded down as well - 999990000: '999.99 MB', # rounded down - 999990001: '999.99 MB', # rounded down - 999995000: '1.00 GB', # rounded up to next unit - 10**6: '1.00 MB', # and all the remaining units, megabytes - 10**9: '1.00 GB', # gigabytes - 10**12: '1.00 TB', # terabytes - 10**15: '1.00 PB', # petabytes - 10**18: '1.00 EB', # exabytes - 10**21: '1.00 ZB', # zottabytes - 10**24: '1.00 YB', # yottabytes - } + si_size_map = { + 0: '0 B', # no rounding necessary for those + 1: '1 B', + 142: '142 B', + 999: '999 B', + 1000: '1.00 kB', # rounding starts here + 1001: '1.00 kB', # should be rounded away + 1234: '1.23 kB', # should be rounded down + 1235: '1.24 kB', # should be rounded up + 1010: '1.01 kB', # rounded down as well + 999990000: '999.99 MB', # rounded down + 999990001: '999.99 MB', # rounded down + 999995000: '1.00 GB', # rounded up to next unit + 10**6: '1.00 MB', # and all the remaining units, megabytes + 10**9: '1.00 GB', # gigabytes + 10**12: '1.00 TB', # terabytes + 10**15: '1.00 PB', # petabytes + 10**18: '1.00 EB', # exabytes + 10**21: '1.00 ZB', # zottabytes + 10**24: '1.00 YB', # yottabytes + } for size, fmt in si_size_map.items(): assert format_file_size(size) == fmt + def test_file_size_precision(): - assert format_file_size(1234, precision=1) == '1.2 kB' # rounded down - assert format_file_size(1254, precision=1) == '1.3 kB' # rounded up - assert format_file_size(999990000, precision=1) == '1.0 GB' # and not 999.9 MB or 1000.0 MB + assert format_file_size(1234, precision=1) == '1.2 kB' # rounded down + assert format_file_size(1254, precision=1) == '1.3 kB' # rounded up + assert format_file_size(999990000, precision=1) == '1.0 GB' # and not 999.9 MB or 1000.0 MB