-
Thomas Holene Løkkeborg authoredThomas Holene Løkkeborg authored
test_soi.py 9.98 KiB
"""Test the soi module.
To view the SOI during manual testing the following snippet is useful:
```python
from soitool.inline_editable_soi_view import InlineEditableSOIView
self.soi.reorganize()
view = InlineEditableSOIView(self.soi)
view.show()
app.exec_()
```
"""
import unittest
from PySide2.QtWidgets import QApplication, QWidget
from PySide2 import QtGui
from PySide2.QtGui import QPalette, QColor
from soitool.soi import SOI, ModuleLargerThanBinError
from soitool.modules.module_base import ModuleBase
if isinstance(QtGui.qApp, type(None)):
app = QApplication([])
else:
app = QtGui.qApp
class Meta(type(ModuleBase), type(QWidget)):
"""Used as a metaclass to enable multiple inheritance."""
class TestModule(ModuleBase, QWidget, metaclass=Meta):
"""A simple module of given width, height and color for testing."""
def __init__(self, color, width, height, *args, **kwargs):
super(TestModule, self).__init__(*args, **kwargs)
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.Window, QColor(color))
self.setPalette(palette)
self.setGeometry(0, 0, width, height)
def get_size(self):
"""Override."""
size = self.size()
return size.width(), size.height()
def set_pos(self, pos):
"""Override."""
self.move(pos)
def render_onto_pdf(self):
"""Not used."""
raise NotImplementedError()
# The modules below have sizes that make the ideal for testing.
# Sorting them by width should yield
# 1. wide_module
# 2. big_module
# 3. tall_module
# Sorting them by height should yield
# 1. tall_module
# 2. big_module
# 3. wide_module
# And sorting them by area should yield
# 1. big_module
# 2. tall_module
# 3. wide_module
# Note that when sorting by area the two modules 'tall_module' and
# 'wide_module' have the same area, but 'tall_module' should come first
# because sorting should be stable
# https://en.wikipedia.org/wiki/Category:Stable_sorts
# NOTE the only meta information necessary here is "name", if we assume
# automatic sorting..
TEST_MODULES = [
{
"widget": TestModule("red", 100, 400),
"meta": {"x": 0, "y": 0, "page": 1, "name": "tall_module"},
},
{
"widget": TestModule("blue", 400, 100),
"meta": {"x": 0, "y": 0, "page": 1, "name": "wide_module"},
},
{
"widget": TestModule("orange", 300, 300),
"meta": {"x": 0, "y": 0, "page": 1, "name": "big_module"},
},
]
class TestSOI(unittest.TestCase):
"""TestCase for the SOI class."""
def setUp(self):
"""Prepare SOI to test on.
Note that the SOI can be modified by individual tests before use, this
simply sets the default.
"""
self.soi = SOI(
modules=list(TEST_MODULES),
attachments=list(TEST_MODULES),
algorithm_sort="none",
placement_strategy="auto",
)
def sort_modules_and_assert_order(self, sorting_algorithm, expected_order):
"""Sort the SOI modules using algorithm and expect order.
This function was created to reduce code-duplication only.
Parameters
----------
sorting_algorithm : str
name of sorting algorithm to pass to the SOI
expected_order : list of string names
names of modules in the expected order
"""
self.soi.algorithm_sort = sorting_algorithm
self.soi.sort_modules()
self.assertEqual(
expected_order,
[module["meta"]["name"] for module in self.soi.modules],
f"Modules should have expected order when sorting algorithm is "
"set to '{sorting_algorithm}'",
)
def test_sort_modules_none(self):
"""Test sort algorithm 'none'."""
self.sort_modules_and_assert_order(
"none", ["tall_module", "wide_module", "big_module"]
)
def test_sort_modules_width(self):
"""Test sort algorithm 'width'."""
self.sort_modules_and_assert_order(
"width", ["wide_module", "big_module", "tall_module"]
)
def test_sort_modules_height(self):
"""Test sort algorithm 'height'."""
self.sort_modules_and_assert_order(
"height", ["tall_module", "big_module", "wide_module"]
)
def test_sort_modules_area(self):
"""Test sort algorithm 'area'."""
self.sort_modules_and_assert_order(
"area", ["big_module", "tall_module", "wide_module"]
)
def test_reorganization_listener(self):
"""Test reorganization listener functionality of SOI."""
listener_was_called = False
def listener():
# nonlocal is required to access variable in nesting function's
# scope
nonlocal listener_was_called
listener_was_called = True
self.soi.add_reorganization_listener(listener)
self.soi.reorganize()
self.assertTrue(listener_was_called)
def test_reorganize_of_too_big_module(self):
"""Test exception thrown by reorganize in case of too large modules."""
maximum_width = self.soi.CONTENT_WIDTH
maximum_height = self.soi.CONTENT_HEIGHT - self.soi.HEADER_HEIGHT
module_too_wide = {
"widget": TestModule("red", maximum_width + 1, 100),
"meta": {"x": 0, "y": 0, "page": 1, "name": "too_wide"},
}
module_too_tall = {
"widget": TestModule("red", 100, maximum_height + 1),
"meta": {"x": 0, "y": 0, "page": 1, "name": "too_tall"},
}
module_maximum_size = {
"widget": TestModule("red", maximum_width, maximum_height),
"meta": {"x": 0, "y": 0, "page": 1, "name": "maximum_size"},
}
# too wide module should raise exception
self.soi.modules = [module_too_wide]
self.assertRaises(
ModuleLargerThanBinError, self.soi.reorganize,
)
# too tall module should raise exception
self.soi.modules = [module_too_tall]
self.assertRaises(
ModuleLargerThanBinError, self.soi.reorganize,
)
# module with maximum size shouldn't cause any exception
self.soi.modules = [module_maximum_size]
try:
self.soi.reorganize()
except ModuleLargerThanBinError:
self.fail(
"ModuleLargerThanBinError was raised even though module was "
"not too large"
)
def test_packing(self):
"""Test that 4 test modules are packed as we expect them to be.
Only the algorithms "MaxRectsBl" and "GuillotineBssfSas" are tested.
This is because if these two work, we can assume other choices of
algorithm will work as well. The modules in the SOI have been chosen
such that these two algorithms should yield different packings.
This test was constructed by first running the algorithms on our
modules and inspecting the resulting packing, and then this packing is
used for testing. This way if reorganize gives a different packing in
the future this test should break. It is required that reorganize
yields the same packing given the same input.
Note that this test does not check the actual module widget positions,
only the meta positions.
"""
# append module of maximum size
maximum_width = self.soi.CONTENT_WIDTH
maximum_height = self.soi.CONTENT_HEIGHT - self.soi.HEADER_HEIGHT
module_maximum_size = {
"widget": TestModule("red", maximum_width, maximum_height),
"meta": {"x": 0, "y": 0, "page": 1, "name": "maximum_size"},
}
self.soi.modules.append(module_maximum_size)
# store modules so we can use the exact same input for both algorithms
input_modules = self.soi.modules
# test packing with Guillotine algorithm
expected_module_packing_metadata_guillotinebssfsas = [
{"x": 0, "y": 0, "page": 1, "name": "tall_module"},
{"x": 100, "y": 0, "page": 1, "name": "wide_module"},
{"x": 100, "y": 100, "page": 1, "name": "big_module"},
{"x": 0, "y": 0, "page": 2, "name": "maximum_size"},
]
self.soi.algorithm_pack = "GuillotineBssfSas"
self.soi.reorganize()
self.assertEqual(
expected_module_packing_metadata_guillotinebssfsas,
[module["meta"] for module in self.soi.modules],
)
# restore modules and test packing with MaxRects
self.soi.modules = input_modules
expected_module_packing_metadata_maxrectsbl = [
{"x": 0, "y": 0, "page": 1, "name": "tall_module"},
{"x": 100, "y": 0, "page": 1, "name": "wide_module"},
{"x": 500, "y": 0, "page": 1, "name": "big_module"},
{"x": 0, "y": 0, "page": 2, "name": "maximum_size"},
]
self.soi.algorithm_pack = "MaxRectsBl"
self.soi.reorganize()
self.assertEqual(
expected_module_packing_metadata_maxrectsbl,
[module["meta"] for module in self.soi.modules],
)
def test_module_widget_update_position(self):
"""Test update of real widget position to match meta position."""
page_2_module = {
"widget": TestModule("red", 100, 100),
"meta": {"x": 100, "y": 100, "page": 2, "name": "maximum_size"},
}
self.soi.modules = [page_2_module]
# using [0] below because we're guaranteed there is only one
# module, and it's the one we want to test
self.soi.update_module_widget_position(self.soi.modules[0])
# calculate expected widget positions
expected_x = page_2_module["meta"]["x"] + self.soi.PADDING
expected_y = (
page_2_module["meta"]["y"] + self.soi.PADDING * 3 + self.soi.HEIGHT
)
self.assertEqual(
expected_x, self.soi.modules[0]["widget"].pos().x(),
)
self.assertEqual(
expected_y, self.soi.modules[0]["widget"].pos().y(),
)