From 70274c448c94140db9b49e4868fdf4b0980d5660 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Thu, 2 Nov 2023 14:59:27 -0400 Subject: [PATCH] gtk: Add a ListModel for holding the XfstestsRuns attached to a device I will add to this to create a TreeModel to display individual runs associated with each device in the sidebar. Signed-off-by: Anna Schumaker --- tests/gtk/test_tree.py | 120 +++++++++++++++++++++++++++++++++++++++++ xfstestsdb/gtk/tree.py | 74 +++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 tests/gtk/test_tree.py create mode 100644 xfstestsdb/gtk/tree.py diff --git a/tests/gtk/test_tree.py b/tests/gtk/test_tree.py new file mode 100644 index 0000000..2ce86f0 --- /dev/null +++ b/tests/gtk/test_tree.py @@ -0,0 +1,120 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Tests our xfstests run selector tree.""" +import datetime +import unittest +import xfstestsdb.gtk.tree +from gi.repository import GObject +from gi.repository import Gio + + +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) diff --git a/xfstestsdb/gtk/tree.py b/xfstestsdb/gtk/tree.py new file mode 100644 index 0000000..5255ae7 --- /dev/null +++ b/xfstestsdb/gtk/tree.py @@ -0,0 +1,74 @@ +# 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 + + +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