#pylint: disable=too-many-locals,no-self-use
#pylint: disable=too-many-public-methods,too-many-statements
"""
Element container tests.
"""
from .test import EbmlTest, UNK_ID
__all__ = ['ContainerTest']
class ContainerTest(EbmlTest):
"Container tests."
def make_test_container(self):
"""Return a container with elements from 5--10, 16--25, 33--40.
The middle element is shrinkable to size 5.
"""
from ebml.element import ElementMaster, ElementUnsupported, ElementVoid
from ebml.atomic import ElementString
elt = ElementMaster.new(UNK_ID)
ElementVoid.of_size(5, elt, 0)
ElementUnsupported.of_size(UNK_ID, 5, elt, 5)
ElementVoid.of_size(6, elt, 10)
ElementString.new_with_value(UNK_ID, '', elt, 16).resize_total(9)
ElementVoid.of_size(8, elt, 25)
ElementUnsupported.of_size(UNK_ID, 7, elt, 33)
ElementVoid.of_size(8, elt, 40)
elt.resize(48)
return elt
def check_child_pos(self, elt, *positions):
"Check elements have the specified start and end positions."
children = iter(elt)
prev_child = None
for start in positions:
try:
child = next(children)
except StopIteration:
child = None
if prev_child is not None:
self.assertEqual(prev_child.pos_end_relative, start)
if child is not None:
self.assertEqual(child.pos_relative, start)
prev_child = child
self.assertEqual(len(positions), len(elt) + 1)
def test_1_containers(self):
"Test Container methods."
from ebml import Inconsistent
from ebml.element import ElementVoid, ElementMaster, ElementUnsupported
from ebml.atomic import ElementString, ElementUnicode
from ebml.tags import MATROSKA_TAGS
ebmlf = self.read_file_data()
seg = ebmlf[1]
elt = self.track_entry_elt(ebmlf)
# Test end_last_child
self.assertEqual(elt.end_last_child, elt.size)
# Two elements starting at the same final position
void = ElementVoid.of_size(20, elt, elt[-1].pos_relative)
self.assertEqual(elt.end_last_child, void.pos_end_relative)
elt.remove_child(void)
# Test children_named, children_with_id
self.assertEqual(list(seg.children_named('SeekHead')),
[seg[0], seg[2], seg[10], seg[12]])
ebml_id = MATROSKA_TAGS['Void'].ebml_id
self.assertEqual(list(seg.children_with_id(ebml_id)),
[seg[1], seg[7]])
# Test children_in_region
children = seg.children_in_region(seg[-1].pos_relative + 1)
self.assertEqual(list(children), [])
children = seg.children_in_region(seg[-2].pos_relative)
self.assertEqual(list(children), seg[-2:])
children = seg.children_in_region(seg[-2].pos_relative-1)
self.assertEqual(list(children), seg[-2:])
children = seg.children_in_region(seg[1].pos_relative,
seg[1].total_size)
self.assertEqual(list(children), seg[1:2])
children = seg.children_in_region(seg[1].pos_relative,
seg[1].total_size+1)
self.assertEqual(list(children), seg[1:3])
children = seg.children_in_region(seg[-2].pos_relative, 1000)
self.assertEqual(list(children), seg[-2:])
children = seg.children_in_region(0, seg[3].pos_relative, novoids=True)
self.assertEqual(list(children), [seg[0], seg[2]])
# Test adding and moving children
void = ElementVoid.of_size(20)
old_end = seg.end_last_child
seg.add_child(void)
self.assertIs(void.parent, seg)
self.assertEqual(void.pos_relative, old_end)
seg.remove_child(void)
seg.add_child(void, 3)
self.assertEqual(void.pos_relative, 3)
seg.remove_child(void)
elt = seg[3]
seg.move_child(elt, 3)
self.assertEqual(elt.pos_relative, 3)
self.assertIs(seg[1], elt)
seg.move_child(1, 24)
self.assertEqual(elt.pos_relative, 24)
self.assertIs(seg[2], elt)
# Test find_gap()
elt = ElementMaster.new(UNK_ID)
# No children
#self.assertEqual(elt.find_gap(100), (0, MAX_DATA_SIZE, None))
self.assertEqual(elt.find_gap(100), None)
self.assertEqual(elt.find_gap(100, region_size=150), (0, 150, None))
self.assertEqual(elt.find_gap(100, region_size=50), None)
ElementUnsupported.of_size(UNK_ID, 20, elt, 5)
elt1 = ElementString.new_with_value(UNK_ID, 'abcde', elt, 31)
elt1.resize_total(20)
# gaps: 0--5, 25--31, 51--
self.assertEqual(elt.find_gap(100, start=51, region_size=150),
(51, 150, None))
# Gap at beginning
self.assertEqual(elt.find_gap(5), (0, 5, None))
self.assertEqual(elt.find_gap(5, start=25), None)
self.assertEqual(elt.find_gap(5, start=25, region_size=35),
(51, 9, elt[1]))
self.assertEqual(elt.find_gap(5, start=25, one_byte_ok=True),
(25, 6, None))
# Gap in the middle
self.assertEqual(elt.find_gap(6), (25, 6, elt[0]))
self.assertEqual(elt.find_gap(5), (0, 5, None))
# Gap at the end
self.assertEqual(elt.find_gap(4, region_size=55), (51, 4, elt[1]))
self.assertEqual(elt.find_gap(7), None)
self.assertEqual(elt.find_gap(7, region_size=60), (51, 9, elt[1]))
# No solution
self.assertEqual(elt.find_gap(7, region_size=57), None)
self.assertEqual(elt.find_gap(7, region_size=59), None)
self.assertEqual(elt.find_gap(7, region_size=58),
(51, 7, elt[1]))
# Several gaps: 0--5, 25--31, 51--55, 75--
ElementUnsupported.of_size(UNK_ID, 20, elt, 55)
self.assertEqual(elt.find_gap(3), (0, 5, None))
self.assertEqual(elt.find_gap(3, one_byte_ok=True), (51, 4, elt[1]))
self.assertEqual(elt.find_gap(4), (51, 4, elt[1]))
self.assertEqual(elt.find_gap(5), (0, 5, None))
self.assertEqual(elt.find_gap(5, start=10, one_byte_ok=True),
(25, 6, elt[0]))
self.assertEqual(elt.find_gap(6), (25, 6, elt[0]))
self.assertEqual(elt.find_gap(7), None)
self.assertEqual(elt.find_gap(7, region_size=100), (75, 25, elt[2]))
# Shrinking child: gaps 0--5, 25--31, 41--55, 75--
self.assertEqual(elt.find_gap(4, shrink=True, one_byte_ok=True),
(0, 5, None))
self.assertEqual(elt.find_gap(6, shrink=True), (25, 6, elt[0]))
self.assertEqual(elt.find_gap(7, shrink=True), (41, 14, elt[1]))
self.assertEqual(elt.find_gap(14, shrink=True), (41, 14, elt[1]))
self.assertEqual(elt.find_gap(15, region_size=100, shrink=True),
(75, 25, elt[2]))
# Test place_child()
# No shrink necessary
elt = self.make_test_container()
child = ElementVoid.of_size(3)
elt.place_child(child, shrink_child=False, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (0, 3))
elt = self.make_test_container()
child = ElementVoid.of_size(4)
elt.place_child(child, shrink_child=False, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (10, 4))
elt = self.make_test_container()
child = ElementVoid.of_size(4)
elt.place_child(child, shrink_child=False, shrink_previous=False,
grow_child=True)
self.assertEqual((child.pos_relative, child.total_size), (0, 5))
elt = self.make_test_container()
child = ElementVoid.of_size(7)
with self.assertRaises(Inconsistent):
elt.place_child(child, region_size=40, shrink_child=False,
shrink_previous=False, grow_child=False)
elt.place_child(child, shrink_child=False, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (40, 7))
elt = self.make_test_container()
child = ElementVoid.of_size(7)
elt.place_child(child, shrink_child=False, shrink_previous=False,
grow_child=True)
self.assertEqual((child.pos_relative, child.total_size), (25, 8))
# Shrink child
elt = self.make_test_container()
child = ElementVoid.of_size(10)
elt.place_child(child, shrink_child=True, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (0, 2))
elt = self.make_test_container()
child = ElementString.new_with_value(UNK_ID, 'a') # min size == 6
child.resize_total(10)
elt.place_child(child, shrink_child=True, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (10, 6))
elt = self.make_test_container()
child = ElementString.new_with_value(UNK_ID, 'ab') # min size == 7
child.resize_total(10)
elt.place_child(child, shrink_child=True, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (25, 8))
elt = self.make_test_container()
child = ElementString.new_with_value(UNK_ID, 'abcd') # min size == 9
child.resize_total(10)
with self.assertRaises(Inconsistent):
elt.place_child(child, region_size=40, shrink_child=True,
shrink_previous=False, grow_child=False)
elt.place_child(child, shrink_child=True, shrink_previous=False,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (40, 10))
# Shrink previous
elt = self.make_test_container()
child = ElementString.new_with_value(UNK_ID, 'abcd') # min size == 9
elt.place_child(child, shrink_child=False, shrink_previous=True,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (24, 9))
self.assertEqual(elt.find(16).total_size, 8)
elt = self.make_test_container()
child = ElementString.new_with_value(UNK_ID, 'abcd') # min size == 9
child.resize_total(10)
elt.place_child(child, shrink_child=False, shrink_previous=True,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (23, 10))
self.assertEqual(elt.find(16).total_size, 7)
elt = self.make_test_container()
child = ElementString.new_with_value(UNK_ID, 'abcd') # min size == 9
child.resize_total(10)
elt.place_child(child, shrink_child=True, shrink_previous=True,
grow_child=False)
self.assertEqual((child.pos_relative, child.total_size), (24, 9))
self.assertEqual(elt.find(16).total_size, 8)
elt = self.make_test_container()
elt.remove_child(elt.find(16))
ElementMaster.new(UNK_ID, elt, 16, size=2) # total size == 7
# gap from 23 -- 33, shrinks to 22--33
child = ElementString.new_with_value(UNK_ID, 'abcd')
child.resize_total(11)
with self.assertRaises(Inconsistent):
# Won't find the gap with grow_child=False
elt.place_child(child, region_size=40, shrink_child=False,
shrink_previous=True, grow_child=False)
elt.place_child(child, shrink_child=False, shrink_previous=True,
grow_child=True)
self.assertEqual((child.pos_relative, child.total_size), (22, 11))
self.assertEqual(elt.find(16).total_size, 6)
# Test get_overlapping()
elt = self.make_test_container()
self.assertEqual(elt.get_overlapping(), frozenset())
# Negative position
child = ElementUnicode.new_with_value('MuxingApp', 'abcd', elt, -1)
self.assertEqual(elt.get_overlapping(), frozenset({child}))
elt.add_child(child, -1)
self.assertRaises(Inconsistent, elt.get_overlapping, ('MuxingApp', ))
# Two fixed
elt.add_child(child, 100)
ElementUnicode.new_with_value('MuxingApp', 'efgh', elt, 105)
self.assertRaises(Inconsistent, elt.get_overlapping, ('MuxingApp', ))
# One fixed
elt = self.make_test_container()
result = frozenset({elt[0], elt[1], elt[2]})
child = ElementUnicode.new_with_value('MuxingApp', 'abcd', elt, 2)
child.resize_total(10)
self.assertEqual(elt.get_overlapping(('MuxingApp',)), result)
# None fixed
elt = self.make_test_container()
result = frozenset({elt[0], elt[1], elt[2]})
child = ElementUnicode.new_with_value('MuxingApp', 'abcd', elt, 2)
child.resize_total(10) # this one is largest
self.assertEqual(elt.get_overlapping(), result)
elt = self.make_test_container()
child = ElementVoid.of_size(2, elt, 4) # this one is smallest
self.assertEqual(elt.get_overlapping(), frozenset({child}))
# One-byte gap
elt = self.make_test_container()
result = frozenset({elt[0]})
elt[0].resize_total(4)
self.assertEqual(elt.get_overlapping(), result)
def test_2_rearrange(self):
"Test Container.rearrange() and its relatives."
from ebml.element import ElementMaster, ElementVoid, ElementUnsupported
# -- Test rearrange_resize()
# Already arranged
elt = self.make_test_container()
elt.resize(40)
elt.rearrange_resize()
self.check_child_pos(elt, 0, 5, 10, 16, 25, 33, 40)
self.assertEqual(elt.size, 40)
# Shrink
elt = self.make_test_container()
elt.resize(50)
elt.rearrange_resize()
self.check_child_pos(elt, 0, 5, 10, 16, 25, 33, 40)
self.assertEqual(elt.size, 40)
# Grow
elt = self.make_test_container()
elt.resize(30)
elt.rearrange_resize()
self.check_child_pos(elt, 0, 5, 10, 16, 25, 33, 40)
self.assertEqual(elt.size, 40)
# Grow because we're lazy
elt = self.make_test_container()
elt.resize(40)
ElementUnsupported.of_size(UNK_ID, 15, elt, 24)
elt.rearrange_resize()
self.check_child_pos(elt, 0, 5, 10, 16, 24, 39, 46)
self.assertEqual(elt.size, 46)
# Grow because we're forced to
elt = self.make_test_container()
elt.resize(40)
ElementUnsupported.of_size(UNK_ID, 35, elt, 0)
elt.rearrange_resize(prefer_grow=False)
self.check_child_pos(elt, 0, 35, 40, 45, 52)
self.assertEqual(elt.size, 52)
elt = self.make_test_container()
elt.resize(16)
elt.rearrange_resize(prefer_grow=False)
self.check_child_pos(elt, 0, 5, 10, 17)
self.assertEqual(elt.size, 17)
# Add a Void at the end
elt = self.make_test_container()
elt.resize(50)
elt.rearrange_resize(allow_shrink=False)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 33, 40, 50)
self.assertEqual(elt.size, 50)
elt = self.make_test_container()
elt.resize(41)
elt.rearrange_resize(allow_shrink=False)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 33, 40, 42)
self.assertEqual(elt.size, 42)
elt = self.make_test_container()
elt.resize(20)
elt.rearrange_resize(prefer_grow=False, allow_shrink=False)
self.check_child_pos(elt, 0, 5, 10, 17, 20)
self.assertEqual(elt.size, 20)
elt = self.make_test_container()
elt.resize(18)
elt.rearrange_resize(prefer_grow=False, allow_shrink=False)
self.check_child_pos(elt, 0, 5, 10, 17, 19)
self.assertEqual(elt.size, 19)
# -- Test rearrange() --
# No children
elt = ElementMaster.new(UNK_ID)
ElementVoid.of_size(5, elt, 0)
elt.rearrange()
self.assertEqual(len(elt), 0)
# Eliminate overlaps (part 1 of the algorithm)
# Push everything forward
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 35, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 35, 40, 49, 56)
# Push first and second forward less
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 24, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 24, 29, 38, 45)
# Push first and second forward and shrink second
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 23, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 23, 28, 33, 40)
# Push first and second forward and shrink second less
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 22, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 22, 27, 33, 40)
# No need to shrink second
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 19, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 19, 24, 33, 40)
# Push first and second forward with 1-byte gap
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 18, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 18, 23, 32, 39)
# Push first and second forward with 4-byte gap
elt = self.make_test_container()
ElementUnsupported.of_size(UNK_ID, 15, elt, 0)
elt.rearrange()
self.check_child_pos(elt, 0, 15, 20, 29, 33, 40)
# One-byte gap at beginning
elt = self.make_test_container()
ElementUnsupported.of_size(0x012233, 4, elt, 1)
elt.rearrange()
self.check_child_pos(elt, 0, 4, 9, 16, 25, 33, 40)
# Fit in goal size (part 2 of the algorithm)
# Everything already fits
elt = self.make_test_container()
elt.rearrange(40)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 33, 40)
# One-byte gap
elt = self.make_test_container()
elt.rearrange(41)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 32)
# Move last
elt = self.make_test_container()
elt.rearrange(39)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 32)
elt = self.make_test_container()
elt.rearrange(34)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 32)
elt = self.make_test_container()
elt.rearrange(32)
self.check_child_pos(elt, 0, 5, 10, 16, 25, 32)
# Move last with 1-byte gap (shrinks second)
elt = self.make_test_container()
elt.rearrange(33)
self.check_child_pos(elt, 0, 5, 10, 16, 21, 28)
# Move last and shrink second
elt = self.make_test_container()
elt.rearrange(31)
self.check_child_pos(elt, 0, 5, 10, 16, 21, 28)
elt = self.make_test_container()
elt.rearrange(30)
self.check_child_pos(elt, 0, 5, 10, 16, 21, 28)
elt = self.make_test_container()
elt.rearrange(28)
self.check_child_pos(elt, 0, 5, 10, 16, 21, 28)
# Move two last
elt = self.make_test_container()
elt.rearrange(29)
self.check_child_pos(elt, 0, 5, 10, 15, 22)
elt = self.make_test_container()
elt.rearrange(27)
self.check_child_pos(elt, 0, 5, 10, 15, 22)
elt = self.make_test_container()
elt.rearrange(24)
self.check_child_pos(elt, 0, 5, 10, 15, 22)
elt = self.make_test_container()
elt.rearrange(22)
self.check_child_pos(elt, 0, 5, 10, 15, 22)
# Maximal shrink
elt = self.make_test_container()
elt.rearrange(23)
self.check_child_pos(elt, 0, 5, 10, 17)
elt = self.make_test_container()
elt.rearrange(21)
self.check_child_pos(elt, 0, 5, 10, 17)
elt = self.make_test_container()
elt.rearrange(17)
self.check_child_pos(elt, 0, 5, 10, 17)
# Nothing to do about the trailing 1-byte gap
elt = self.make_test_container()
elt.rearrange(18)
self.check_child_pos(elt, 0, 5, 10, 17)
# Doesn't fit
elt = self.make_test_container()
elt.rearrange(16)
self.check_child_pos(elt, 0, 5, 10, 17)
# Recursively rearrange (part 3 of the algorithm)
elt = self.make_test_container()
elt2 = self.make_test_container()
elt.add_child(elt2, 7)
elt.rearrange()
self.check_child_pos(elt, 0, 5, 10, 63, 72, 79)
self.check_child_pos(elt2, 0, 5, 10, 16, 25, 33, 40, 48)
self.assertEqual(elt2.size, 48)
elt = self.make_test_container()
elt2 = self.make_test_container()
elt.add_child(elt2, 7)
elt.rearrange(0)
self.check_child_pos(elt, 0, 5, 27, 32, 39)
self.check_child_pos(elt2, 0, 5, 10, 17)
self.assertEqual(elt2.size, 17)
# Test expand_header() and element at negative position
elt = self.make_test_container()
size = elt.total_size
elt.remove_child(0)
elt.remove_child(-1)
ElementUnsupported.of_size(UNK_ID, 5, elt, 0)
elt.expand_header(4)
self.assertEqual(elt.total_size, size)
self.check_child_pos(elt, -3, 2, 7, 13, 22, 30, 37)
elt.rearrange()
self.check_child_pos(elt, 0, 5, 10, 13, 22, 30, 37)