Compare commits
22 Commits
xfstestsdb
...
main
Author | SHA1 | Date | |
---|---|---|---|
4600258721 | |||
79fae64f37 | |||
2e85870c87 | |||
7e6f944cde | |||
dc6f5f54c3 | |||
06d10cf883 | |||
3cc3412c6d | |||
11941c3bd3 | |||
ce2c36a0dd | |||
6c8e155a44 | |||
521c96b432 | |||
704eb08091 | |||
bac99c9c54 | |||
2352bc3512 | |||
6fcb4eb5e7 | |||
70274c448c | |||
bf668fc936 | |||
64abc86fee | |||
b05a9ecc82 | |||
5621847451 | |||
f951d4c998 | |||
daae654b8e |
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our row widgets and factories."""
|
||||
import datetime
|
||||
import unittest
|
||||
import xfstestsdb.gtk.row
|
||||
import tests.xunit
|
||||
|
@ -375,3 +376,67 @@ class TestSummaryFactory(unittest.TestCase):
|
|||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertFalse(self.listitem.get_child().has_css_class("accent"))
|
||||
|
||||
|
||||
class TestSidebarFactory(unittest.TestCase):
|
||||
"""Tests our Gtk.Factory to show Xfstsets runs in the sidebar."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.devlist = xfstestsdb.gtk.tree.DeviceRunsList("/dev/vda1")
|
||||
self.treeitem = Gtk.TreeListRow()
|
||||
self.treeitem.get_item = unittest.mock.Mock(return_value=self.devlist)
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.treeitem)
|
||||
|
||||
self.factory = xfstestsdb.gtk.row.SidebarFactory()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the factory was initialized correctly."""
|
||||
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
|
||||
|
||||
def test_setup(self):
|
||||
"""Test that thefactory implements the 'setup' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
expander = self.listitem.get_child()
|
||||
self.assertIsInstance(expander, Gtk.TreeExpander)
|
||||
self.assertIsInstance(expander.get_child(), Gtk.Label)
|
||||
self.assertEqual(expander.get_child().props.yalign, 0.75)
|
||||
self.assertTrue(expander.get_child().has_css_class("numeric"))
|
||||
|
||||
def test_bind_device_list(self):
|
||||
"""Test binding to a DeviceRunsList object."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_child().get_text(),
|
||||
"/dev/vda1")
|
||||
self.assertEqual(self.listitem.get_child().get_list_row(),
|
||||
self.treeitem)
|
||||
self.assertFalse(self.listitem.props.selectable)
|
||||
|
||||
def test_bind_xfstests_run(self):
|
||||
"""Test binding to an XfstestsRun object."""
|
||||
now = datetime.datetime.now()
|
||||
self.devlist.add_run(1, now)
|
||||
self.treeitem.get_item.return_value = self.devlist[0]
|
||||
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_child().get_text(),
|
||||
f"#1: {now.strftime('%T')}")
|
||||
self.assertEqual(self.listitem.get_child().get_list_row(),
|
||||
self.treeitem)
|
||||
self.assertTrue(self.listitem.props.selectable)
|
||||
|
||||
def test_unbind(self):
|
||||
"""Test that the factory implements the 'unbind' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_child().get_text(), "")
|
||||
|
||||
def test_teardown(self):
|
||||
"""Test that the factory implements the 'teardown' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("teardown", self.listitem)
|
||||
self.assertIsNone(self.listitem.get_child())
|
||||
|
|
246
tests/gtk/test_sidebar.py
Normal file
246
tests/gtk/test_sidebar.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our sidebar test selector."""
|
||||
import datetime
|
||||
import unittest
|
||||
import xfstestsdb.gtk.sidebar
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestRunidView(unittest.TestCase):
|
||||
"""Test the RunidView class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
|
||||
self.today = datetime.date.today()
|
||||
self.devlist = xfstestsdb.gtk.tree.DateDeviceList(self.xfstestsdb.sql,
|
||||
self.today)
|
||||
self.view = xfstestsdb.gtk.sidebar.RunidView(self.devlist)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the RunidView was set up properly."""
|
||||
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
|
||||
self.assertEqual(self.view.props.vexpand, True)
|
||||
|
||||
def test_listview(self):
|
||||
"""Test that the listview child is set up properly."""
|
||||
self.assertIsInstance(self.view._view, Gtk.ListView)
|
||||
self.assertIsInstance(self.view._selection, Gtk.SingleSelection)
|
||||
self.assertIsInstance(self.view._view.props.factory,
|
||||
xfstestsdb.gtk.row.SidebarFactory)
|
||||
|
||||
self.assertTrue(self.view._view.props.single_click_activate)
|
||||
self.assertTrue(self.view._view.has_css_class("navigation-sidebar"))
|
||||
self.assertTrue(self.view._view.has_css_class("background"))
|
||||
|
||||
self.assertEqual(self.view._view.props.model,
|
||||
self.view._selection)
|
||||
self.assertEqual(self.view.props.child, self.view._view)
|
||||
|
||||
def test_model_property(self):
|
||||
"""Test the model property."""
|
||||
self.assertEqual(self.view.model, self.devlist)
|
||||
self.assertEqual(self.view._selection.props.model,
|
||||
self.devlist.treemodel)
|
||||
|
||||
self.view.model = None
|
||||
self.assertIsNone(self.view._selection.props.model)
|
||||
|
||||
self.view.model = self.devlist
|
||||
self.assertEqual(self.view._selection.props.model,
|
||||
self.devlist.treemodel)
|
||||
|
||||
def test_runid_property(self):
|
||||
"""Test the runid property."""
|
||||
self.view.model = self.devlist
|
||||
self.assertEqual(self.view.runid, 0)
|
||||
|
||||
self.view._view.emit("activate", 1)
|
||||
self.assertEqual(self.view.runid, 2)
|
||||
self.view._view.emit("activate", 2)
|
||||
self.assertEqual(self.view.runid, 3)
|
||||
self.view._view.emit("activate", 4)
|
||||
self.assertEqual(self.view.runid, 1)
|
||||
self.view._view.emit("activate", 6)
|
||||
self.assertEqual(self.view.runid, 4)
|
||||
|
||||
def test_expand_collapse(self):
|
||||
"""Test expanding and collapsing child rows."""
|
||||
self.view.model = self.devlist
|
||||
|
||||
self.assertTrue(self.view._selection[0].get_expanded())
|
||||
self.assertEqual(self.view.runid, 0)
|
||||
|
||||
self.view._view.emit("activate", 0)
|
||||
self.assertFalse(self.view._selection[0].get_expanded())
|
||||
self.assertEqual(self.view.runid, 0)
|
||||
|
||||
self.view._view.emit("activate", 0)
|
||||
self.assertTrue(self.view._selection[0].get_expanded())
|
||||
self.assertEqual(self.view.runid, 2)
|
||||
|
||||
|
||||
class TestCalendarView(unittest.TestCase):
|
||||
"""Test the CalendarView class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda4"])
|
||||
|
||||
query = "UPDATE xfstests_runs SET timestamp=? WHERE runid=?"
|
||||
dtime = datetime.datetime.now().replace(year=2023, month=1)
|
||||
self.xfstestsdb.sql(query, dtime.replace(day=1), 1)
|
||||
self.xfstestsdb.sql(query, dtime.replace(day=8), 2)
|
||||
self.xfstestsdb.sql(query, dtime.replace(day=15), 3)
|
||||
|
||||
self.sidebar = xfstestsdb.gtk.sidebar.CalendarView(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the calendar view was set up properly."""
|
||||
self.assertIsInstance(self.sidebar, Gtk.Box)
|
||||
self.assertEqual(self.sidebar.props.spacing, 6)
|
||||
self.assertEqual(self.sidebar.props.orientation,
|
||||
Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(self.sidebar.sql, self.xfstestsdb.sql)
|
||||
|
||||
def test_calendar(self):
|
||||
"""Test the calendar widget."""
|
||||
self.assertIsInstance(self.sidebar._calendar, Gtk.Calendar)
|
||||
self.assertEqual(self.sidebar.get_first_child(),
|
||||
self.sidebar._calendar)
|
||||
|
||||
today = datetime.date.today()
|
||||
self.assertTrue(self.sidebar._calendar.get_day_is_marked(today.day))
|
||||
|
||||
def test_view(self):
|
||||
"""Test the runid view widget."""
|
||||
self.assertIsInstance(self.sidebar._view,
|
||||
xfstestsdb.gtk.sidebar.RunidView)
|
||||
self.assertIsInstance(self.sidebar._view.model,
|
||||
xfstestsdb.gtk.tree.DateDeviceList)
|
||||
self.assertEqual(self.sidebar._calendar.get_next_sibling(),
|
||||
self.sidebar._view)
|
||||
|
||||
self.assertEqual(self.sidebar._view.model.date, datetime.date.today())
|
||||
|
||||
def test_marked_days(self):
|
||||
"""Test marking days in the calendar."""
|
||||
gl_date = GLib.DateTime.new_local(2023, 1, 10, 0, 0, 0)
|
||||
self.sidebar._calendar.select_day(gl_date)
|
||||
|
||||
for signal in ["next-month", "next-year", "prev-month", "prev-year"]:
|
||||
with self.subTest(signal=signal):
|
||||
with unittest.mock.patch.object(self.sidebar._calendar,
|
||||
"clear_marks") as mock_clear:
|
||||
with unittest.mock.patch.object(self.sidebar._calendar,
|
||||
"mark_day") as mock_mark:
|
||||
self.sidebar._calendar.emit(signal)
|
||||
mock_clear.assert_called()
|
||||
mock_mark.assert_has_calls([unittest.mock.call(1),
|
||||
unittest.mock.call(8),
|
||||
unittest.mock.call(15)])
|
||||
|
||||
def test_select_day(self):
|
||||
"""Test selecting a day in the calendar."""
|
||||
gl_now = GLib.DateTime.new_now_local()
|
||||
self.sidebar._calendar.select_day(gl_now.add_days(-1))
|
||||
|
||||
today = datetime.date.today()
|
||||
self.assertEqual(self.sidebar._view.model.date,
|
||||
today - datetime.timedelta(days=1))
|
||||
|
||||
def test_runid_property(self):
|
||||
"""Test the runid property."""
|
||||
self.assertEqual(self.sidebar.runid, 0)
|
||||
self.sidebar._view.runid = 42
|
||||
self.assertEqual(self.sidebar.runid, 42)
|
||||
|
||||
|
||||
class TestSidebar(unittest.TestCase):
|
||||
"""Test the Sidebar class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
self.sidebar = xfstestsdb.gtk.sidebar.Sidebar(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test taht the sidebar was set up properly."""
|
||||
self.assertIsInstance(self.sidebar, Gtk.Box)
|
||||
self.assertEqual(self.sidebar.props.orientation,
|
||||
Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(self.sidebar.sql, self.xfstestsdb.sql)
|
||||
|
||||
def test_stack_switcher(self):
|
||||
"""Test the Gtk.StackSwitcher."""
|
||||
self.assertIsInstance(self.sidebar._switcher, Gtk.StackSwitcher)
|
||||
self.assertEqual(self.sidebar._switcher.props.stack,
|
||||
self.sidebar._stack)
|
||||
self.assertTrue(self.sidebar._switcher.has_css_class("large-icons"))
|
||||
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_top, 6)
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_bottom, 6)
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_start, 80)
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_end, 80)
|
||||
|
||||
self.assertEqual(self.sidebar.get_first_child(),
|
||||
self.sidebar._switcher)
|
||||
|
||||
def test_separator(self):
|
||||
"""Test the Gtk.Separator between sidebar widgets."""
|
||||
sep = self.sidebar._switcher.get_next_sibling()
|
||||
self.assertIsInstance(sep, Gtk.Separator)
|
||||
self.assertEqual(sep.props.orientation, Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(sep.get_next_sibling(), self.sidebar._stack)
|
||||
|
||||
def test_stack(self):
|
||||
"""Test the Gtk.Stack."""
|
||||
self.assertIsInstance(self.sidebar._stack, Gtk.Stack)
|
||||
self.assertEqual(self.sidebar._stack.props.transition_type,
|
||||
Gtk.StackTransitionType.OVER_LEFT_RIGHT)
|
||||
self.assertEqual(self.sidebar.get_last_child(), self.sidebar._stack)
|
||||
|
||||
def test_calendar_page(self):
|
||||
"""Test the Sidebar calendar view page."""
|
||||
self.assertIsInstance(self.sidebar._calendar,
|
||||
xfstestsdb.gtk.sidebar.CalendarView)
|
||||
self.assertEqual(self.sidebar._calendar.sql, self.xfstestsdb.sql)
|
||||
|
||||
self.assertEqual(self.sidebar._stack.get_child_by_name("calendar"),
|
||||
self.sidebar._calendar)
|
||||
page = self.sidebar._stack.get_page(self.sidebar._calendar)
|
||||
self.assertEqual(page.props.title, "Calendar")
|
||||
self.assertEqual(page.props.icon_name, "month-symbolic")
|
||||
|
||||
def test_tag_page(self):
|
||||
"""Test the Sidebar tag view page."""
|
||||
self.assertIsInstance(self.sidebar._tags,
|
||||
xfstestsdb.gtk.sidebar.RunidView)
|
||||
|
||||
self.assertEqual(self.sidebar._stack.get_child_by_name("tags"),
|
||||
self.sidebar._tags)
|
||||
page = self.sidebar._stack.get_page(self.sidebar._tags)
|
||||
self.assertEqual(page.props.title, "Tags")
|
||||
self.assertEqual(page.props.icon_name, "tag-symbolic")
|
||||
|
||||
def test_runid(self):
|
||||
"""Test the runid property."""
|
||||
self.assertEqual(self.sidebar.runid, 0)
|
||||
|
||||
self.sidebar._calendar.runid = 1
|
||||
self.assertEqual(self.sidebar.runid, 1)
|
||||
|
||||
self.sidebar._tags.runid = 2
|
||||
self.assertEqual(self.sidebar.runid, 2)
|
330
tests/gtk/test_tree.py
Normal file
330
tests/gtk/test_tree.py
Normal file
|
@ -0,0 +1,330 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our xfstests run selector tree."""
|
||||
import datetime
|
||||
import unittest
|
||||
import xfstestsdb.gtk.tree
|
||||
import tests.xunit
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestXfstestsRun(unittest.TestCase):
|
||||
"""Test case for our XfstestsRun GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.now = datetime.datetime.now()
|
||||
self.run = xfstestsdb.gtk.tree.XfstestsRun(1, self.now)
|
||||
|
||||
def test_run(self):
|
||||
"""Test the XfsetstsRun object."""
|
||||
self.assertIsInstance(self.run, GObject.GObject)
|
||||
self.assertEqual(self.run.runid, 1)
|
||||
self.assertEqual(self.run.timestamp, self.now)
|
||||
|
||||
def test_gt(self):
|
||||
"""Test the XfstestsRun Greater-Than operator."""
|
||||
soon = self.now + datetime.timedelta(seconds=5)
|
||||
run2 = xfstestsdb.gtk.tree.XfstestsRun(2, soon)
|
||||
run3 = xfstestsdb.gtk.tree.XfstestsRun(3, soon)
|
||||
|
||||
self.assertTrue(run2 > self.run)
|
||||
self.assertFalse(self.run > run2)
|
||||
self.assertFalse(self.run > self.run)
|
||||
|
||||
self.assertTrue(run3 > run2)
|
||||
self.assertFalse(run2 > run3)
|
||||
|
||||
def test_lt(self):
|
||||
"""Test the XfstestsRun Less-Than operator."""
|
||||
soon = self.now + datetime.timedelta(seconds=5)
|
||||
run2 = xfstestsdb.gtk.tree.XfstestsRun(2, soon)
|
||||
run3 = xfstestsdb.gtk.tree.XfstestsRun(3, soon)
|
||||
|
||||
self.assertTrue(self.run < run2)
|
||||
self.assertFalse(run2 < self.run)
|
||||
self.assertFalse(self.run < self.run)
|
||||
|
||||
self.assertTrue(run2 < run3)
|
||||
self.assertFalse(run3 < run2)
|
||||
|
||||
def test_str(self):
|
||||
"""Test converting an XfstestsRun to a string."""
|
||||
self.assertEqual(self.run.ftime, "%T")
|
||||
self.assertEqual(str(self.run), f"#1: {self.now.strftime('%T')}")
|
||||
|
||||
self.run.ftime = "%c"
|
||||
self.assertEqual(str(self.run), f"#1: {self.now.strftime('%c')}")
|
||||
|
||||
|
||||
class TestDeviceRunModel(unittest.TestCase):
|
||||
"""Test case for our DeviceRow GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.device = xfstestsdb.gtk.tree.DeviceRunsList(name="/dev/vda1")
|
||||
|
||||
def test_init(self):
|
||||
"""Test creating a DeviceRow instance."""
|
||||
self.assertIsInstance(self.device, GObject.GObject)
|
||||
self.assertIsInstance(self.device, Gio.ListModel)
|
||||
self.assertEqual(self.device.name, "/dev/vda1")
|
||||
self.assertEqual(str(self.device), "/dev/vda1")
|
||||
|
||||
def test_lt(self):
|
||||
"""Test comparing DeviceRow instances."""
|
||||
dev_a = xfstestsdb.gtk.tree.DeviceRunsList(name="a")
|
||||
dev_b = xfstestsdb.gtk.tree.DeviceRunsList(name="b")
|
||||
self.assertTrue(dev_a < dev_b)
|
||||
self.assertFalse(dev_b < dev_a)
|
||||
self.assertFalse(dev_a < dev_a)
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.device.get_item_type(),
|
||||
xfstestsdb.gtk.tree.XfstestsRun.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.device.get_n_items(), 0)
|
||||
self.assertEqual(self.device.n_items, 0)
|
||||
|
||||
self.device.add_run(1, datetime.datetime.now())
|
||||
self.assertEqual(self.device.get_n_items(), 1)
|
||||
self.assertEqual(self.device.n_items, 1)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
now = datetime.datetime.now()
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
|
||||
self.device.add_run(1, now)
|
||||
self.assertIsInstance(self.device[0], xfstestsdb.gtk.tree.XfstestsRun)
|
||||
self.assertEqual(self.device[0].runid, 1)
|
||||
self.assertEqual(self.device[0].timestamp, now)
|
||||
|
||||
self.device.add_run(2, then, "%c")
|
||||
self.assertEqual(self.device[0].runid, 2)
|
||||
self.assertEqual(self.device[0].ftime, "%c")
|
||||
self.assertEqual(self.device[1].runid, 1)
|
||||
|
||||
def test_get_earliest_run(self):
|
||||
"""Test the get_earliest_run() function."""
|
||||
self.assertIsNone(self.device.get_earliest_run())
|
||||
|
||||
now = datetime.datetime.now()
|
||||
self.device.add_run(1, now)
|
||||
self.assertEqual(self.device.get_earliest_run().runid, 1)
|
||||
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
self.device.add_run(2, then)
|
||||
self.assertEqual(self.device.get_earliest_run().runid, 2)
|
||||
|
||||
|
||||
class TestDateDeviceList(unittest.TestCase):
|
||||
"""Test case for our listmodel of test devices on a specific day."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
|
||||
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-1",
|
||||
"1", str(tests.xunit.XUNIT_1)])
|
||||
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-2",
|
||||
"1", str(tests.xunit.XUNIT_1)])
|
||||
|
||||
yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
self.xfstestsdb.sql("""UPDATE xfstests_runs SET timestamp=?
|
||||
WHERE device=?""", yesterday, "/dev/vda3")
|
||||
|
||||
self.today = datetime.date.today()
|
||||
self.devlist = xfstestsdb.gtk.tree.DateDeviceList(self.xfstestsdb.sql,
|
||||
self.today)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the DateDeviceList was set up properly."""
|
||||
self.assertIsInstance(self.devlist, GObject.GObject)
|
||||
self.assertIsInstance(self.devlist, Gio.ListModel)
|
||||
self.assertEqual(self.devlist.date, datetime.date.today())
|
||||
|
||||
def test_get_range(self):
|
||||
"""Test finding the date range for the displayed entries."""
|
||||
min = datetime.datetime.combine(self.today, datetime.time())
|
||||
max = min + datetime.timedelta(days=1)
|
||||
self.assertTupleEqual(self.devlist.get_range(self.today), (min, max))
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.devlist.get_item_type(),
|
||||
xfstestsdb.gtk.tree.DeviceRunsList.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.devlist.get_n_items(), 2)
|
||||
self.assertEqual(self.devlist.n_items, 2)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
self.assertIsInstance(self.devlist.get_item(0),
|
||||
xfstestsdb.gtk.tree.DeviceRunsList)
|
||||
self.assertIsInstance(self.devlist.get_item(0).get_item(0).timestamp,
|
||||
datetime.datetime)
|
||||
self.assertEqual(self.devlist.get_item(0).name, "/dev/vda1")
|
||||
self.assertEqual(self.devlist.get_item(1).name, "/dev/vda2")
|
||||
|
||||
def test_treemodel(self):
|
||||
"""Test the treemodel property."""
|
||||
self.assertIsInstance(self.devlist.treemodel, Gtk.TreeListModel)
|
||||
self.assertFalse(self.devlist.treemodel.props.passthrough)
|
||||
self.assertTrue(self.devlist.treemodel.props.autoexpand)
|
||||
|
||||
tree = self.devlist.treemodel
|
||||
self.assertEqual(tree[0].get_item().name, "/dev/vda1")
|
||||
self.assertEqual(tree[1].get_item().runid, 2)
|
||||
self.assertEqual(tree[2].get_item().runid, 3)
|
||||
self.assertEqual(tree[3].get_item().name, "/dev/vda2")
|
||||
self.assertEqual(tree[4].get_item().runid, 1)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
self.assertIsNone(tree[5])
|
||||
|
||||
|
||||
class TestTagDeviceList(unittest.TestCase):
|
||||
"""Test case for our TagDeviceList GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.tag = xfstestsdb.gtk.tree.TagDeviceList(name="mytag")
|
||||
|
||||
def test_init(self):
|
||||
"""Test creating a TagDeviceList instance."""
|
||||
self.assertIsInstance(self.tag, GObject.GObject)
|
||||
self.assertIsInstance(self.tag, Gio.ListModel)
|
||||
self.assertEqual(self.tag.name, "mytag")
|
||||
self.assertEqual(str(self.tag), "mytag")
|
||||
|
||||
def test_lt(self):
|
||||
"""Test comparing TagDeviceList instances."""
|
||||
tag_a = xfstestsdb.gtk.tree.TagDeviceList(name="a")
|
||||
tag_b = xfstestsdb.gtk.tree.TagDeviceList(name="b")
|
||||
|
||||
now = datetime.datetime.now()
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
tag_a.add_run(1, "/dev/vda1", now)
|
||||
tag_b.add_run(2, "/dev/vda2", then)
|
||||
|
||||
self.assertTrue(tag_a < tag_b)
|
||||
self.assertFalse(tag_b < tag_a)
|
||||
self.assertFalse(tag_a < tag_a)
|
||||
|
||||
tag_a.add_run(2, "/dev/vda2", then)
|
||||
self.assertTrue(tag_b < tag_a)
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.tag.get_item_type(),
|
||||
xfstestsdb.gtk.tree.DeviceRunsList.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.tag.get_n_items(), 0)
|
||||
self.assertEqual(self.tag.n_items, 0)
|
||||
|
||||
self.tag.add_run(1, "/dev/vda1", datetime.datetime.now())
|
||||
self.assertEqual(self.tag.get_n_items(), 1)
|
||||
self.assertEqual(self.tag.n_items, 1)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
now = datetime.datetime.now()
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
|
||||
self.tag.add_run(1, "/dev/vda2", now)
|
||||
self.assertIsInstance(self.tag[0], xfstestsdb.gtk.tree.DeviceRunsList)
|
||||
self.assertEqual(self.tag[0].name, "/dev/vda2")
|
||||
self.assertEqual(self.tag[0][0].runid, 1)
|
||||
self.assertEqual(self.tag[0][0].timestamp, now)
|
||||
self.assertEqual(self.tag[0][0].ftime, "%c")
|
||||
|
||||
self.tag.add_run(2, "/dev/vda1", then)
|
||||
self.assertEqual(self.tag[0].name, "/dev/vda1")
|
||||
self.assertEqual(self.tag[1].name, "/dev/vda2")
|
||||
|
||||
def test_get_earliest_run(self):
|
||||
"""Test the get_earliest_run() function."""
|
||||
self.assertIsNone(self.tag.get_earliest_run())
|
||||
|
||||
now = datetime.datetime.now()
|
||||
self.tag.add_run(1, "/dev/vda1", now)
|
||||
self.assertEqual(self.tag.get_earliest_run().runid, 1)
|
||||
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
self.tag.add_run(2, "/dev/vda2", then)
|
||||
self.assertEqual(self.tag.get_earliest_run().runid, 2)
|
||||
|
||||
|
||||
class TestTagList(unittest.TestCase):
|
||||
"""Test case for our TagList ListModel."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
|
||||
self.xfstestsdb.run(["tag", "1", "mytag2"])
|
||||
self.xfstestsdb.run(["tag", "2", "mytag2"])
|
||||
self.xfstestsdb.run(["tag", "4", "mytag1"])
|
||||
|
||||
self.taglist = xfstestsdb.gtk.tree.TagList(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the TagList was set up properly."""
|
||||
self.assertIsInstance(self.taglist, GObject.GObject)
|
||||
self.assertIsInstance(self.taglist, Gio.ListModel)
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.taglist.get_item_type(),
|
||||
xfstestsdb.gtk.tree.TagDeviceList.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.taglist.get_n_items(), 2)
|
||||
self.assertEqual(self.taglist.n_items, 2)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
self.assertIsInstance(self.taglist.get_item(0),
|
||||
xfstestsdb.gtk.tree.TagDeviceList)
|
||||
self.assertIsInstance(self.taglist[0][0][0].timestamp,
|
||||
datetime.datetime)
|
||||
self.assertEqual(self.taglist.get_item(0).name, "mytag1")
|
||||
self.assertEqual(self.taglist.get_item(1).name, "mytag2")
|
||||
|
||||
def test_treemodel(self):
|
||||
"""Test the treemodel property."""
|
||||
self.assertIsInstance(self.taglist.treemodel, Gtk.TreeListModel)
|
||||
self.assertFalse(self.taglist.treemodel.props.passthrough)
|
||||
self.assertFalse(self.taglist.treemodel.props.autoexpand)
|
||||
|
||||
tree = self.taglist.treemodel
|
||||
self.assertEqual(tree[0].get_item().name, "mytag1")
|
||||
tree[0].set_expanded(True)
|
||||
self.assertEqual(tree[1].get_item().name, "/dev/vda3")
|
||||
tree[1].set_expanded(True)
|
||||
self.assertEqual(tree[2].get_item().runid, 4)
|
||||
|
||||
self.assertEqual(tree[3].get_item().name, "mytag2")
|
||||
tree[3].set_expanded(True)
|
||||
self.assertEqual(tree[4].get_item().name, "/dev/vda1")
|
||||
self.assertEqual(tree[5].get_item().name, "/dev/vda2")
|
|
@ -26,6 +26,8 @@ class TestXunitView(unittest.TestCase):
|
|||
def test_init(self):
|
||||
"""Test that we created the XunitView correctly."""
|
||||
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
|
||||
self.assertTrue(self.view.has_css_class("undershoot-top"))
|
||||
self.assertTrue(self.view.has_css_class("undershoot-bottom"))
|
||||
self.assertTrue(self.view.has_css_class("card"))
|
||||
|
||||
def test_columnview(self):
|
||||
|
@ -383,8 +385,8 @@ class MessagesView(unittest.TestCase):
|
|||
self.view._back)
|
||||
|
||||
self.assertEqual(self.view._back.props.child.props.icon_name,
|
||||
"go-previous-symbolic")
|
||||
self.assertEqual(self.view._back.props.child.props.label, "back")
|
||||
"down-large-symbolic")
|
||||
self.assertEqual(self.view._back.props.child.props.label, "done")
|
||||
self.assertTrue(self.view._back.has_css_class("suggested-action"))
|
||||
self.assertTrue(self.view._back.has_css_class("pill"))
|
||||
|
||||
|
@ -500,7 +502,7 @@ class TestXfstestsView(unittest.TestCase):
|
|||
self.assertIsInstance(sep, Gtk.Separator)
|
||||
self.assertIsInstance(self.view._stack, Gtk.Stack)
|
||||
self.assertEqual(self.view._stack.props.transition_type,
|
||||
Gtk.StackTransitionType.OVER_LEFT_RIGHT)
|
||||
Gtk.StackTransitionType.OVER_UP_DOWN)
|
||||
self.assertEqual(sep.get_next_sibling(), self.view._stack)
|
||||
|
||||
def test_testcase_view(self):
|
||||
|
|
|
@ -38,21 +38,61 @@ class TestWindow(unittest.TestCase):
|
|||
self.assertEqual(self.window.headerbar.props.title_widget,
|
||||
self.window.title)
|
||||
|
||||
def test_child(self):
|
||||
"""Test the window child property."""
|
||||
self.assertIsInstance(self.window.headerbar.get_next_sibling(),
|
||||
Adw.Bin)
|
||||
def test_splitview(self):
|
||||
"""Check that the Window's splitview is set up correctly."""
|
||||
self.assertIsInstance(self.window._splitview, Adw.OverlaySplitView)
|
||||
self.assertEqual(self.window.headerbar.get_next_sibling(),
|
||||
self.window._splitview)
|
||||
self.assertTrue(self.window._splitview.props.collapsed)
|
||||
|
||||
def test_show_sidebar(self):
|
||||
"""Test the window show sidebar button."""
|
||||
self.assertIsInstance(self.window._show_sidebar, Gtk.ToggleButton)
|
||||
self.assertEqual(self.window._show_sidebar.get_ancestor(Adw.HeaderBar),
|
||||
self.window.headerbar)
|
||||
self.assertEqual(self.window._show_sidebar.props.icon_name,
|
||||
"sidebar-show")
|
||||
|
||||
self.assertFalse(self.window.show_sidebar)
|
||||
self.assertFalse(self.window._show_sidebar.props.active)
|
||||
self.assertFalse(self.window._splitview.props.show_sidebar)
|
||||
|
||||
self.window._show_sidebar.props.active = True
|
||||
self.assertTrue(self.window._splitview.props.show_sidebar)
|
||||
self.assertTrue(self.window.show_sidebar)
|
||||
|
||||
self.window.show_sidebar = False
|
||||
self.assertFalse(self.window._show_sidebar.props.active)
|
||||
self.assertFalse(self.window._splitview.props.show_sidebar)
|
||||
|
||||
window2 = xfstestsdb.gtk.window.Window(show_sidebar=True)
|
||||
self.assertTrue(window2.show_sidebar)
|
||||
self.assertTrue(window2._show_sidebar.props.active)
|
||||
self.assertTrue(window2._splitview.props.show_sidebar)
|
||||
|
||||
def test_content(self):
|
||||
"""Test the window content property."""
|
||||
self.assertIsNone(self.window.child)
|
||||
self.window.child = Gtk.Label()
|
||||
self.assertEqual(self.window.headerbar.get_next_sibling().props.child,
|
||||
self.assertEqual(self.window._splitview.props.content,
|
||||
self.window.child)
|
||||
|
||||
label = Gtk.Label()
|
||||
window2 = xfstestsdb.gtk.window.Window(child=label)
|
||||
self.assertEqual(window2.child, label)
|
||||
self.assertEqual(window2.headerbar.get_next_sibling().props.child,
|
||||
label)
|
||||
self.assertEqual(window2._splitview.props.content, label)
|
||||
|
||||
def test_sidebar(self):
|
||||
"""Test the window sidebar property."""
|
||||
self.assertIsNone(self.window.sidebar)
|
||||
self.window.sidebar = Gtk.Label()
|
||||
self.assertEqual(self.window._splitview.props.sidebar,
|
||||
self.window.sidebar)
|
||||
|
||||
label = Gtk.Label()
|
||||
window2 = xfstestsdb.gtk.window.Window(sidebar=label)
|
||||
self.assertEqual(window2.sidebar, label)
|
||||
self.assertEqual(window2._splitview.props.sidebar, label)
|
||||
|
||||
def test_runid(self):
|
||||
"""Test the window runid property."""
|
||||
|
|
|
@ -24,9 +24,15 @@ class TestCommand(unittest.TestCase):
|
|||
self.assertEqual(self.command.parser.description, "description")
|
||||
self.assertEqual(self.command.parser._defaults["function"],
|
||||
self.command.do_command)
|
||||
self.assertEqual(self.command.parser._defaults["done"],
|
||||
self.command.do_done)
|
||||
self.assertEqual(self.command.sql, self.sql)
|
||||
|
||||
def test_do_command(self):
|
||||
"""Test the do_command() function."""
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.command.do_command(argparse.Namespace())
|
||||
|
||||
def test_do_done(self):
|
||||
"""Test the do_done() function."""
|
||||
self.command.do_done(argparse.Namespace())
|
||||
|
|
|
@ -8,6 +8,8 @@ import xfstestsdb.xunit.gc
|
|||
import tests.xunit
|
||||
|
||||
|
||||
@unittest.mock.patch("xfstestsdb.sqlite.Connection.vacuum")
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
class TestGC(unittest.TestCase):
|
||||
"""Tests the `xfstestsdb xunit gc` command."""
|
||||
|
||||
|
@ -31,24 +33,26 @@ class TestGC(unittest.TestCase):
|
|||
mock_stdout.seek(0)
|
||||
mock_stdout.truncate(0)
|
||||
|
||||
def test_init(self):
|
||||
def test_init(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Check that the gc command was set up properly."""
|
||||
self.assertIsInstance(self.gc, xfstestsdb.commands.Command)
|
||||
self.assertIsInstance(self.gc, xfstestsdb.xunit.gc.Command)
|
||||
self.assertEqual(self.xunit.subparser.choices["gc"],
|
||||
self.gc.parser)
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_empty(self, mock_stdout: io.StringIO):
|
||||
def test_gc_empty(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting an empty database."""
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_gc_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [])
|
||||
|
||||
self.xfstestsdb.run(["gc"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "")
|
||||
mock_vacuum.assert_not_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_no_testcases(self, mock_stdout: io.StringIO):
|
||||
def test_gc_no_testcases(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting runs with no testcases."""
|
||||
self.setup_runs(mock_stdout)
|
||||
|
||||
|
@ -61,9 +65,10 @@ class TestGC(unittest.TestCase):
|
|||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
|
||||
mock_vacuum.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_expired(self, mock_stdout: io.StringIO):
|
||||
def test_gc_expired(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting old runs."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
|
||||
|
@ -87,9 +92,10 @@ class TestGC(unittest.TestCase):
|
|||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [2])
|
||||
mock_vacuum.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_expired_tagged(self, mock_stdout: io.StringIO):
|
||||
def test_gc_expired_tagged(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test that we don't garbage collect expired runs that are tagged."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
|
||||
|
@ -109,9 +115,10 @@ class TestGC(unittest.TestCase):
|
|||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
|
||||
mock_vacuum.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_dry_run(self, mock_stdout: io.StringIO):
|
||||
def test_gc_dry_run(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting with the --dry-run option."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["gc", "--dry-run"])
|
||||
|
@ -121,3 +128,4 @@ class TestGC(unittest.TestCase):
|
|||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()],
|
||||
[1, 2, 3])
|
||||
mock_vacuum.assert_not_called()
|
||||
|
|
|
@ -44,11 +44,21 @@ class TestApplication(unittest.TestCase):
|
|||
mock_cmd.get_arguments.assert_called()
|
||||
mock_activate.assert_called()
|
||||
self.assertEqual(self.application.runid, 0)
|
||||
self.assertFalse(self.application.show_sidebar)
|
||||
self.assertIsNone(self.application.environment)
|
||||
self.assertIsNone(self.application.properties)
|
||||
self.assertIsNone(self.application.model)
|
||||
self.assertIsNone(self.application.summary)
|
||||
|
||||
mock_command_line.reset_mock()
|
||||
mock_activate.reset_mock()
|
||||
mock_cmd.reset_mock()
|
||||
mock_cmd.get_arguments.return_value = ["show-sidebar"]
|
||||
|
||||
self.application.do_command_line(mock_cmd)
|
||||
self.assertTrue(self.application.show_sidebar)
|
||||
|
||||
self.application.show_sidebar = False
|
||||
mock_command_line.reset_mock()
|
||||
mock_activate.reset_mock()
|
||||
mock_cmd.reset_mock()
|
||||
|
@ -68,6 +78,7 @@ class TestApplication(unittest.TestCase):
|
|||
self.assertEqual(self.application.environment,
|
||||
self.application.properties.environment)
|
||||
self.assertEqual(self.application.model.runid, 42)
|
||||
self.assertFalse(self.application.show_sidebar)
|
||||
|
||||
@unittest.mock.patch("xfstestsdb.gtk.gsetup.add_style")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.add_window")
|
||||
|
@ -77,22 +88,33 @@ class TestApplication(unittest.TestCase):
|
|||
mock_add_style: unittest.mock.Mock):
|
||||
"""Check that startup sets up our application instance correctly."""
|
||||
self.assertIsNone(self.application.win)
|
||||
self.assertIsNone(self.application.sidebar)
|
||||
self.assertIsNone(self.application.view)
|
||||
|
||||
self.application.emit("startup")
|
||||
|
||||
self.assertIsInstance(self.application.sidebar,
|
||||
xfstestsdb.gtk.sidebar.Sidebar)
|
||||
self.assertIsInstance(self.application.view,
|
||||
xfstestsdb.gtk.view.XfstestsView)
|
||||
self.assertIsInstance(self.application.win,
|
||||
xfstestsdb.gtk.window.Window)
|
||||
self.assertEqual(self.application.win.child, self.application.view)
|
||||
self.assertEqual(self.application.win.sidebar,
|
||||
self.application.sidebar)
|
||||
self.assertEqual(self.application.sidebar.sql,
|
||||
self.application.sql)
|
||||
mock_startup.assert_called_with(self.application)
|
||||
mock_add_window.assert_called_with(self.application.win)
|
||||
mock_add_style.assert_called()
|
||||
|
||||
self.application.runid = 42
|
||||
self.application.sidebar.runid = 42
|
||||
self.assertEqual(self.application.runid, 42)
|
||||
self.assertEqual(self.application.win.runid, 42)
|
||||
|
||||
self.application.show_sidebar = True
|
||||
self.assertTrue(self.application.win.show_sidebar)
|
||||
|
||||
properties = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 42)
|
||||
self.application.properties = properties
|
||||
self.assertEqual(self.application.view.properties, properties)
|
||||
|
@ -150,11 +172,9 @@ class TestGtk(unittest.TestCase):
|
|||
def test_gtk_empty(self, mock_app: unittest.mock.Mock,
|
||||
mock_stderr: unittest.mock.Mock):
|
||||
"""Check that running `xfstestsdb gtk` without a runid."""
|
||||
with self.assertRaises(SystemExit):
|
||||
self.xfstestsdb.run(["gtk"])
|
||||
|
||||
self.assertRegex(mock_stderr.getvalue(),
|
||||
"error: the following arguments are required: runid")
|
||||
self.xfstestsdb.run(["gtk"])
|
||||
mock_app.assert_called_with(self.xfstestsdb.sql)
|
||||
mock_app.return_value.run.assert_called_with(["show-sidebar"])
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
@unittest.mock.patch("xfstestsdb.gtk.Application")
|
||||
|
|
|
@ -30,6 +30,7 @@ class TestList(unittest.TestCase):
|
|||
self.xfstestsdb.run(["tag", "1", "mytag1"])
|
||||
self.xfstestsdb.run(["tag", "1", "mytag2"])
|
||||
self.xfstestsdb.run(["tag", "3", "mytag3"])
|
||||
self.xfstestsdb.run(["tag", "3", "othertag"])
|
||||
|
||||
self.xfstestsdb.sql.executemany("""UPDATE xfstests_runs SET timestamp=?
|
||||
WHERE rowid=?""", (timestamp, 1),
|
||||
|
@ -116,13 +117,12 @@ class TestList(unittest.TestCase):
|
|||
self.xfstestsdb.run(["list", "--color", "none", "--tags"])
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"""
|
||||
+-----+---------------------+-----------+---------------+
|
||||
| run | timestamp | device | tags |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
| 1 | 2023-01-01 12:59:59 | /dev/vda1 | mytag1,mytag2 |
|
||||
| 2 | 2023-01-02 12:59:59 | /dev/vda2 | |
|
||||
| 3 | 2023-01-03 12:59:59 | /dev/vdb1 | mytag3 |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
+-----+---------------------+-----------+-----------------+
|
||||
| run | timestamp | device | tags |
|
||||
+-----+---------------------+-----------+-----------------+
|
||||
| 1 | 2023-01-01 12:59:59 | /dev/vda1 | mytag1,mytag2 |
|
||||
| 3 | 2023-01-03 12:59:59 | /dev/vdb1 | mytag3,othertag |
|
||||
+-----+---------------------+-----------+-----------------+
|
||||
""")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
|
@ -170,7 +170,6 @@ class TestList(unittest.TestCase):
|
|||
| run | timestamp | device | xunits |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
| 1 | 2023-01-01 12:59:59 | /dev/vda1 | test-1,test-2 |
|
||||
| 2 | 2023-01-02 12:59:59 | /dev/vda2 | |
|
||||
| 3 | 2023-01-03 12:59:59 | /dev/vdb1 | test-3 |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
""")
|
||||
|
|
|
@ -29,6 +29,12 @@ class TestNew(unittest.TestCase):
|
|||
r"created run #1 with test device '/dev/vdb1' "
|
||||
r"\[[\d\-\: ]+\]\n")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_new_terse(self, mock_stdout: io.StringIO):
|
||||
"""Test running `xfstestsdb new --terse`."""
|
||||
self.xfstestsdb.run(["new", "--terse", "/dev/vdb1"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "1\n")
|
||||
|
||||
@unittest.mock.patch("sys.stderr", new_callable=io.StringIO)
|
||||
def test_new_error(self, mock_stderr: io.StringIO):
|
||||
"""Test running the `xfstestsdb new` command with invalid input."""
|
||||
|
|
|
@ -114,6 +114,10 @@ class TestConnection(unittest.TestCase):
|
|||
with self.assertRaises(sqlite3.OperationalError):
|
||||
self.sql("SELECT COUNT(*) FROM other_table")
|
||||
|
||||
def test_vacuum(self):
|
||||
"""Test vacuuming the database."""
|
||||
self.assertIsInstance(self.sql.vacuum(), sqlite3.Cursor)
|
||||
|
||||
def test_close(self):
|
||||
"""Check closing the connection."""
|
||||
self.sql.close()
|
||||
|
|
|
@ -37,22 +37,24 @@ class TestXfstestsdb(unittest.TestCase):
|
|||
def test_run(self):
|
||||
"""Test running the xfstestsdb."""
|
||||
parser = self.xfstestsdb.subparser.add_parser("test-run", help="help")
|
||||
test_done = unittest.mock.Mock()
|
||||
test_passed = False
|
||||
|
||||
def test_func(args: argparse.Namespace) -> None:
|
||||
nonlocal test_passed
|
||||
self.assertTrue(self.xfstestsdb.sql.sql.in_transaction)
|
||||
test_passed = True
|
||||
parser.set_defaults(function=test_func)
|
||||
parser.set_defaults(function=test_func, done=test_done)
|
||||
|
||||
self.xfstestsdb.run(["test-run"])
|
||||
self.assertTrue(test_passed)
|
||||
test_done.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_version(self, mock_stdout: io.StringIO):
|
||||
"""Test printing version information."""
|
||||
self.assertEqual(xfstestsdb.MAJOR, 1)
|
||||
self.assertEqual(xfstestsdb.MINOR, 5)
|
||||
self.assertEqual(xfstestsdb.MINOR, 6)
|
||||
|
||||
self.xfstestsdb.run(["--version"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.5-debug\n")
|
||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.6-debug\n")
|
||||
|
|
|
@ -15,7 +15,7 @@ from . import untag
|
|||
from . import xunit
|
||||
|
||||
MAJOR = 1
|
||||
MINOR = 5
|
||||
MINOR = 6
|
||||
|
||||
|
||||
class Command:
|
||||
|
@ -24,7 +24,8 @@ class Command:
|
|||
def __init__(self) -> None:
|
||||
"""Initialize the xfstestsdb command."""
|
||||
self.parser = argparse.ArgumentParser()
|
||||
self.parser.set_defaults(function=lambda x: self.parser.print_usage())
|
||||
self.parser.set_defaults(function=lambda x: self.parser.print_usage(),
|
||||
done=lambda x: None)
|
||||
self.parser.add_argument("--version", action="store_true",
|
||||
help="show version number and exit")
|
||||
self.subparser = self.parser.add_subparsers(title="commands")
|
||||
|
@ -55,3 +56,4 @@ class Command:
|
|||
else:
|
||||
with self.sql:
|
||||
parsed.function(parsed)
|
||||
parsed.done(parsed)
|
||||
|
|
|
@ -12,8 +12,12 @@ class Command:
|
|||
"""Set up the Command."""
|
||||
self.parser = subparser.add_parser(name, help=help, **kwargs)
|
||||
self.parser.set_defaults(function=self.do_command)
|
||||
self.parser.set_defaults(done=self.do_done)
|
||||
self.sql = sql
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Do something."""
|
||||
raise NotImplementedError
|
||||
|
||||
def do_done(self, args: argparse.Namespace) -> None:
|
||||
"""Run after the main command, outside of a transaction."""
|
||||
|
|
|
@ -25,3 +25,10 @@ class Command(commands.Command):
|
|||
if not args.dry_run:
|
||||
self.sql("DELETE FROM xfstests_runs WHERE runid=?", runid)
|
||||
print(f"run #{runid} {how} deleted")
|
||||
|
||||
args.need_vacuum = len(rows) > 0
|
||||
|
||||
def do_done(self, args: argparse.Namespace) -> None:
|
||||
"""Vacuum the database after deleting."""
|
||||
if args.need_vacuum and not args.dry_run:
|
||||
self.sql.vacuum()
|
||||
|
|
|
@ -8,6 +8,7 @@ from gi.repository import GObject
|
|||
from gi.repository import Gio
|
||||
from gi.repository import Adw
|
||||
from . import model
|
||||
from . import sidebar
|
||||
from . import view
|
||||
from . import window
|
||||
from .. import commands
|
||||
|
@ -18,11 +19,13 @@ class Application(Adw.Application):
|
|||
"""Our Adw.Application for displaying xfstests results."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
show_sidebar = GObject.Property(type=bool, default=False)
|
||||
environment = GObject.Property(type=Gio.ListStore)
|
||||
properties = GObject.Property(type=model.PropertyList)
|
||||
summary = GObject.Property(type=model.SummaryList)
|
||||
model = GObject.Property(type=model.TestCaseList)
|
||||
win = GObject.Property(type=window.Window)
|
||||
sidebar = GObject.Property(type=sidebar.Sidebar)
|
||||
view = GObject.Property(type=view.XfstestsView)
|
||||
sql = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
|
@ -32,6 +35,14 @@ class Application(Adw.Application):
|
|||
resource_base_path=gsetup.RESOURCE_PATH,
|
||||
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
||||
sql=sql)
|
||||
self.connect("notify::runid", self.__notify_runid)
|
||||
|
||||
def __notify_runid(self, app: Adw.Application,
|
||||
param: GObject.ParamSpec) -> None:
|
||||
self.properties = model.PropertyList(self.sql, self.runid)
|
||||
self.environment = self.properties.environment
|
||||
self.model = model.TestCaseList(self.sql, self.runid)
|
||||
self.summary = model.SummaryList(self.sql, self.runid)
|
||||
|
||||
def do_command_line(self, cmd_line: Gio.ApplicationCommandLine) -> int:
|
||||
"""Handle the Adw.Application::command-line signal."""
|
||||
|
@ -42,10 +53,8 @@ class Application(Adw.Application):
|
|||
match split[0]:
|
||||
case "runid":
|
||||
self.runid = int(split[1])
|
||||
self.properties = model.PropertyList(self.sql, self.runid)
|
||||
self.environment = self.properties.environment
|
||||
self.model = model.TestCaseList(self.sql, self.runid)
|
||||
self.summary = model.SummaryList(self.sql, self.runid)
|
||||
case "show-sidebar":
|
||||
self.show_sidebar = True
|
||||
|
||||
self.activate()
|
||||
return 0
|
||||
|
@ -55,11 +64,14 @@ class Application(Adw.Application):
|
|||
Adw.Application.do_startup(self)
|
||||
gsetup.add_style()
|
||||
|
||||
self.sidebar = sidebar.Sidebar(self.sql)
|
||||
self.view = view.XfstestsView()
|
||||
self.win = window.Window(child=self.view)
|
||||
self.win = window.Window(child=self.view, sidebar=self.sidebar)
|
||||
self.win.headerbar.pack_end(self.view.filterbuttons)
|
||||
|
||||
self.sidebar.bind_property("runid", self, "runid")
|
||||
self.bind_property("runid", self.win, "runid")
|
||||
self.bind_property("show-sidebar", self.win, "show-sidebar")
|
||||
self.bind_property("environment", self.view, "environment")
|
||||
self.bind_property("properties", self.view, "properties")
|
||||
self.bind_property("model", self.view, "model")
|
||||
|
@ -83,19 +95,24 @@ class Command(commands.Command):
|
|||
sql: sqlite.Connection) -> None:
|
||||
"""Set up the gtk command."""
|
||||
super().__init__(subparser, sql, "gtk", help="show a gtk-based ui")
|
||||
self.parser.add_argument("runid", metavar="runid", nargs=1, type=int,
|
||||
self.parser.add_argument("runid", metavar="runid", nargs='?', type=int,
|
||||
help="show a specific xfstests run")
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Run the Gtk UI."""
|
||||
app_args = []
|
||||
args.app_args = []
|
||||
|
||||
if self.sql("SELECT 1 FROM xfstests_runs WHERE runid=?",
|
||||
args.runid[0]).fetchone():
|
||||
app_args.append(f"runid={args.runid[0]}")
|
||||
if args.runid is not None:
|
||||
if self.sql("SELECT 1 FROM xfstests_runs WHERE runid=?",
|
||||
args.runid).fetchone():
|
||||
args.app_args.append(f"runid={args.runid}")
|
||||
else:
|
||||
print(f"error: run #{args.runid} does not exist",
|
||||
file=sys.stderr)
|
||||
sys.exit(errno.ENOENT)
|
||||
else:
|
||||
print(f"error: run #{args.runid[0]} does not exist",
|
||||
file=sys.stderr)
|
||||
sys.exit(errno.ENOENT)
|
||||
args.app_args.append("show-sidebar")
|
||||
|
||||
Application(self.sql).run(app_args)
|
||||
def do_done(self, args: argparse.Namespace) -> None:
|
||||
"""Run the application outside of a transaction."""
|
||||
Application(self.sql).run(args.app_args)
|
||||
|
|
2
xfstestsdb/gtk/icons/down-large-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/down-large-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m -0.00390625 3 v 1 c 0 0.292969 0.12890625 0.558594 0.32812525 0.738281 l 7.671875 7.675781 l 7.707031 -7.707031 c 0.183594 -0.179687 0.292969 -0.429687 0.292969 -0.707031 v -1 h -1 c -0.273438 0 -0.523438 0.113281 -0.707032 0.292969 c -0.007812 0.011719 -0.019531 0.019531 -0.03125 0.03125 l -6.261718 6.261719 l -6.257813 -6.261719 c -0.183593 -0.199219 -0.445312 -0.324219 -0.742187 -0.324219 z m 0 0"/></svg>
|
After Width: | Height: | Size: 549 B |
2
xfstestsdb/gtk/icons/month-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/month-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 3.042969 1 c -1.128907 0 -2.042969 0.914062 -2.042969 2.042969 v 9.875 c 0 1.132812 0.914062 2.042969 2.042969 2.042969 h 9.914062 c 1.128907 0 2.042969 -0.910157 2.042969 -2.042969 v -9.875 c 0 -1.128907 -0.914062 -2.042969 -2.042969 -2.042969 z m -0.042969 4.960938 h 10 v 7 h -10 z m 1 1.039062 v 2 h 2 v -2 z m 3 0 v 2 h 2 v -2 z m 3 0 v 2 h 2 v -2 z m -6 3 v 2 h 2 v -2 z m 3 0 v 2 h 2 v -2 z m 0 0"/></svg>
|
After Width: | Height: | Size: 550 B |
35
xfstestsdb/gtk/icons/tag-symbolic.svg
Normal file
35
xfstestsdb/gtk/icons/tag-symbolic.svg
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7384"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1">
|
||||
<title
|
||||
id="title9167">Gnome Symbolic Icon Theme</title>
|
||||
<defs
|
||||
id="defs9" />
|
||||
<metadata
|
||||
id="metadata90">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Gnome Symbolic Icon Theme</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
id="path855"
|
||||
d="m 1,1 v 6.0008052 l 7.9783226,7.9783228 c 0.5857862,0.585786 1.5355344,0.585786 2.1213204,0 l 0.0097,-0.0097 3.87942,-3.87942 0.0097,-0.0097 c 0.585786,-0.585786 0.585786,-1.535534 0,-2.1213204 L 7.0394752,1 Z m 2.4775289,2.4403054 c 0.271447,-0.271447 0.6464468,-0.4391799 1.0606602,-0.4391797 0.8284268,3e-7 1.4998395,0.671413 1.4998398,1.4998398 4e-7,0.8284269 -0.6714131,1.4998403 -1.4998399,1.49984 -0.8284275,4e-7 -1.4998402,-0.6714123 -1.4998398,-1.4998399 -2e-7,-0.4142134 0.1677334,-0.7892139 0.4391797,-1.0606602 z"
|
||||
style="display:inline;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
|
||||
<title
|
||||
id="title864">tag-symbolic</title>
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
@ -4,5 +4,8 @@
|
|||
<file preprocess="xml-stripblanks">test-pass-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">test-skip-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">test-fail-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">down-large-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">month-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">tag-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
|
|
@ -4,6 +4,7 @@ import typing
|
|||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import tree
|
||||
|
||||
|
||||
STYLES = {"passed": "success", "failed": "error",
|
||||
|
@ -181,3 +182,40 @@ class SummaryFactory(XunitFactory):
|
|||
def do_unbind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
|
||||
"""Unbind a ListItem from the child widget."""
|
||||
child.remove_css_class(STYLES[row.name])
|
||||
|
||||
|
||||
class SidebarFactory(Gtk.SignalListItemFactory):
|
||||
"""Factory for making sidebar widgets."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize our InscriptionFactory."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.connect("setup", self.__setup)
|
||||
self.connect("bind", self.__bind)
|
||||
self.connect("unbind", self.__unbind)
|
||||
self.connect("teardown", self.__teardown)
|
||||
|
||||
def __setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
"""Set up a ListItem child widget."""
|
||||
child = Gtk.Label(yalign=0.75)
|
||||
child.add_css_class("numeric")
|
||||
expander = Gtk.TreeExpander(child=child)
|
||||
listitem.set_child(expander)
|
||||
|
||||
def __bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
"""Bind a ListItem to the child widget."""
|
||||
treeitem = listitem.get_item()
|
||||
expander = listitem.get_child()
|
||||
expander.set_list_row(treeitem)
|
||||
|
||||
row = treeitem.get_item()
|
||||
child = expander.get_child()
|
||||
child.set_text(str(row))
|
||||
listitem.props.selectable = isinstance(row, tree.XfstestsRun)
|
||||
|
||||
def __unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
"""Unbind a ListItem from the child widget."""
|
||||
listitem.get_child().get_child().set_text("")
|
||||
|
||||
def __teardown(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
listitem.set_child(None)
|
||||
|
|
131
xfstestsdb/gtk/sidebar.py
Normal file
131
xfstestsdb/gtk/sidebar.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Our sidebar for selecting a specific xfstests run to view."""
|
||||
import datetime
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from .. import sqlite
|
||||
from . import row
|
||||
from . import tree
|
||||
|
||||
|
||||
class RunidView(Gtk.ScrolledWindow):
|
||||
"""Our RunidView class."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
model = GObject.Property(type=Gio.ListModel)
|
||||
|
||||
def __init__(self, model: tree.DateDeviceList) -> None:
|
||||
"""Initialize a RunidView instance."""
|
||||
super().__init__(model=model, vexpand=True)
|
||||
self._selection = Gtk.SingleSelection(model=model.treemodel)
|
||||
self._view = Gtk.ListView(model=self._selection,
|
||||
factory=row.SidebarFactory(),
|
||||
single_click_activate=True)
|
||||
|
||||
self._view.connect("activate", self.__activate)
|
||||
self.connect("notify::model", self.__notify_model)
|
||||
|
||||
self._view.add_css_class("navigation-sidebar")
|
||||
self._view.add_css_class("background")
|
||||
self.set_child(self._view)
|
||||
|
||||
def __activate(self, view: Gtk.ListView, position: int) -> None:
|
||||
treeitem = self._selection[position]
|
||||
item = treeitem.get_item()
|
||||
if isinstance(item, tree.XfstestsRun):
|
||||
self.runid = item.runid
|
||||
elif treeitem.props.expanded:
|
||||
treeitem.props.expanded = False
|
||||
else:
|
||||
treeitem.props.expanded = True
|
||||
self.runid = item.get_earliest_run().runid
|
||||
|
||||
def __notify_model(self, sidebar: typing.Self,
|
||||
param: GObject.ParamSpec) -> None:
|
||||
model = None if self.model is None else self.model.treemodel
|
||||
self._selection.props.model = model
|
||||
|
||||
|
||||
class CalendarView(Gtk.Box):
|
||||
"""Our calendar view for seleting an xfstests run by date."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
sql = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection) -> None:
|
||||
"""Initialize a CalendarView instance."""
|
||||
super().__init__(sql=sql, spacing=6,
|
||||
orientation=Gtk.Orientation.VERTICAL)
|
||||
today = datetime.date.today()
|
||||
self._calendar = Gtk.Calendar()
|
||||
self._view = RunidView(model=tree.DateDeviceList(sql, today))
|
||||
|
||||
self._view.bind_property("runid", self, "runid")
|
||||
self._calendar.connect("day-selected", self.__day_selected)
|
||||
self._calendar.connect("next-month", self.__date_changed)
|
||||
self._calendar.connect("next-year", self.__date_changed)
|
||||
self._calendar.connect("prev-month", self.__date_changed)
|
||||
self._calendar.connect("prev-year", self.__date_changed)
|
||||
|
||||
self.__mark_days(today)
|
||||
|
||||
self.append(self._calendar)
|
||||
self.append(self._view)
|
||||
|
||||
def __day_selected(self, calendar: Gtk.Calendar) -> None:
|
||||
self.__select_day(datetime.date(*calendar.get_date().get_ymd()))
|
||||
|
||||
def __date_changed(self, calendar: Gtk.Calendar) -> None:
|
||||
date = datetime.date(*calendar.get_date().get_ymd())
|
||||
self.__mark_days(date)
|
||||
self.__select_day(date)
|
||||
|
||||
def __select_day(self, date: datetime.date) -> None:
|
||||
self._view.model = tree.DateDeviceList(self.sql, date)
|
||||
|
||||
def __mark_days(self, date: datetime.date) -> None:
|
||||
min = datetime.datetime.combine(date.replace(day=1), datetime.time())
|
||||
max = (min + datetime.timedelta(days=40)).replace(day=1)
|
||||
|
||||
self._calendar.clear_marks()
|
||||
for stamp in self.sql("""SELECT DISTINCT timestamp FROM tagged_runs
|
||||
WHERE timestamp >= ? AND timestamp < ?""",
|
||||
min, max).fetchall():
|
||||
ts = datetime.datetime.fromisoformat(stamp['timestamp'])
|
||||
self._calendar.mark_day(ts.day)
|
||||
|
||||
|
||||
class Sidebar(Gtk.Box):
|
||||
"""Our sidebar for seleting an xfstests run."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
sql = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection) -> None:
|
||||
"""Initialize a CalendarView instance."""
|
||||
animation = Gtk.StackTransitionType.OVER_LEFT_RIGHT
|
||||
super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL)
|
||||
self._stack = Gtk.Stack(transition_type=animation)
|
||||
self._switcher = Gtk.StackSwitcher(stack=self._stack,
|
||||
margin_top=6, margin_bottom=6,
|
||||
margin_start=80, margin_end=80)
|
||||
self._calendar = CalendarView(sql)
|
||||
self._tags = RunidView(model=tree.TagList(sql))
|
||||
|
||||
self._calendar.bind_property("runid", self, "runid")
|
||||
self._tags.bind_property("runid", self, "runid")
|
||||
|
||||
self.__add_page(self._calendar, "Calendar", "month-symbolic")
|
||||
self.__add_page(self._tags, "Tags", "tag-symbolic")
|
||||
|
||||
self._switcher.add_css_class("large-icons")
|
||||
|
||||
self.append(self._switcher)
|
||||
self.append(Gtk.Separator())
|
||||
self.append(self._stack)
|
||||
|
||||
def __add_page(self, page: Gtk.Widget, title: str, icon: str) -> None:
|
||||
pg = self._stack.add_titled(page, title.lower(), title)
|
||||
pg.set_icon_name(icon)
|
224
xfstestsdb/gtk/tree.py
Normal file
224
xfstestsdb/gtk/tree.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Our tree model for selecting an xfstests run to view."""
|
||||
import bisect
|
||||
import datetime
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from .. import sqlite
|
||||
|
||||
|
||||
class XfstestsRun(GObject.GObject):
|
||||
"""A single XfstessRun."""
|
||||
|
||||
timestamp = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
runid = GObject.Property(type=int)
|
||||
ftime = GObject.Property(type=str)
|
||||
|
||||
def __init__(self, runid: int, timestamp: datetime.datetime,
|
||||
ftime: str = "%T") -> None:
|
||||
"""Initialize an XfstestsRun instance."""
|
||||
super().__init__(timestamp=timestamp, runid=runid, ftime=ftime)
|
||||
|
||||
def __gt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare the timestamps an runids of two XfstestRun objects."""
|
||||
return (self.timestamp, self.runid) > (rhs.timestamp, rhs.runid)
|
||||
|
||||
def __lt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare the timestamps and runids of two XfstestsRun objects."""
|
||||
return (self.timestamp, self.runid) < (rhs.timestamp, rhs.runid)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get a string representation of this run."""
|
||||
return f"#{self.runid}: {self.timestamp.strftime(self.ftime)}"
|
||||
|
||||
|
||||
class DeviceRunsList(GObject.GObject, Gio.ListModel):
|
||||
"""Contains a single test device with multiple runs."""
|
||||
|
||||
name = GObject.Property(type=str)
|
||||
n_items = GObject.Property(type=int)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
"""Initialize our DeviceRunsList."""
|
||||
super().__init__(name=name)
|
||||
self.__runs = []
|
||||
|
||||
def __lt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare two DeviceRunsList objects."""
|
||||
return self.name < rhs.name
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the name of this device."""
|
||||
return self.name
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return XfstestsRun.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of runs added to this device."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> XfstestsRun:
|
||||
"""Get the run at the nth position."""
|
||||
return self.__runs[n]
|
||||
|
||||
def add_run(self, runid: int, timestamp: datetime.datetime,
|
||||
ftime: str = "%T") -> None:
|
||||
"""Add a run to the Device's list."""
|
||||
bisect.insort(self.__runs, XfstestsRun(runid, timestamp, ftime))
|
||||
self.n_items = len(self.__runs)
|
||||
|
||||
def get_earliest_run(self) -> XfstestsRun | None:
|
||||
"""Get the earliest XfstestsRun that we know about."""
|
||||
return self.__runs[0] if self.n_items > 0 else None
|
||||
|
||||
|
||||
class DateDeviceList(GObject.GObject, Gio.ListModel):
|
||||
"""A list containing test devices used on a specific day."""
|
||||
|
||||
date = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
n_items = GObject.Property(type=int)
|
||||
treemodel = GObject.Property(type=Gtk.TreeListModel)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection, date: datetime.date) -> None:
|
||||
"""Initialize our DateDeviceList."""
|
||||
super().__init__(date=date)
|
||||
|
||||
devices = {}
|
||||
for row in sql("""SELECT DISTINCT runid, device, timestamp
|
||||
FROM tagged_runs
|
||||
WHERE timestamp >= ? AND timestamp < ?""",
|
||||
*self.get_range(date)).fetchall():
|
||||
if (dev := devices.get(row['device'])) is None:
|
||||
devices[row['device']] = dev = DeviceRunsList(row['device'])
|
||||
ts = datetime.datetime.fromisoformat(row['timestamp'])
|
||||
dev.add_run(row['runid'], ts)
|
||||
|
||||
self.__items = sorted(devices.values())
|
||||
self.n_items = len(self.__items)
|
||||
self.treemodel = Gtk.TreeListModel.new(root=self, passthrough=False,
|
||||
autoexpand=True,
|
||||
create_func=self.__create_func)
|
||||
|
||||
def __create_func(self, item: GObject.GObject) -> Gio.ListModel:
|
||||
if isinstance(item, DeviceRunsList):
|
||||
return item
|
||||
return None
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return DeviceRunsList.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of items in the list."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> DeviceRunsList:
|
||||
"""Get a specific item in the list."""
|
||||
return self.__items[n]
|
||||
|
||||
def get_range(self, date: datetime.date) -> tuple[datetime.datetime,
|
||||
datetime.datetime]:
|
||||
"""Get the minimum and maximum timestamps for the date."""
|
||||
min = datetime.datetime.combine(date, datetime.time())
|
||||
max = min + datetime.timedelta(days=1)
|
||||
return (min, max)
|
||||
|
||||
|
||||
class TagDeviceList(GObject.GObject, Gio.ListModel):
|
||||
"""Contains a single tag with multiple devices and runs."""
|
||||
|
||||
name = GObject.Property(type=str)
|
||||
n_items = GObject.Property(type=int)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
"""Initialize our TagDeviceList."""
|
||||
super().__init__(name=name)
|
||||
self.__devices = []
|
||||
|
||||
def __lt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare two TagDeviceList objects."""
|
||||
lhs_run = self.get_earliest_run()
|
||||
rhs_run = rhs.get_earliest_run()
|
||||
if lhs_run.runid == rhs_run.runid:
|
||||
return self.name > rhs.name
|
||||
return lhs_run > rhs_run
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the name of this tag."""
|
||||
return self.name
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return DeviceRunsList.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of devices added to this tag."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> DeviceRunsList:
|
||||
"""Get the device at the nth position."""
|
||||
return self.__devices[n]
|
||||
|
||||
def add_run(self, runid: int, device: str,
|
||||
timestamp: datetime.datetime) -> None:
|
||||
"""Add a run to the Tag's list."""
|
||||
pos = bisect.bisect_left(self.__devices, device, key=str)
|
||||
if pos < len(self.__devices) and self.__devices[pos].name == device:
|
||||
dev = self.__devices[pos]
|
||||
else:
|
||||
dev = DeviceRunsList(device)
|
||||
self.__devices.insert(pos, dev)
|
||||
|
||||
dev.add_run(runid, timestamp, "%c")
|
||||
self.n_items = len(self.__devices)
|
||||
|
||||
def get_earliest_run(self) -> XfstestsRun | None:
|
||||
"""Get the earliest run added to the tag."""
|
||||
runs = [d.get_earliest_run() for d in self.__devices]
|
||||
return min(runs, default=None)
|
||||
|
||||
|
||||
class TagList(GObject.GObject, Gio.ListModel):
|
||||
"""A list containing all tagged xfstests runs."""
|
||||
|
||||
n_items = GObject.Property(type=int)
|
||||
treemodel = GObject.Property(type=Gtk.TreeListModel)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection) -> None:
|
||||
"""Initialize our TagList."""
|
||||
super().__init__()
|
||||
|
||||
tags = {}
|
||||
for row in sql("""SELECT DISTINCT runid, device, timestamp, tag
|
||||
FROM tagged_runs WHERE tag IS NOT NULL""").fetchall():
|
||||
if (tag := tags.get(row['tag'])) is None:
|
||||
tags[row['tag']] = tag = TagDeviceList(row['tag'])
|
||||
ts = datetime.datetime.fromisoformat(row['timestamp'])
|
||||
tag.add_run(row['runid'], row['device'], ts)
|
||||
|
||||
self.__items = sorted(tags.values())
|
||||
self.n_items = len(self.__items)
|
||||
self.treemodel = Gtk.TreeListModel.new(root=self, passthrough=False,
|
||||
autoexpand=False,
|
||||
create_func=self.__create_func)
|
||||
|
||||
def __create_func(self, item: GObject.GObject) -> Gio.ListModel:
|
||||
if isinstance(item, TagDeviceList | DeviceRunsList):
|
||||
return item
|
||||
return None
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return TagDeviceList.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of items in the list."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> TagDeviceList:
|
||||
"""Get a specific item in the list."""
|
||||
return self.__items[n]
|
|
@ -33,6 +33,8 @@ class XunitView(Gtk.ScrolledWindow):
|
|||
hexpand=True),
|
||||
**kwargs)
|
||||
self.props.child.get_model().set_model(self.filtermodel)
|
||||
self.add_css_class("undershoot-top")
|
||||
self.add_css_class("undershoot-bottom")
|
||||
self.add_css_class("card")
|
||||
|
||||
def do_make_factory(self, xunit: str) -> row.XunitFactory:
|
||||
|
@ -242,11 +244,11 @@ class MessagesView(Gtk.Box):
|
|||
|
||||
def __init__(self):
|
||||
"""Initialize a MessagesView."""
|
||||
icon = "go-previous-symbolic"
|
||||
icon = "down-large-symbolic"
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL, margin_top=24,
|
||||
margin_start=24, margin_end=24, margin_bottom=24)
|
||||
self._back = Gtk.Button(child=Adw.ButtonContent(icon_name=icon,
|
||||
label="back"))
|
||||
label="done"))
|
||||
self._title = Adw.WindowTitle()
|
||||
self._stdout = MessageView("stdout")
|
||||
self._stderr = MessageView("stderr")
|
||||
|
@ -299,7 +301,7 @@ class XfstestsView(Gtk.Box):
|
|||
|
||||
def __init__(self):
|
||||
"""Initialize an XfstestsView."""
|
||||
animation = Gtk.StackTransitionType.OVER_LEFT_RIGHT
|
||||
animation = Gtk.StackTransitionType.OVER_UP_DOWN
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||
self._environview = EnvironmentView()
|
||||
self._propertyview = PropertyView()
|
||||
|
|
|
@ -10,9 +10,11 @@ class Window(Adw.Window):
|
|||
"""Our customized Window displayed to the user."""
|
||||
|
||||
child = GObject.Property(type=Gtk.Widget)
|
||||
sidebar = GObject.Property(type=Gtk.Widget)
|
||||
headerbar = GObject.Property(type=Adw.HeaderBar)
|
||||
title = GObject.Property(type=Adw.WindowTitle)
|
||||
runid = GObject.Property(type=int)
|
||||
show_sidebar = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Set up our Window."""
|
||||
|
@ -20,13 +22,26 @@ class Window(Adw.Window):
|
|||
content=Gtk.Box(orientation=Gtk.Orientation.VERTICAL),
|
||||
title=Adw.WindowTitle(title="xfstestsdb gtk"),
|
||||
headerbar=Adw.HeaderBar(), **kwargs)
|
||||
self._splitview = Adw.OverlaySplitView(content=self.child,
|
||||
sidebar=self.sidebar,
|
||||
collapsed=True,
|
||||
show_sidebar=self.show_sidebar)
|
||||
self._show_sidebar = Gtk.ToggleButton(icon_name="sidebar-show",
|
||||
active=self.show_sidebar)
|
||||
|
||||
self.headerbar.pack_start(self._show_sidebar)
|
||||
self.headerbar.props.title_widget = self.title
|
||||
self.headerbar.add_css_class("flat")
|
||||
|
||||
self.props.content.append(self.headerbar)
|
||||
self.props.content.append(Adw.Bin(child=self.child))
|
||||
self.props.content.append(self._splitview)
|
||||
|
||||
self._show_sidebar.bind_property("active", self, "show-sidebar",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("show-sidebar", self._splitview, "show-sidebar")
|
||||
self.bind_property("sidebar", self._splitview, "sidebar")
|
||||
self.bind_property("child", self._splitview, "content")
|
||||
|
||||
self.bind_property("child", self.headerbar.get_next_sibling(), "child")
|
||||
self.connect("notify::runid", self.__notify_runid)
|
||||
|
||||
if __debug__:
|
||||
|
|
|
@ -52,6 +52,8 @@ class Command(commands.Command):
|
|||
if isinstance(args.tags, str):
|
||||
filter.append("tag GLOB ?")
|
||||
sql_args.append(args.tags)
|
||||
else:
|
||||
filter.append("tag IS NOT NULL")
|
||||
|
||||
if args.timestamp:
|
||||
filter.append("timestamp GLOB ?")
|
||||
|
@ -62,6 +64,8 @@ class Command(commands.Command):
|
|||
if isinstance(args.xunits, str):
|
||||
filter.append("xunit GLOB ?")
|
||||
sql_args.append(args.xunits)
|
||||
else:
|
||||
filter.append("xunit IS NOT NULL")
|
||||
|
||||
if len(filter) > 0:
|
||||
sql_where = " WHERE " + " AND ".join(filter) + " "
|
||||
|
|
|
@ -16,6 +16,8 @@ class Command(commands.Command):
|
|||
self.parser.add_argument("device", metavar="device", nargs=1,
|
||||
help="the test device used for"
|
||||
"this xfstests run")
|
||||
self.parser.add_argument("--terse", action="store_true",
|
||||
help="output only the new runid")
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Create a new row in the xfstestsdb_runs table."""
|
||||
|
@ -24,6 +26,9 @@ class Command(commands.Command):
|
|||
datetime(timestamp, 'localtime') as timestamp""",
|
||||
args.device[0])
|
||||
row = cur.fetchone()
|
||||
print(f"created run #{row['runid']}", end=" ")
|
||||
print(f"with test device '{row['device']}'", end=" ")
|
||||
print(f"[{row['timestamp']}]")
|
||||
if args.terse:
|
||||
print(row['runid'])
|
||||
else:
|
||||
print(f"created run #{row['runid']}", end=" ")
|
||||
print(f"with test device '{row['device']}'", end=" ")
|
||||
print(f"[{row['timestamp']}]")
|
||||
|
|
|
@ -76,3 +76,7 @@ class Connection:
|
|||
cur = self.sql.executescript(f.read())
|
||||
self.sql.commit()
|
||||
return cur
|
||||
|
||||
def vacuum(self) -> sqlite3.Cursor | None:
|
||||
"""Vacuum the database."""
|
||||
return self.sql.execute("VACUUM")
|
||||
|
|
Loading…
Reference in New Issue
Block a user