kitty/kitty_tests/graphics.py

669 lines
24 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2017-09-28 07:03:53 +03:00
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
2021-01-03 15:06:40 +03:00
import random
2017-09-28 11:48:54 +03:00
import tempfile
2021-01-03 02:37:01 +03:00
import time
2017-09-28 17:33:26 +03:00
import unittest
2017-09-28 10:50:48 +03:00
import zlib
from base64 import standard_b64decode, standard_b64encode
2017-09-28 17:33:26 +03:00
from io import BytesIO
2021-01-03 02:37:01 +03:00
from itertools import cycle
2021-01-27 11:16:08 +03:00
from typing import NamedTuple
2020-12-30 11:56:38 +03:00
from kitty.constants import cache_dir
2017-10-03 13:40:04 +03:00
from kitty.fast_data_types import (
2020-12-30 11:56:38 +03:00
load_png_data, parse_bytes, set_send_to_gpu, shm_unlink, shm_write,
xor_data
2017-10-03 13:40:04 +03:00
)
2017-09-28 07:03:53 +03:00
2017-09-28 10:50:48 +03:00
from . import BaseTest
2017-09-28 17:33:26 +03:00
try:
from PIL import Image
except ImportError:
Image = None
2017-10-01 06:34:46 +03:00
set_send_to_gpu(False)
2017-09-28 07:03:53 +03:00
def relpath(name):
2017-09-28 07:03:53 +03:00
base = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base, name)
def send_command(screen, cmd, payload=b''):
cmd = '\033_G' + cmd
if payload:
if isinstance(payload, str):
payload = payload.encode('utf-8')
payload = standard_b64encode(payload).decode('ascii')
cmd += ';' + payload
cmd += '\033\\'
c = screen.callbacks
c.clear()
parse_bytes(screen, cmd.encode('ascii'))
return c.wtcbuf
def parse_response(res):
if not res:
return
return res.decode('ascii').partition(';')[2].partition('\033')[0]
def parse_response_with_ids(res):
if not res:
return
a, b = res.decode('ascii').split(';', 1)
code = b.partition('\033')[0].split(':', 1)[0]
a = a.split('G', 1)[1]
return code, a
2021-01-27 11:16:08 +03:00
class Response(NamedTuple):
code: str = 'OK'
msg: str = ''
image_id: int = 0
image_number: int = 0
frame_number: int = 0
def parse_full_response(res):
if not res:
return
a, b = res.decode('ascii').split(';', 1)
code = b.partition('\033')[0].split(':', 1)
if len(code) == 1:
code = code[0]
msg = ''
else:
code, msg = code
a = a.split('G', 1)[1]
ans = {'code': code, 'msg': msg}
for x in a.split(','):
k, _, v = x.partition('=')
ans[{'i': 'image_id', 'I': 'image_number', 'r': 'frame_number'}[k]] = int(v)
return Response(**ans)
2017-10-06 20:22:50 +03:00
all_bytes = bytes(bytearray(range(256)))
def byte_block(sz):
d, m = divmod(sz, len(all_bytes))
return (all_bytes * d) + all_bytes[:m]
2017-10-03 13:40:04 +03:00
def load_helpers(self):
2017-09-28 17:33:26 +03:00
s = self.create_screen()
g = s.grman
2017-10-23 15:03:57 +03:00
def pl(payload, **kw):
2017-09-28 17:33:26 +03:00
kw.setdefault('i', 1)
cmd = ','.join('%s=%s' % (k, v) for k, v in kw.items())
res = send_command(s, cmd, payload)
return parse_response(res)
2017-09-28 17:33:26 +03:00
def sl(payload, **kw):
if isinstance(payload, str):
payload = payload.encode('utf-8')
data = kw.pop('expecting_data', payload)
cid = kw.setdefault('i', 1)
2017-10-23 15:03:57 +03:00
self.ae('OK', pl(payload, **kw))
2017-09-28 17:33:26 +03:00
img = g.image_for_client_id(cid)
self.ae(img['client_id'], cid)
self.ae(img['data'], data)
if 's' in kw:
self.ae((kw['s'], kw['v']), (img['width'], img['height']))
self.ae(img['is_4byte_aligned'], kw.get('f') != 24)
return img
2017-10-23 15:03:57 +03:00
return s, g, pl, sl
2017-09-28 17:33:26 +03:00
2017-10-05 18:24:45 +03:00
def put_helpers(self, cw, ch):
iid = 0
def create_screen():
s = self.create_screen(10, 5, cell_width=cw, cell_height=ch)
2017-10-05 18:24:45 +03:00
return s, 2 / s.columns, 2 / s.lines
def put_cmd(z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0, cell_y_off=0, placement_id=0):
return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d' % (
z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, cell_y_off, placement_id)
2017-10-05 18:24:45 +03:00
def put_image(screen, w, h, **kw):
nonlocal iid
iid += 1
imgid = kw.pop('id', None) or iid
cmd = 'a=T,f=24,i=%d,s=%d,v=%d,%s' % (imgid, w, h, put_cmd(**kw))
2017-10-05 18:24:45 +03:00
data = b'x' * w * h * 3
res = send_command(screen, cmd, data)
return imgid, parse_response(res)
2017-10-05 18:24:45 +03:00
def put_ref(screen, **kw):
imgid = kw.pop('id', None) or iid
cmd = 'a=p,i=%d,%s' % (imgid, put_cmd(**kw))
return imgid, parse_response_with_ids(send_command(screen, cmd))
2017-10-05 18:24:45 +03:00
def layers(screen, scrolled_by=0, xstart=-1, ystart=1):
return screen.grman.update_layers(scrolled_by, xstart, ystart, dx, dy, screen.columns, screen.lines, cw, ch)
2017-10-05 18:24:45 +03:00
def rect_eq(r, left, top, right, bottom):
for side in 'left top right bottom'.split():
a, b = r[side], locals()[side]
if abs(a - b) > 0.0001:
self.ae(a, b, 'the %s side is not equal' % side)
s, dx, dy = create_screen()
return s, dx, dy, put_image, put_ref, layers, rect_eq
2017-09-28 07:03:53 +03:00
class TestGraphics(BaseTest):
2020-12-30 11:56:38 +03:00
def setUp(self):
self.cache_dir = cache_dir.override_dir = tempfile.mkdtemp()
def tearDown(self):
2021-01-03 01:58:48 +03:00
os.rmdir(self.cache_dir)
2020-12-30 11:56:38 +03:00
cache_dir.override_dir = None
def test_xor_data(self):
def xor(skey, data):
ckey = cycle(bytearray(skey))
return bytes(bytearray(k ^ d for k, d in zip(ckey, bytearray(data))))
base_data = os.urandom(64)
key = os.urandom(len(base_data))
for base in (b'', base_data):
for extra in range(len(base_data)):
data = base + base_data[:extra]
self.assertEqual(xor_data(key, data), xor(key, data))
2021-01-03 01:58:48 +03:00
def test_disk_cache(self):
s = self.create_screen()
dc = s.grman.disk_cache
data = {}
def key_as_bytes(key):
if isinstance(key, int):
key = str(key)
if isinstance(key, str):
key = key.encode('utf-8')
return bytes(key)
def add(key, val):
bkey = key_as_bytes(key)
data[key] = key_as_bytes(val)
dc.add(bkey, data[key])
2021-01-03 02:30:40 +03:00
def remove(key):
bkey = key_as_bytes(key)
data.pop(key, None)
2021-01-03 15:06:40 +03:00
return dc.remove(bkey)
2021-01-03 02:30:40 +03:00
def check_data():
for key, val in data.items():
self.ae(dc.get(key_as_bytes(key)), val)
2021-01-03 01:58:48 +03:00
for i in range(25):
self.assertIsNone(add(i, f'{i}' * i))
self.assertEqual(dc.total_size, sum(map(len, data.values())))
self.assertTrue(dc.wait_for_write())
2021-01-03 02:30:40 +03:00
check_data()
sz = dc.size_on_disk()
self.assertEqual(sz, sum(map(len, data.values())))
for x in (2, 4, 6, 8):
remove(x)
check_data()
self.assertRaises(KeyError, dc.get, key_as_bytes(x))
self.assertEqual(sz, dc.size_on_disk())
2021-01-03 02:37:01 +03:00
for x in ('xy', 'C'*4, 'B'*6, 'A'*8):
add(x, x)
self.assertTrue(dc.wait_for_write())
self.assertEqual(sz, dc.size_on_disk())
check_data()
check_data()
dc.clear()
st = time.monotonic()
while dc.size_on_disk() and time.monotonic() - st < 2:
time.sleep(0.001)
self.assertEqual(dc.size_on_disk(), 0)
2021-01-03 01:58:48 +03:00
2021-01-03 15:06:40 +03:00
data.clear()
for i in range(25):
self.assertIsNone(add(i, f'{i}' * i))
dc.wait_for_write()
check_data()
2021-01-03 15:06:40 +03:00
before = dc.size_on_disk()
while dc.total_size > before // 3:
key = random.choice(tuple(data))
self.assertTrue(remove(key))
check_data()
2021-01-03 15:06:40 +03:00
add('trigger defrag', 'XXX')
dc.wait_for_write()
self.assertLess(dc.size_on_disk(), before)
check_data()
2017-09-28 07:03:53 +03:00
def test_load_images(self):
2017-10-03 13:40:04 +03:00
s, g, l, sl = load_helpers(self)
2021-01-24 11:57:52 +03:00
self.assertEqual(g.disk_cache.total_size, 0)
# Test load query
self.ae(l('abcd', s=1, v=1, a='q'), 'OK')
self.assertIsNone(l('abcd', s=1, v=1, a='q', q=1))
self.ae(g.image_count, 0)
2017-09-28 10:31:13 +03:00
# Test simple load
for f in 32, 24:
p = 'abc' + ('d' if f == 32 else '')
img = sl(p, s=1, v=1, f=f)
self.ae(bool(img['is_4byte_aligned']), f == 32)
2017-09-28 10:31:13 +03:00
# Test chunked load
2017-09-28 10:31:13 +03:00
self.assertIsNone(l('abcd', s=2, v=2, m=1))
self.assertIsNone(l('efgh', m=1))
self.assertIsNone(l('ijkl', m=1))
self.ae(l('mnop', m=0), 'OK')
img = g.image_for_client_id(1)
self.ae(img['data'], b'abcdefghijklmnop')
self.ae(l('abcd', s=10, v=10, q=1), 'ENODATA:Insufficient image data: 4 < 400')
self.ae(l('abcd', s=10, v=10, q=2), None)
2017-09-28 10:50:48 +03:00
# Test compression
2017-10-06 20:22:50 +03:00
random_data = byte_block(3 * 1024)
2017-09-28 11:48:54 +03:00
compressed_random_data = zlib.compress(random_data)
2017-09-28 17:33:26 +03:00
sl(
compressed_random_data,
s=24,
v=32,
o='z',
expecting_data=random_data
)
2017-09-28 11:48:54 +03:00
# Test chunked + compressed
b = len(compressed_random_data) // 2
self.assertIsNone(l(compressed_random_data[:b], s=24, v=32, o='z', m=1))
self.ae(l(compressed_random_data[b:], m=0), 'OK')
img = g.image_for_client_id(1)
self.ae(img['data'], random_data)
2017-09-28 11:48:54 +03:00
# Test loading from file
f = tempfile.NamedTemporaryFile()
f.write(random_data), f.flush()
sl(f.name, s=24, v=32, t='f', expecting_data=random_data)
self.assertTrue(os.path.exists(f.name))
f.seek(0), f.truncate(), f.write(compressed_random_data), f.flush()
sl(f.name, s=24, v=32, t='t', o='z', expecting_data=random_data)
2017-09-28 17:33:26 +03:00
self.assertRaises(
FileNotFoundError, f.close
) # check that file was deleted
# Test loading from POSIX SHM
name = '/kitty-test-shm'
2017-10-01 06:34:46 +03:00
shm_write(name, random_data)
sl(name, s=24, v=32, t='s', expecting_data=random_data)
2017-09-28 17:33:26 +03:00
self.assertRaises(
2017-10-01 06:34:46 +03:00
FileNotFoundError, shm_unlink, name
2017-09-28 17:33:26 +03:00
) # check that file was deleted
2021-01-24 11:57:52 +03:00
s.reset()
self.assertEqual(g.disk_cache.total_size, 0)
2017-09-28 17:33:26 +03:00
@unittest.skipIf(Image is None, 'PIL not available, skipping PNG tests')
def test_load_png(self):
2017-10-03 13:40:04 +03:00
s, g, l, sl = load_helpers(self)
2017-09-28 17:33:26 +03:00
w, h = 5, 3
2017-10-06 20:22:50 +03:00
rgba_data = byte_block(w * h * 4)
img = Image.frombytes('RGBA', (w, h), rgba_data)
rgb_data = img.convert('RGB').convert('RGBA').tobytes()
2021-01-24 11:57:52 +03:00
self.assertEqual(g.disk_cache.total_size, 0)
2017-09-28 17:33:26 +03:00
def png(mode='RGBA'):
buf = BytesIO()
i = img
if mode != i.mode:
i = img.convert(mode)
i.save(buf, 'PNG')
return buf.getvalue()
2017-10-06 20:22:50 +03:00
for mode in 'RGBA RGB'.split():
2017-09-28 17:33:26 +03:00
data = png(mode)
sl(data, f=100, expecting_data=rgb_data if mode == 'RGB' else rgba_data)
2017-09-28 17:33:26 +03:00
2017-10-06 20:22:50 +03:00
for m in 'LP':
img = img.convert(m)
rgba_data = img.convert('RGBA').tobytes()
data = png(m)
sl(data, f=100, expecting_data=rgba_data)
2017-10-06 20:22:50 +03:00
2017-09-28 17:56:06 +03:00
self.ae(l(b'a' * 20, f=100, S=20).partition(':')[0], 'EBADPNG')
2021-01-24 11:57:52 +03:00
s.reset()
self.assertEqual(g.disk_cache.total_size, 0)
2017-10-03 13:40:04 +03:00
def test_load_png_simple(self):
# 1x1 transparent PNG
png_data = standard_b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==')
2018-07-07 05:16:15 +03:00
expected = b'\x00\xff\xff\x7f'
self.ae(load_png_data(png_data), (expected, 1, 1))
s, g, l, sl = load_helpers(self)
2018-07-07 05:16:15 +03:00
sl(png_data, f=100, expecting_data=expected)
# test error handling for loading bad png data
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, b'dsfsdfsfsfd')
2020-12-16 16:27:46 +03:00
def test_gr_operations_with_numbers(self):
s = self.create_screen()
g = s.grman
2021-01-24 11:57:52 +03:00
self.assertEqual(g.disk_cache.total_size, 0)
def li(payload, **kw):
cmd = ','.join('%s=%s' % (k, v) for k, v in kw.items())
res = send_command(s, cmd, payload)
return parse_response_with_ids(res)
code, ids = li('abc', s=1, v=1, f=24, I=1, i=3)
self.ae(code, 'EINVAL')
code, ids = li('abc', s=1, v=1, f=24, I=1)
self.ae((code, ids), ('OK', 'i=1,I=1'))
img = g.image_for_client_number(1)
self.ae(img['client_number'], 1)
self.ae(img['client_id'], 1)
code, ids = li('abc', s=1, v=1, f=24, I=1)
self.ae((code, ids), ('OK', 'i=2,I=1'))
img = g.image_for_client_number(1)
self.ae(img['client_number'], 1)
self.ae(img['client_id'], 2)
code, ids = li('abc', s=1, v=1, f=24, I=1)
self.ae((code, ids), ('OK', 'i=3,I=1'))
code, ids = li('abc', s=1, v=1, f=24, i=5)
self.ae((code, ids), ('OK', 'i=5'))
code, ids = li('abc', s=1, v=1, f=24, I=3)
self.ae((code, ids), ('OK', 'i=4,I=3'))
# Test chunked load with number
self.assertIsNone(li('abcd', s=2, v=2, m=1, I=93))
self.assertIsNone(li('efgh', m=1))
self.assertIsNone(li('ijkx', m=1))
self.ae(li('mnop', m=0), ('OK', 'i=6,I=93'))
img = g.image_for_client_number(93)
self.ae(img['data'], b'abcdefghijkxmnop')
self.ae(img['client_id'], 6)
# test put with number
def put(**kw):
cmd = ','.join('%s=%s' % (k, v) for k, v in kw.items())
cmd = 'a=p,' + cmd
return parse_response_with_ids(send_command(s, cmd))
code, idstr = put(c=2, r=2, I=93)
self.ae((code, idstr), ('OK', 'i=6,I=93'))
code, idstr = put(c=2, r=2, I=94)
self.ae(code, 'ENOENT')
2020-12-16 16:27:46 +03:00
# test delete with number
def delete(ac='N', **kw):
cmd = 'a=d'
if ac:
cmd += ',d={}'.format(ac)
if kw:
cmd += ',' + ','.join('{}={}'.format(k, v) for k, v in kw.items())
send_command(s, cmd)
count = s.grman.image_count
put(i=1), put(i=2), put(i=3), put(i=4), put(i=5)
delete(I=94)
self.ae(s.grman.image_count, count)
delete(I=93)
self.ae(s.grman.image_count, count - 1)
delete(I=1)
self.ae(s.grman.image_count, count - 2)
2021-01-24 11:57:52 +03:00
s.reset()
self.assertEqual(g.disk_cache.total_size, 0)
2020-12-16 16:27:46 +03:00
2017-10-03 13:40:04 +03:00
def test_image_put(self):
cw, ch = 10, 20
2017-10-05 18:24:45 +03:00
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
self.ae(put_image(s, 10, 20)[1], 'OK')
l0 = layers(s)
self.ae(len(l0), 1)
rect_eq(l0[0]['src_rect'], 0, 0, 1, 1)
rect_eq(l0[0]['dest_rect'], -1, 1, -1 + dx, 1 - dy)
self.ae(l0[0]['group_count'], 1)
2017-10-03 13:40:04 +03:00
self.ae(s.cursor.x, 1), self.ae(s.cursor.y, 0)
iid, (code, idstr) = put_ref(s, num_cols=s.columns, x_off=2, y_off=1, width=3, height=5, cell_x_off=3, cell_y_off=1, z=-1, placement_id=17)
self.ae(idstr, f'i={iid},p=17')
2017-10-23 15:03:57 +03:00
l2 = layers(s)
self.ae(len(l2), 2)
rect_eq(l2[0]['src_rect'], 2 / 10, 1 / 20, (2 + 3) / 10, (1 + 5)/20)
left, top = -1 + dx + 3 * dx / cw, 1 - 1 * dy / ch
2017-10-23 15:03:57 +03:00
rect_eq(l2[0]['dest_rect'], left, top, -1 + (1 + s.columns) * dx, top - dy * 5 / ch)
rect_eq(l2[1]['src_rect'], 0, 0, 1, 1)
rect_eq(l2[1]['dest_rect'], -1, 1, -1 + dx, 1 - dy)
self.ae(l2[0]['group_count'], 1), self.ae(l2[1]['group_count'], 1)
2017-10-03 13:40:04 +03:00
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 1)
2021-01-24 11:57:52 +03:00
s.reset()
self.assertEqual(s.grman.disk_cache.total_size, 0)
def test_gr_scroll(self):
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
put_image(s, 10, 20) # a one cell image at (0, 0)
self.ae(len(layers(s)), 1)
for i in range(s.lines):
s.index()
self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1)
for i in range(s.historybuf.ynum - 1):
s.index()
self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1)
s.index()
self.ae(s.grman.image_count, 0)
# Now test with margins
s.reset()
# Test images outside page area untouched
put_image(s, cw, ch) # a one cell image at (0, 0)
for i in range(s.lines - 1):
s.index()
put_image(s, cw, ch) # a one cell image at (0, bottom)
s.set_margins(2, 4) # 1-based indexing
self.ae(s.grman.image_count, 2)
for i in range(s.lines + s.historybuf.ynum):
s.index()
self.ae(s.grman.image_count, 2)
for i in range(s.lines): # ensure cursor is at top margin
s.reverse_index()
# Test clipped scrolling during index
put_image(s, cw, 2*ch, z=-1) # 1x2 cell image
self.ae(s.grman.image_count, 3)
self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0})
s.index(), s.index()
l0 = layers(s)
self.ae(len(l0), 3)
self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.5, 'right': 1.0, 'bottom': 1.0})
s.index()
self.ae(s.grman.image_count, 2)
# Test clipped scrolling during reverse_index
for i in range(s.lines):
s.reverse_index()
put_image(s, cw, 2*ch, z=-1) # 1x2 cell image
self.ae(s.grman.image_count, 3)
self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0})
while s.cursor.y != 1:
s.reverse_index()
s.reverse_index()
self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.5})
s.reverse_index()
self.ae(s.grman.image_count, 2)
2021-01-24 11:57:52 +03:00
s.reset()
self.assertEqual(s.grman.disk_cache.total_size, 0)
def test_gr_reset(self):
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
put_image(s, cw, ch) # a one cell image at (0, 0)
self.ae(len(layers(s)), 1)
s.reset()
self.ae(s.grman.image_count, 0)
put_image(s, cw, ch) # a one cell image at (0, 0)
self.ae(s.grman.image_count, 1)
for i in range(s.lines):
s.index()
s.reset()
self.ae(s.grman.image_count, 1)
def test_gr_delete(self):
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
def delete(ac=None, **kw):
cmd = 'a=d'
if ac:
cmd += ',d={}'.format(ac)
if kw:
cmd += ',' + ','.join('{}={}'.format(k, v) for k, v in kw.items())
send_command(s, cmd)
put_image(s, cw, ch)
delete()
self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1)
delete('A')
self.ae(s.grman.image_count, 0)
2021-01-24 11:57:52 +03:00
self.assertEqual(s.grman.disk_cache.total_size, 0)
iid = put_image(s, cw, ch)[0]
delete('I', i=iid, p=7)
self.ae(s.grman.image_count, 1)
delete('I', i=iid)
self.ae(s.grman.image_count, 0)
2021-01-24 11:57:52 +03:00
self.assertEqual(s.grman.disk_cache.total_size, 0)
iid = put_image(s, cw, ch, placement_id=9)[0]
delete('I', i=iid, p=9)
self.ae(s.grman.image_count, 0)
2021-01-24 11:57:52 +03:00
self.assertEqual(s.grman.disk_cache.total_size, 0)
s.reset()
put_image(s, cw, ch)
put_image(s, cw, ch)
delete('C')
self.ae(s.grman.image_count, 2)
s.cursor_position(1, 1)
delete('C')
self.ae(s.grman.image_count, 1)
delete('P', x=2, y=1)
self.ae(s.grman.image_count, 0)
2021-01-24 11:57:52 +03:00
self.assertEqual(s.grman.disk_cache.total_size, 0)
put_image(s, cw, ch, z=9)
delete('Z', z=9)
self.ae(s.grman.image_count, 0)
2021-01-24 11:57:52 +03:00
self.assertEqual(s.grman.disk_cache.total_size, 0)
# test put + delete + put
iid = 999999
self.ae(put_image(s, cw, ch, id=iid), (iid, 'OK'))
self.ae(put_ref(s, id=iid), (iid, ('OK', f'i={iid}')))
delete('i', i=iid)
self.ae(s.grman.image_count, 1)
self.ae(put_ref(s, id=iid), (iid, ('OK', f'i={iid}')))
delete('I', i=iid)
self.ae(put_ref(s, id=iid), (iid, ('ENOENT', f'i={iid}')))
2020-12-03 19:08:42 +03:00
self.ae(s.grman.image_count, 0)
2021-01-24 11:57:52 +03:00
self.assertEqual(s.grman.disk_cache.total_size, 0)
2021-01-27 11:16:08 +03:00
def test_animation_frame_loading(self):
s = screen = self.create_screen()
g = s.grman
def li(payload='abcdefghijkl'*3, s=4, v=3, f=24, a='f', i=1, **kw):
if s:
kw['s'] = s
if v:
kw['v'] = v
if f:
kw['f'] = f
if i:
kw['i'] = i
kw['a'] = a
cmd = ','.join('%s=%s' % (k, v) for k, v in kw.items())
res = send_command(screen, cmd, payload)
return parse_full_response(res)
def t(code='OK', image_id=1, frame_number=2, **kw):
res = li(**kw)
if code is not None:
2021-01-27 19:21:17 +03:00
self.assertEqual(code, res.code, f'{code} != {res.code}: {res.msg}')
2021-01-27 11:16:08 +03:00
if image_id is not None:
self.assertEqual(image_id, res.image_id)
if frame_number is not None:
self.assertEqual(frame_number, res.frame_number)
# test error on send frame for non-existent image
self.assertEqual(li().code, 'ENOENT')
# test error sending incompatible data formats
self.assertEqual(li(a='t').code, 'OK')
self.assertEqual(g.disk_cache.total_size, 36)
res = li(s=3, v=3, f=32)
self.assertEqual(res.code, 'EINVAL')
self.assertIn('ransparen', res.msg)
# simple new frame
t(payload='2' * 36)
2021-01-27 12:35:08 +03:00
img = g.image_for_client_id(1)
2021-01-28 07:29:44 +03:00
self.assertEqual(img['extra_frames'], ({'gap': 40, 'id': 1, 'data': b'2' * 36},))
2021-01-27 19:21:17 +03:00
# test editing a frame
t(payload='3' * 36, r=2)
img = g.image_for_client_id(1)
2021-01-28 07:29:44 +03:00
self.assertEqual(img['extra_frames'], ({'gap': 40, 'id': 1, 'data': b'3' * 36},))
2021-01-27 19:21:17 +03:00
# test editing part of a frame
t(payload='4' * 12, r=2, s=2, v=2)
img = g.image_for_client_id(1)
2021-01-28 07:29:44 +03:00
self.assertEqual(img['extra_frames'], ({'gap': 40, 'id': 1, 'data': b'444444333333444444333333333333333333'},))
2021-01-27 19:21:17 +03:00
t(payload='5' * 12, r=2, s=2, v=2, x=1, y=1)
img = g.image_for_client_id(1)
2021-01-28 07:29:44 +03:00
self.assertEqual(img['extra_frames'], ({'gap': 40, 'id': 1, 'data': b'444444333555555444555555333333333333'},))
2021-01-27 19:21:17 +03:00
t(payload='3' * 36, r=2)
img = g.image_for_client_id(1)
2021-01-28 07:29:44 +03:00
self.assertEqual(img['extra_frames'], ({'gap': 40, 'id': 1, 'data': b'3' * 36},))
2021-01-27 19:21:17 +03:00
# test loading from previous frame
t(payload='4' * 12, c=2, s=2, v=2, z=101, frame_number=3)
img = g.image_for_client_id(1)
self.assertEqual(img['extra_frames'], (
2021-01-28 07:29:44 +03:00
{'gap': 40, 'id': 1, 'data': b'3' * 36},
{'gap': 101, 'id': 2, 'data': b'444444333333444444333333333333333333'},
2021-01-27 19:21:17 +03:00
))
2021-01-28 07:29:44 +03:00
# test delete of frames
t(payload='5' * 36, frame_number=4)
img = g.image_for_client_id(1)
self.assertEqual(img['extra_frames'], (
{'gap': 40, 'id': 1, 'data': b'3' * 36},
{'gap': 101, 'id': 2, 'data': b'444444333333444444333333333333333333'},
{'gap': 40, 'id': 3, 'data': b'5' * 36},
))
self.assertIsNone(li(a='d', d='f', i=1, r=1))
img = g.image_for_client_id(1)
self.assertEqual(img['data'], b'3' * 36)
self.assertEqual(img['extra_frames'], (
{'gap': 101, 'id': 2, 'data': b'444444333333444444333333333333333333'},
{'gap': 40, 'id': 3, 'data': b'5' * 36},
))
self.assertIsNone(li(a='d', d='f', i=1, r=2))
img = g.image_for_client_id(1)
self.assertEqual(img['data'], b'3' * 36)
self.assertEqual(img['extra_frames'], (
{'gap': 40, 'id': 3, 'data': b'5' * 36},
))
self.assertIsNone(li(a='d', d='f', i=1))
img = g.image_for_client_id(1)
self.assertEqual(img['data'], b'5' * 36)
self.assertFalse(img['extra_frames'])
self.assertIsNone(li(a='d', d='f', i=1))
img = g.image_for_client_id(1)
self.assertEqual(img['data'], b'5' * 36)
self.assertIsNone(li(a='d', d='F', i=1))
2021-01-27 19:21:17 +03:00
self.ae(g.image_count, 0)
self.assertEqual(g.disk_cache.total_size, 0)