summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc2
-rw-r--r--tests/data/meter/meter1.txt12
-rw-r--r--tests/data/meter/meter2.txt12
-rw-r--r--tests/data/meter/meter3.txt7
-rw-r--r--tests/data/meter/meter4.txt7
-rw-r--r--tests/data/meter/meter5.txt12
-rw-r--r--tests/data/meter/meter6.txt12
-rw-r--r--tests/test_misc.py75
-rw-r--r--virtinst/_progresspriv.py169
9 files changed, 174 insertions, 134 deletions
diff --git a/.coveragerc b/.coveragerc
index 8e9c2a17..c404df72 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -3,7 +3,7 @@ source=virtinst/
[report]
skip_covered = yes
-omit=virtinst/_progresspriv.py
+#omit=virtinst/_progresspriv.py
exclude_lines =
# Have to re-enable the standard pragma
diff --git a/tests/data/meter/meter1.txt b/tests/data/meter/meter1.txt
new file mode 100644
index 00000000..a3f7c7d2
--- /dev/null
+++ b/tests/data/meter/meter1.txt
@@ -0,0 +1,12 @@
+
+Meter text test 0% [ ] 0 B/s | 0 B --:-- ETA
+
+Meter text test 1% [ ] 0 B/s | 100 B --:-- ETA
+
+Meter text test 2% [ ] 67 B/s | 200 B 02:27 ETA
+
+Meter text test 20% [=== ] 413 B/s | 2.0 kB 00:19 ETA
+
+Meter text test 40% [======- ] 731 B/s | 3.9 kB 00:08 ETA
+
+Meter text test | 3.9 kB 00:04 ...
diff --git a/tests/data/meter/meter2.txt b/tests/data/meter/meter2.txt
new file mode 100644
index 00000000..93e93dc3
--- /dev/null
+++ b/tests/data/meter/meter2.txt
@@ -0,0 +1,12 @@
+
+Meter text test 0% [ ] 0 B/s | 0 B --:--:-- ETA
+
+Meter text test 1% [ ] 0 B/s | 100 B --:--:-- ETA
+
+Meter text test 2% [- ] 67 B/s | 200 B 00:02:27 ETA
+
+Meter text test 20% [======= ] 413 B/s | 2.0 kB 00:00:19 ETA
+
+Meter text test 40% [============== ] 731 B/s | 3.9 kB 00:00:08 ETA
+
+Meter text test | 3.9 kB 00:00:04 ...
diff --git a/tests/data/meter/meter3.txt b/tests/data/meter/meter3.txt
new file mode 100644
index 00000000..474e40f7
--- /dev/null
+++ b/tests/data/meter/meter3.txt
@@ -0,0 +1,7 @@
+Meter text test 0 B/s | 0 B 00:00
+Meter text test 0 B/s | 100 B 00:00
+Meter text test 67 B/s | 200 B 00:02
+Meter text test 413 B/s | 2.0 kB 00:03
+Meter text test 731 B/s | 3.9 kB 00:04
+
+Meter text test | 3.9 kB 00:04
diff --git a/tests/data/meter/meter4.txt b/tests/data/meter/meter4.txt
new file mode 100644
index 00000000..aa05acd5
--- /dev/null
+++ b/tests/data/meter/meter4.txt
@@ -0,0 +1,7 @@
+12345678 | 0 B
+12345678 | 100 B
+12345678 | 200 B
+12345678 | 2.0 kB
+12345678 | 3.9 kB
+
+1234567890
diff --git a/tests/data/meter/meter5.txt b/tests/data/meter/meter5.txt
new file mode 100644
index 00000000..1d232a5d
--- /dev/null
+++ b/tests/data/meter/meter5.txt
@@ -0,0 +1,12 @@
+
+Meter text test 0% [ ] 0 B/s | 0 B --:-- ETA
+
+Meter text test 50% [========- ] 0 B/s | 100 B --:-- ETA
+
+Meter text test 100% [================] 67 B/s | 200 B 00:00 ETA
+
+Meter text test 1000% [================] 413 B/s | 2.0 kB --:-- ETA
+
+Meter text test 2000% [================] 731 B/s | 3.9 kB --:-- ETA
+
+Meter text test | 3.9 kB 00:04 !!!
diff --git a/tests/data/meter/meter6.txt b/tests/data/meter/meter6.txt
new file mode 100644
index 00000000..07d99bfd
--- /dev/null
+++ b/tests/data/meter/meter6.txt
@@ -0,0 +1,12 @@
+
+Meter text test 100% [================] 0 B/s | 0 B --:-- ETA
+
+Meter text test 100% [================] 0 B/s | 100 B --:-- ETA
+
+Meter text test 100% [================] 67 B/s | 200 B --:-- ETA
+
+Meter text test 100% [================] 413 B/s | 2.0 kB --:-- ETA
+
+Meter text test 100% [================] 731 B/s | 3.9 kB --:-- ETA
+
+Meter text test | 3.9 kB 00:04
diff --git a/tests/test_misc.py b/tests/test_misc.py
index f908c74a..2dced1e8 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -3,6 +3,10 @@
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
+import io
+import os
+import unittest
+
import virtinst
from tests import utils
@@ -124,3 +128,74 @@ def test_misc_cpu_cornercases():
guest.cpu.model = "idontexist"
guest.cpu._validate_default_host_model_only(guest)
assert guest.cpu.model is None
+
+
+def test_misc_meter():
+ """
+ Test coverage of our urlgrabber meter copy
+ """
+ # pylint: disable=protected-access
+ from virtinst import _progresspriv
+
+ def _test_meter_values(m, startval=10000, text="Meter text test"):
+ with unittest.mock.patch("time.time", return_value=1.0):
+ m.start(text, startval)
+ with unittest.mock.patch("time.time", return_value=1.1):
+ m.update(0)
+ with unittest.mock.patch("time.time", return_value=1.5):
+ m.update(0)
+ with unittest.mock.patch("time.time", return_value=2.0):
+ m.update(100)
+ with unittest.mock.patch("time.time", return_value=3.0):
+ m.update(200)
+ with unittest.mock.patch("time.time", return_value=4.0):
+ m.update(2000)
+ with unittest.mock.patch("time.time", return_value=5.0):
+ m.update(4000)
+ with unittest.mock.patch("time.time", return_value=6.0):
+ m.end()
+
+ # Basic output testing
+ meter = _progresspriv.TextMeter(output=io.StringIO())
+ _test_meter_values(meter)
+ out = meter.output.getvalue().replace("\r", "\n")
+ utils.diff_compare(out, os.path.join(utils.DATADIR, "meter", "meter1.txt"))
+
+ # Fake having a longer terminal, it affects output a bit
+ meter = _progresspriv.TextMeter(output=io.StringIO())
+ _progresspriv._term_width_val = 120
+ _test_meter_values(meter)
+ _progresspriv._term_width_val = 80
+ out = meter.output.getvalue().replace("\r", "\n")
+ utils.diff_compare(out, os.path.join(utils.DATADIR, "meter", "meter2.txt"))
+
+ # meter with size=None
+ meter = _progresspriv.TextMeter(output=io.StringIO())
+ _test_meter_values(meter, None)
+ out = meter.output.getvalue().replace("\r", "\n")
+ utils.diff_compare(out, os.path.join(utils.DATADIR, "meter", "meter3.txt"))
+
+ # meter with size=None and small terminal size
+ meter = _progresspriv.TextMeter(output=io.StringIO())
+ _progresspriv._term_width_val = 11
+ _test_meter_values(meter, None, "1234567890")
+ assert meter.re.fraction_read() is None
+ _progresspriv._term_width_val = 80
+ out = meter.output.getvalue().replace("\r", "\n")
+ utils.diff_compare(out, os.path.join(utils.DATADIR, "meter", "meter4.txt"))
+
+ # meter with size exceeded by the update() values
+ meter = _progresspriv.TextMeter(output=io.StringIO())
+ _test_meter_values(meter, 200)
+ out = meter.output.getvalue().replace("\r", "\n")
+ utils.diff_compare(out, os.path.join(utils.DATADIR, "meter", "meter5.txt"))
+
+ # meter with size 0
+ meter = _progresspriv.TextMeter(output=io.StringIO())
+ _test_meter_values(meter, 0)
+ out = meter.output.getvalue().replace("\r", "\n")
+ utils.diff_compare(out, os.path.join(utils.DATADIR, "meter", "meter6.txt"))
+
+ # BaseMeter coverage
+ meter = _progresspriv.BaseMeter()
+ _test_meter_values(meter)
diff --git a/virtinst/_progresspriv.py b/virtinst/_progresspriv.py
index 5e701c96..5a31a18c 100644
--- a/virtinst/_progresspriv.py
+++ b/virtinst/_progresspriv.py
@@ -10,12 +10,11 @@
# we are just copying this for now.
-import sys
-import time
-import math
import fcntl
import struct
+import sys
import termios
+import time
# Code from https://mail.python.org/pipermail/python-list/2000-May/033365.html
@@ -24,11 +23,8 @@ def terminal_width(fd=1):
try:
buf = 'abcdefgh'
buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, buf)
- ret = struct.unpack('hhhh', buf)[1]
- if ret == 0:
- return 80
- # Add minimum too?
- return ret
+ ret = struct.unpack('hhhh', buf)[1] # pragma: no cover
+ return ret or 80 # pragma: no cover
except IOError:
return 80
@@ -66,9 +62,7 @@ class TerminalLine:
def rest_split(self, fixed, elements=2):
""" After a fixed length, split the rest of the line length among
a number of different elements (default=2). """
- if self.llen < fixed:
- return 0
- return (self.llen - fixed) // elements
+ return max(self.llen - fixed, 0) // elements
def add(self, element, full_len=None):
""" If there is room left in the line, above min_len, add element.
@@ -93,71 +87,48 @@ class BaseMeter:
def __init__(self):
self.update_period = 0.3 # seconds
- self.url = None
- self.basename = None
self.text = None
self.size = None
self.start_time = None
- self.fsize = None
self.last_amount_read = 0
self.last_update_time = None
self.re = RateEstimator()
- def set_text(self, text):
- self.text = text
-
def start(self, text, size):
self.text = text
-
self.size = size
- if size is not None:
- self.fsize = format_number(size) + 'B'
+ assert type(size) in [int, type(None)]
+ assert self.text is not None
now = time.time()
self.start_time = now
self.re.start(size, now)
self.last_amount_read = 0
self.last_update_time = now
- self._do_start(now)
- def _do_start(self, now=None):
- pass
-
- def update(self, amount_read, now=None):
+ def update(self, amount_read):
# for a real gui, you probably want to override and put a call
# to your mainloop iteration function here
- if now is None:
- now = time.time()
+ assert type(amount_read) is int
+
+ now = time.time()
if (not self.last_update_time or
(now >= self.last_update_time + self.update_period)):
self.re.update(amount_read, now)
self.last_amount_read = amount_read
self.last_update_time = now
- self._do_update(amount_read, now)
+ self._do_update(amount_read)
- def _do_update(self, amount_read, now=None):
+ def _do_update(self, amount_read):
pass
def end(self):
- self._do_end(self.last_amount_read, self.last_update_time)
+ self._do_end()
- def _do_end(self, amount_read, now=None):
+ def _do_end(self):
pass
-# This is kind of a hack, but progress is gotten from grabber which doesn't
-# know about the total size to download. So we do this so we can get the data
-# out of band here. This will be "fixed" one way or anther soon.
-_text_meter_total_size = 0
-_text_meter_sofar_size = 0
-
-
-def text_meter_total_size(size, downloaded=0):
- global _text_meter_total_size
- global _text_meter_sofar_size
- _text_meter_total_size = size
- _text_meter_sofar_size = downloaded
-
#
# update: No size (minimal: 17 chars)
# -----------------------------------
@@ -230,20 +201,11 @@ class TextMeter(BaseMeter):
BaseMeter.__init__(self)
self.output = output
- def _do_update(self, amount_read, now=None):
+ def _do_update(self, amount_read):
etime = self.re.elapsed_time()
fread = format_number(amount_read)
- # self.size = None
- if self.text is not None:
- text = self.text
- else:
- text = self.basename
ave_dl = format_number(self.re.average_rate())
- sofar_size = None
- if _text_meter_total_size:
- sofar_size = _text_meter_sofar_size + amount_read
- sofar_pc = (sofar_size * 100) // _text_meter_total_size
# Include text + ui_rate in minimal
tl = TerminalLine(8, 8 + 1 + 8)
@@ -254,7 +216,7 @@ class TextMeter(BaseMeter):
ui_time = tl.add(' %s' % format_time(etime, use_hours))
ui_end = tl.add(' ' * 5)
ui_rate = tl.add(' %5sB/s' % ave_dl)
- out = '%-*.*s%s%s%s%s\r' % (tl.rest(), tl.rest(), text,
+ out = '%-*.*s%s%s%s%s\r' % (tl.rest(), tl.rest(), self.text,
ui_rate, ui_size, ui_time, ui_end)
else:
rtime = self.re.remaining_time()
@@ -264,35 +226,23 @@ class TextMeter(BaseMeter):
ui_time = tl.add(' %s' % frtime)
ui_end = tl.add(' ETA ')
- if sofar_size is None:
- ui_sofar_pc = ''
- else:
- ui_sofar_pc = tl.add(' (%i%%)' % sofar_pc,
- full_len=len(" (100%)"))
-
ui_pc = tl.add(' %2i%%' % (frac * 100))
ui_rate = tl.add(' %5sB/s' % ave_dl)
# Make text grow a bit before we start growing the bar too
blen = 4 + tl.rest_split(8 + 8 + 4)
ui_bar = _term_add_bar(tl, blen, frac)
- out = '\r%-*.*s%s%s%s%s%s%s%s\r' % (
- tl.rest(), tl.rest(), text,
- ui_sofar_pc, ui_pc, ui_bar,
+ out = '\r%-*.*s%s%s%s%s%s%s\r' % (
+ tl.rest(), tl.rest(), self.text,
+ ui_pc, ui_bar,
ui_rate, ui_size, ui_time, ui_end
)
self.output.write(out)
self.output.flush()
- def _do_end(self, amount_read, now=None):
- global _text_meter_total_size
- global _text_meter_sofar_size
-
+ def _do_end(self):
+ amount_read = self.last_amount_read
total_size = format_number(amount_read)
- if self.text is not None:
- text = self.text
- else:
- text = self.basename
tl = TerminalLine(8)
# For big screens, make it more readable.
@@ -301,29 +251,16 @@ class TextMeter(BaseMeter):
ui_time = tl.add(' %s' % format_time(self.re.elapsed_time(),
use_hours))
ui_end, not_done = _term_add_end(tl, self.size, amount_read)
- out = '\r%-*.*s%s%s%s\n' % (tl.rest(), tl.rest(), text,
+ dummy = not_done
+ out = '\r%-*.*s%s%s%s\n' % (tl.rest(), tl.rest(), self.text,
ui_size, ui_time, ui_end)
self.output.write(out)
self.output.flush()
- # Don't add size to the sofar size until we have all of it.
- # If we don't have a size, then just pretend/hope we got all of it.
- if not_done:
- return
-
- if _text_meter_total_size:
- _text_meter_sofar_size += amount_read
- if _text_meter_total_size <= _text_meter_sofar_size:
- _text_meter_total_size = 0
- _text_meter_sofar_size = 0
-
-
-text_progress_meter = TextMeter
######################################################################
# support classes and functions
-
class RateEstimator:
def __init__(self, timescale=5.0):
self.timescale = timescale
@@ -333,22 +270,14 @@ class RateEstimator:
self.last_amount_read = 0
self.ave_rate = None
- def start(self, total=None, now=None):
- if now is None:
- now = time.time()
+ def start(self, total, now):
self.total = total
self.start_time = now
self.last_update_time = now
self.last_amount_read = 0
self.ave_rate = None
- def update(self, amount_read, now=None):
- if now is None:
- now = time.time()
- # libcurl calls the progress callback when fetching headers
- # too, thus amount_read = 0 .. hdr_size .. 0 .. content_size.
- # Occasionally we miss the 2nd zero and report avg speed < 0.
- # Handle read_diff < 0 here. BZ 1001767.
+ def update(self, amount_read, now):
if amount_read == 0 or amount_read < self.last_amount_read:
# if we just started this file, all bets are off
self.last_update_time = now
@@ -386,10 +315,9 @@ class RateEstimator:
(can be None for unknown transfer size)"""
if self.total is None:
return None
- elif self.total == 0:
- return 1.0
- else:
- return float(self.last_amount_read) / self.total
+ if self.total == 0:
+ return 1.0 # pragma: no cover
+ return float(self.last_amount_read) / self.total
#########################################################################
# support methods
@@ -413,37 +341,16 @@ class RateEstimator:
try:
recent_rate = read_diff / time_diff
- except ZeroDivisionError:
+ except ZeroDivisionError: # pragma: no cover
recent_rate = None
if last_ave is None:
return recent_rate
- elif recent_rate is None:
- return last_ave
+ if recent_rate is None:
+ return last_ave # pragma: no cover
# at this point, both last_ave and recent_rate are numbers
return epsilon * recent_rate + (1 - epsilon) * last_ave
- def _round_remaining_time(self, rt, start_time=15.0):
- """round the remaining time, depending on its size
- If rt is between n*start_time and (n+1)*start_time round downward
- to the nearest multiple of n (for any counting number n).
- If rt < start_time, round down to the nearest 1.
- For example (for start_time = 15.0):
- 2.7 -> 2.0
- 25.2 -> 25.0
- 26.4 -> 26.0
- 35.3 -> 34.0
- 63.6 -> 60.0
- """
-
- if rt < 0:
- return 0.0
- shift = int(math.log(rt / start_time) / math.log(2))
- rt = int(rt)
- if shift <= 0:
- return rt
- return float(int(rt) >> shift << shift)
-
def format_time(seconds, use_hours=0):
if seconds is None or seconds < 0:
@@ -452,7 +359,7 @@ def format_time(seconds, use_hours=0):
else:
return '--:--'
elif seconds == float('inf'):
- return 'Infinite'
+ return 'Infinite' # pragma: no cover
else:
seconds = int(seconds)
minutes = seconds // 60
@@ -465,7 +372,7 @@ def format_time(seconds, use_hours=0):
return '%02i:%02i' % (minutes, seconds)
-def format_number(number, SI=0, space=' '):
+def format_number(number):
"""Turn numbers into human-readable metric-like numbers"""
symbols = ['', # (none)
'k', # kilo
@@ -477,11 +384,7 @@ def format_number(number, SI=0, space=' '):
'Z', # zetta
'Y'] # yotta
- if SI:
- step = 1000.0
- else:
- step = 1024.0
-
+ step = 1024.0
thresh = 999
depth = 0
max_depth = len(symbols) - 1
@@ -505,4 +408,4 @@ def format_number(number, SI=0, space=' '):
else:
fmt = '%.0f%s%s'
- return(fmt % (float(number or 0), space, symbols[depth]))
+ return fmt % (float(number or 0), " ", symbols[depth])