Compare commits

...

10 Commits

Author SHA1 Message Date
Anna Schumaker 2deb484754 xfstestsdb 1.4
Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 10:43:41 -04:00
Anna Schumaker af1ab81ea2 gtk: Increase the default Window size
Now that we've added properties, we need a slightly larger window to
display everything clearly.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-16 10:34:21 -04:00
Anna Schumaker 3dc8179624 gtk: Add a PropertyView to the XfstestsView
And have the application create a PropertyList that is passed to the
View.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-16 10:34:21 -04:00
Anna Schumaker c145a67ae6 gtk: Add a PropertyView
The PropertyView displays the properties of each Xunit added to the
displayed Xfstests run.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-16 10:34:21 -04:00
Anna Schumaker 7ae246677b gtk: Add a PropertyFactory
The PropertyFactory is used to display the individual property rows from
each xunit.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-16 10:34:21 -04:00
Anna Schumaker 59cb699bd9 gtk: Add a PropertyList model
Along with Property, PropertyValue, and PropertyFilter objects that are used
togther to show specific properties to the user when placed in a
Gtk.ColumnView.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-16 10:34:20 -04:00
Anna Schumaker 4838889c56 gtk: Create a Factory base class
And convert our other Factory instances to inherit from it. This lets me
set up a Gtk.Inscription() in a singe way that is used everywere, and
abstract out some other binding code to make implementing new factories
easier.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-15 16:37:55 -04:00
Anna Schumaker 5dc7735ba7 gtk: Create an XunitList base class
The XunitList implements the Gio.ListModel interface, and builds in some
virtual functions to query and fill out the rows in the list in a
standard way. I also update the TestCaseList and SummaryList to both
inherit from this class.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-15 13:56:27 -04:00
Anna Schumaker c45ec1909e gtk: Create an XunitRow base class
This Object is intended to be returned by a Gio.ListModel to show
Xfstests xunit information. I change the TestCase and Summary classes to
inherit from this class, removing a lot of duplicated code.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-15 13:01:24 -04:00
Anna Schumaker ad08357121 gtk: Create a XunitCell object and rename the XunitResult object
I use the XunitCell as a base class for the (renamed) TestResult and
SettingsValue objects. This lets us combine common values and
functionality in one reusable place.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-15 13:00:51 -04:00
14 changed files with 783 additions and 304 deletions

View File

@ -9,23 +9,213 @@ from gi.repository import Gio
from gi.repository import Gtk
class TestXunitResult(unittest.TestCase):
class TestXunitCell(unittest.TestCase):
"""Test case for our base XunitCell object."""
def test_cell(self):
"""Test creating a Cell instance."""
cell = xfstestsdb.gtk.model.XunitCell(name="my xunit name")
self.assertIsInstance(cell, GObject.GObject)
self.assertEqual(cell.name, "my xunit name")
self.assertEqual(str(cell), "my xunit name")
class TestXunitRow(unittest.TestCase):
"""Test case for our base XunitRow object."""
def setUp(self):
"""Set up common variables."""
self.row = xfstestsdb.gtk.model.XunitRow(name="row-name")
def test_init(self):
"""Test that the XunitRow is set up properly."""
self.assertIsInstance(self.row, GObject.GObject)
self.assertEqual(self.row.name, "row-name")
def test_compare(self):
"""Test the less-than operator on XunitRows."""
row2 = xfstestsdb.gtk.model.XunitRow(name="row-name-2")
self.assertTrue(self.row < row2)
self.assertFalse(row2 < self.row)
self.assertFalse(self.row < self.row)
def test_xunits(self):
"""Test adding xunits to a XunitRow."""
self.assertIsNone(self.row["xunit-1"])
self.row.add_xunit("xunit-1")
self.assertSetEqual(self.row.get_results(), {"xunit-1"})
xunit = self.row["xunit-1"]
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitCell)
self.assertEqual(xunit.name, "xunit-1")
self.row.add_xunit("xunit-2")
self.assertSetEqual(self.row.get_results(), {"xunit-1", "xunit-2"})
xunit = self.row["xunit-2"]
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitCell)
self.assertEqual(xunit.name, "xunit-2")
class TestXunitList(unittest.TestCase):
"""Test case for our base XunitList object."""
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(["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)])
self.xulist = xfstestsdb.gtk.model.XunitList(self.xfstestsdb.sql, 1)
def test_init(self):
"""Test that the XunitList was set up properly."""
self.assertIsInstance(self.xulist, GObject.GObject)
self.assertIsInstance(self.xulist, Gio.ListModel)
self.assertEqual(self.xulist.runid, 1)
def test_get_item_type(self):
"""Test the get_item_type() function."""
self.assertEqual(self.xulist.get_item_type(),
xfstestsdb.gtk.model.XunitRow.__gtype__)
def test_get_n_items(self):
"""Test the get_n_items() function."""
self.assertEqual(self.xulist.get_n_items(), 0)
self.assertEqual(self.xulist.n_items, 0)
self.xulist.n_items = 2
self.assertEqual(self.xulist.get_n_items(), 2)
def test_get_item(self):
"""Test the get_item() function."""
self.assertIsNone(self.xulist.get_item(0))
def test_get_xunits(self):
"""Test the get_xunits() function."""
self.assertListEqual(self.xulist.get_xunits(), ["xunit-1", "xunit-2"])
class TestPropertyValue(unittest.TestCase):
"""Tests a single Xunit Property instance."""
def test_xunit_property(self):
"""Test creating an xunit property instance."""
property = xfstestsdb.gtk.model.PropertyValue(name="my xunit name",
key="key", value="123")
self.assertIsInstance(property, xfstestsdb.gtk.model.XunitCell)
self.assertEqual(property.name, "my xunit name")
self.assertEqual(property.key, "key")
self.assertEqual(property.value, "123")
self.assertEqual(str(property), "key = 123")
class TestProperty(unittest.TestCase):
"""Tests our Property GObject."""
def setUp(self):
"""Set up common variables."""
self.property = xfstestsdb.gtk.model.Property(name="property")
def test_init(self):
"""Check that the Property is set up properly."""
self.assertIsInstance(self.property, xfstestsdb.gtk.model.XunitRow)
self.assertEqual(self.property.name, "property")
def test_xunits(self):
"""Test adding xunits to a Property."""
self.assertIsNone(self.property["xunit-1"])
self.property.add_xunit("xunit-1", "PLATFORM", "linux-123")
property = self.property["xunit-1"]
self.assertIsInstance(property, xfstestsdb.gtk.model.PropertyValue)
self.assertEqual(property.name, "xunit-1")
self.assertEqual(property.key, "PLATFORM")
self.assertEqual(property.value, "linux-123")
self.assertTrue(self.property.all_same_value())
self.property.add_xunit("xunit-2", "PLATFORM", "linux-123")
property = self.property["xunit-2"]
self.assertIsInstance(property, xfstestsdb.gtk.model.PropertyValue)
self.assertEqual(property.name, "xunit-2")
self.assertEqual(property.key, "PLATFORM")
self.assertEqual(property.value, "linux-123")
self.assertTrue(self.property.all_same_value())
self.property.add_xunit("xunit-3", "PLATFORM", "linux-456")
self.assertFalse(self.property.all_same_value())
class TestPropertyList(unittest.TestCase):
"""Tests our PropertyList GObject."""
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(["xunit", "read", "--name", "xunit-1",
"1", str(tests.xunit.XUNIT_1)])
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-2",
"1", str(tests.xunit.XUNIT_2)])
self.prlist = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 1)
def test_init(self):
"""Test that the PropertyList was set up properly."""
self.assertIsInstance(self.prlist, xfstestsdb.gtk.model.XunitList)
self.assertEqual(self.prlist.runid, 1)
self.assertEqual(self.prlist.n_items, 15)
class TestPropertyFilter(unittest.TestCase):
"""Tests our Gtk.Filter customized for filtering Properties."""
def setUp(self):
"""Set up common variables."""
self.filter = xfstestsdb.gtk.model.PropertyFilter()
def test_init(self):
"""Test that the TestCaseFilter is set up properly."""
self.assertIsInstance(self.filter, Gtk.Filter)
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.SOME)
def test_match(self):
"""Test matching Properties with the Filter."""
property = xfstestsdb.gtk.model.Property("name")
self.assertTrue(self.filter.match(property))
for prop in ["CPUS", "HOST_OPTIONS", "MEM_KB", "LOAD_FACTOR",
"NUMA_NODES", "OVL_LOWER", "OVL_UPPER", "OVL_WORK",
"PLATFORM", "SECTION", "SWAP_KB", "TIME_FACTOR"]:
with self.subTest(property=prop):
property.name = prop
self.assertFalse(self.filter.match(property))
class TestTestResult(unittest.TestCase):
"""Tests a single TestCase Xunit instance."""
def test_xunit_result(self):
"""Test creating an xunit instance."""
xunit = xfstestsdb.gtk.model.XunitResult(name="my xunit name",
status="passed", time=123,
message="my message",
stdout="my stdout",
stderr="my stderr")
self.assertIsInstance(xunit, GObject.GObject)
xunit = xfstestsdb.gtk.model.TestResult(name="my xunit name",
status="passed", time=123,
message="my message",
stdout="my stdout",
stderr="my stderr")
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitCell)
self.assertEqual(xunit.name, "my xunit name")
self.assertEqual(xunit.status, "passed")
self.assertEqual(xunit.time, 123)
self.assertEqual(xunit.message, "my message")
self.assertEqual(xunit.stdout, "my stdout")
self.assertEqual(xunit.stderr, "my stderr")
self.assertEqual(str(xunit), "passed")
class TestTestCase(unittest.TestCase):
@ -37,16 +227,9 @@ class TestTestCase(unittest.TestCase):
def test_init(self):
"""Check that the TestCase is set up properly."""
self.assertIsInstance(self.testcase, GObject.GObject)
self.assertIsInstance(self.testcase, xfstestsdb.gtk.model.XunitRow)
self.assertEqual(self.testcase.name, "test-case")
def test_compare(self):
"""Test the less-than operator on TestCases."""
testcase2 = xfstestsdb.gtk.model.TestCase(name="test-case-2")
self.assertTrue(self.testcase < testcase2)
self.assertFalse(testcase2 < self.testcase)
self.assertFalse(self.testcase < self.testcase)
def test_xunits(self):
"""Test adding xunits to a TestCase."""
self.assertIsNone(self.testcase["xunit-1"])
@ -54,7 +237,7 @@ class TestTestCase(unittest.TestCase):
self.testcase.add_xunit("xunit-1", "passed", 123, "message",
"stdout", "stderr")
xunit = self.testcase["xunit-1"]
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitResult)
self.assertIsInstance(xunit, xfstestsdb.gtk.model.TestResult)
self.assertEqual(xunit.name, "xunit-1")
self.assertEqual(xunit.status, "passed")
self.assertEqual(xunit.time, 123)
@ -64,7 +247,7 @@ class TestTestCase(unittest.TestCase):
self.testcase.add_xunit("xunit-2", "failed", 456, None, None, None)
xunit = self.testcase["xunit-2"]
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitResult)
self.assertIsInstance(xunit, xfstestsdb.gtk.model.TestResult)
self.assertEqual(xunit.name, "xunit-2")
self.assertEqual(xunit.status, "failed")
self.assertEqual(xunit.time, 456)
@ -72,18 +255,6 @@ class TestTestCase(unittest.TestCase):
self.assertEqual(xunit.stdout, "")
self.assertEqual(xunit.stderr, "")
def test_get_results(self):
"""Test getting a set of results for this test case."""
self.testcase.add_xunit("xunit-1", "passed", 123, "", "", "")
self.assertSetEqual(self.testcase.get_results(), {"passed"})
self.testcase.add_xunit("xunit-2", "passed", 123, "", "", "")
self.assertSetEqual(self.testcase.get_results(), {"passed"})
self.testcase.add_xunit("xunit-3", "skipped", 123, "", "", "")
self.assertSetEqual(self.testcase.get_results(), {"passed", "skipped"})
self.testcase.add_xunit("xunit-4", "failure", 123, "", "", "")
self.assertSetEqual(self.testcase.get_results(),
{"passed", "skipped", "failure"})
class TestCaseList(unittest.TestCase):
"""Tests our TestCaseList Gio.ListModel."""
@ -102,18 +273,8 @@ class TestCaseList(unittest.TestCase):
def test_init(self):
"""Test that the TestCaseList was set up properly."""
self.assertIsInstance(self.tclist, GObject.GObject)
self.assertIsInstance(self.tclist, Gio.ListModel)
self.assertIsInstance(self.tclist, xfstestsdb.gtk.model.XunitList)
self.assertEqual(self.tclist.runid, 1)
def test_get_item_type(self):
"""Test the get_item_type() function."""
self.assertEqual(self.tclist.get_item_type(),
xfstestsdb.gtk.model.TestCase.__gtype__)
def test_get_n_items(self):
"""Test the get_n_items() function."""
self.assertEqual(self.tclist.get_n_items(), 10)
self.assertEqual(self.tclist.n_items, 10)
def test_get_item(self):
@ -132,10 +293,6 @@ class TestCaseList(unittest.TestCase):
self.assertIsNone(self.tclist.get_item(10))
def test_get_xunits(self):
"""Test the get_xunits() function."""
self.assertListEqual(self.tclist.get_xunits(), ["xunit-1", "xunit-2"])
class TestCaseFilter(unittest.TestCase):
"""Tests our Gtk.Filter customized for filtering TestCases."""
@ -214,7 +371,7 @@ class TestSummaryValue(unittest.TestCase):
value = xfstestsdb.gtk.model.SummaryValue(name="my xunit name",
value=12345,
unit="testcase")
self.assertIsInstance(value, GObject.GObject)
self.assertIsInstance(value, xfstestsdb.gtk.model.XunitCell)
self.assertEqual(value.name, "my xunit name")
self.assertEqual(value.unit, "testcase")
self.assertEqual(value.value, 12345)
@ -238,7 +395,7 @@ class TestSummary(unittest.TestCase):
def test_init(self):
"""Check that the Summary is set up properly."""
self.assertIsInstance(self.summary, GObject.GObject)
self.assertIsInstance(self.summary, xfstestsdb.gtk.model.XunitRow)
self.assertEqual(self.summary.name, "passed")
def test_compare(self):
@ -265,13 +422,6 @@ class TestSummary(unittest.TestCase):
self.assertEqual(xunit.value, 123)
self.assertEqual(xunit.unit, "unit")
def test_get_results(self):
"""Test getting a set of results for this summary."""
self.summary.add_xunit("xunit-1", 1, "unit")
self.assertSetEqual(self.summary.get_results(), {"1 unit"})
self.summary.add_xunit("xunit-2", 2, "unit")
self.assertSetEqual(self.summary.get_results(), {"1 unit", "2 units"})
class TestSummaryList(unittest.TestCase):
"""Test case for our summary list."""
@ -290,18 +440,8 @@ class TestSummaryList(unittest.TestCase):
def test_init(self):
"""Test that the SummaryList was set up properly."""
self.assertIsInstance(self.summary, GObject.GObject)
self.assertIsInstance(self.summary, Gio.ListModel)
self.assertIsInstance(self.summary, xfstestsdb.gtk.model.XunitList)
self.assertEqual(self.summary.runid, 1)
def test_get_item_type(self):
"""Test the get_item_type() function."""
self.assertEqual(self.summary.get_item_type(),
xfstestsdb.gtk.model.Summary.__gtype__)
def test_get_n_items(self):
"""Test the get_n_items() function."""
self.assertEqual(self.summary.get_n_items(), 4)
self.assertEqual(self.summary.n_items, 4)
def test_get_item(self):
@ -318,7 +458,3 @@ class TestSummaryList(unittest.TestCase):
case "skipped": expected = {"3 testcases"}
case "time": expected = {"43 seconds"}
self.assertSetEqual(summary.get_results(), expected)
def test_get_xunits(self):
"""Test the get_xunits() function."""
self.assertListEqual(self.summary.get_xunits(), ["xunit-1", "xunit-2"])

View File

@ -6,6 +6,66 @@ from gi.repository import Gtk
from gi.repository import Adw
class TestFactory(unittest.TestCase):
"""Tests our base Gtk.Factory to make Gtk.Inscriptions."""
def setUp(self):
"""Set up common variables."""
self.xunitrow = xfstestsdb.gtk.model.XunitRow("xunit/row")
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.xunitrow)
self.factory = xfstestsdb.gtk.row.Factory()
def test_init(self):
"""Test that the Factory was initialized correctly."""
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
def test_setup(self):
"""Test that the factory implements the 'setup' signal."""
with unittest.mock.patch.object(self.factory,
"do_setup") as mock_setup:
self.factory.emit("setup", self.listitem)
self.assertIsInstance(self.listitem.get_child(), Gtk.Inscription)
self.assertEqual(self.listitem.get_child().props.xalign, 0.5)
self.assertEqual(self.listitem.get_child().props.nat_chars, 18)
self.assertEqual(self.listitem.get_child().props.text_overflow,
Gtk.InscriptionOverflow.ELLIPSIZE_END)
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
mock_setup.assert_called_with(self.listitem.get_child())
def test_bind(self):
"""Test that the factory implements the 'bind' signal."""
with unittest.mock.patch.object(self.factory, "do_bind") as mock_bind:
self.factory.emit("setup", self.listitem)
self.factory.emit("bind", self.listitem)
mock_bind.assert_called_with(self.xunitrow,
self.listitem.get_child())
def test_unbind(self):
"""Test that the factory implements the 'unbind' signal."""
with unittest.mock.patch.object(self.factory,
"do_unbind") as mock_unbind:
self.factory.emit("setup", self.listitem)
self.factory.emit("bind", self.listitem)
self.listitem.get_child().set_text("text")
self.factory.emit("unbind", self.listitem)
self.assertIsNone(self.listitem.get_child().get_text())
mock_unbind.assert_called_with(self.xunitrow,
self.listitem.get_child())
def test_teardown(self):
"""Test that the factory implements the 'teardown' signal."""
with unittest.mock.patch.object(self.factory,
"do_teardown") as mock_teardown:
self.factory.emit("setup", self.listitem)
child = self.listitem.get_child()
self.factory.emit("teardown", self.listitem)
self.assertIsNone(self.listitem.get_child())
mock_teardown.assert_called_with(child)
class TestLabelFactory(unittest.TestCase):
"""Tests our Gtk.Factory to make Gtk.Labels."""
@ -15,12 +75,12 @@ class TestLabelFactory(unittest.TestCase):
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.testcase)
self.factory = xfstestsdb.gtk.row.LabelFactory("name")
self.factory = xfstestsdb.gtk.row.LabelFactory(property="name")
self.group = xfstestsdb.gtk.row.LabelFactory.group
def test_init(self):
"""Test that the factory was initialized correctly."""
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.Factory)
self.assertEqual(self.factory.property, "name")
def test_size_group(self):
@ -33,8 +93,6 @@ class TestLabelFactory(unittest.TestCase):
def test_setup(self):
"""Test that the factory implements the 'setup' signal."""
self.factory.emit("setup", self.listitem)
self.assertIsInstance(self.listitem.get_child(), Gtk.Label)
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
self.assertIn(self.listitem.get_child(), self.group.get_widgets())
def test_bind(self):
@ -50,14 +108,13 @@ class TestLabelFactory(unittest.TestCase):
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_text(), "")
self.assertIsNone(self.listitem.get_child().get_text())
def test_teardown(self):
"""Test that the factory implements the 'teardown' signal."""
self.factory.emit("setup", self.listitem)
child = self.listitem.get_child()
self.factory.emit("teardown", self.listitem)
self.assertIsNone(self.listitem.get_child())
self.assertNotIn(child, self.group.get_widgets())
def test_styles(self):
@ -79,6 +136,31 @@ class TestLabelFactory(unittest.TestCase):
self.assertFalse(child.has_css_class(expected))
class TestPropertyFactory(unittest.TestCase):
"""Tests our Gtk.Factory to show xunit properties."""
def setUp(self):
"""Set up common variables."""
self.property = xfstestsdb.gtk.model.Property("property")
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.property)
self.factory = xfstestsdb.gtk.row.PropertyFactory("xunit-1")
def test_init(self):
"""Test that the factory was initialized correctly."""
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.Factory)
self.assertEqual(self.factory.xunit, "xunit-1")
def test_bind_different(self):
"""Test binding to the a property when all values are different."""
self.property.add_xunit("xunit-1", "property", "value")
self.factory.emit("setup", self.listitem)
self.factory.emit("bind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "value")
self.assertEqual(self.listitem.get_child().get_tooltip_text(), "value")
class TestResultFactory(unittest.TestCase):
"""Tests our Gtk.Factory to show test results."""
@ -99,16 +181,9 @@ class TestResultFactory(unittest.TestCase):
def test_init(self):
"""Test that the factory was initialized correctly."""
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.Factory)
self.assertEqual(self.factory.xunit, "xunit-1")
def test_setup(self):
"""Test that the factory implements the 'setup' signal."""
self.factory.emit("setup", self.listitem)
self.assertIsInstance(self.listitem.get_child(), Gtk.Label)
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
self.assertEqual(self.parent.get_child(), self.listitem.get_child())
def test_bind_passed(self):
"""Test binding to a passing test."""
self.testcase.add_xunit("xunit-1", "passed", 3, "", None, None)
@ -119,7 +194,6 @@ class TestResultFactory(unittest.TestCase):
self.assertTrue(self.parent.has_css_class("passed"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.parent.has_css_class("passed"))
def test_bind_skipped(self):
@ -134,7 +208,6 @@ class TestResultFactory(unittest.TestCase):
self.assertTrue(self.parent.has_css_class("skipped"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.parent.has_css_class("skipped"))
def test_bind_failed(self):
@ -149,23 +222,16 @@ class TestResultFactory(unittest.TestCase):
self.assertTrue(self.parent.has_css_class("failure"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.parent.has_css_class("failure"))
def test_bind_missing(self):
"""Test binding to a missing test."""
self.factory.emit("setup", self.listitem)
self.factory.emit("bind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertIsNone(self.listitem.get_child().get_text())
self.factory.emit("unbind", self.listitem)
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())
class TestSummaryFactory(unittest.TestCase):
"""Tests our Gtk.Factory to show Xfstests results summaries."""
@ -180,15 +246,9 @@ class TestSummaryFactory(unittest.TestCase):
def test_init(self):
"""Test that the factory was initialized correctly."""
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.Factory)
self.assertEqual(self.factory.xunit, "xunit-1")
def test_setup(self):
"""Test that the factory implements the 'setup' signal."""
self.factory.emit("setup", self.listitem)
self.assertIsInstance(self.listitem.get_child(), Gtk.Label)
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
def test_bind_passed(self):
"""Test binding to the passed tests summary."""
self.summary.add_xunit("xunit-1", 1, "testcase")
@ -198,7 +258,6 @@ class TestSummaryFactory(unittest.TestCase):
self.assertTrue(self.listitem.get_child().has_css_class("success"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.listitem.get_child().has_css_class("success"))
def test_bind_failed(self):
@ -211,7 +270,6 @@ class TestSummaryFactory(unittest.TestCase):
self.assertTrue(self.listitem.get_child().has_css_class("error"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.listitem.get_child().has_css_class("error"))
def test_bind_skipped(self):
@ -224,7 +282,6 @@ class TestSummaryFactory(unittest.TestCase):
self.assertTrue(self.listitem.get_child().has_css_class("warning"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.listitem.get_child().has_css_class("warning"))
def test_bind_time(self):
@ -237,11 +294,4 @@ class TestSummaryFactory(unittest.TestCase):
self.assertTrue(self.listitem.get_child().has_css_class("accent"))
self.factory.emit("unbind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "")
self.assertFalse(self.listitem.get_child().has_css_class("accent"))
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())

View File

@ -6,6 +6,86 @@ import xfstestsdb.gtk.view
from gi.repository import Gtk
class TestPropertyView(unittest.TestCase):
"""Tests the PropertyView."""
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(["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)])
self.model = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 1)
self.view = xfstestsdb.gtk.view.PropertyView()
def test_init(self):
"""Test that we created the ProeprtyView correctly."""
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
self.assertEqual(self.view.props.vscrollbar_policy,
Gtk.PolicyType.NEVER)
self.assertTrue(self.view.has_css_class("card"))
self.assertIsNone(self.view.model)
def test_columnview(self):
"""Test that we set up the Columnview correctly."""
self.assertIsInstance(self.view.props.child, Gtk.ColumnView)
self.assertIsInstance(self.view.props.child.get_model(),
Gtk.NoSelection)
self.assertEqual(len(self.view.props.child.get_columns()), 0)
self.assertTrue(self.view.props.child.get_show_column_separators())
self.assertTrue(self.view.props.child.get_show_row_separators())
self.assertTrue(self.view.props.child.get_hexpand())
self.assertTrue(self.view.props.child.has_css_class("data-table"))
def test_filter(self):
"""Test that we set up the Gtk.FilterModel correctly."""
self.assertIsInstance(self.view._filtermodel, Gtk.FilterListModel)
self.assertIsInstance(self.view._propfilter,
xfstestsdb.gtk.model.PropertyFilter)
self.assertEqual(self.view.props.child.get_model().get_model(),
self.view._filtermodel)
self.assertEqual(self.view._filtermodel.props.filter,
self.view._propfilter)
def test_property_column(self):
"""Test that we set up the 'property' column correctly."""
self.assertIsInstance(self.view._property, Gtk.ColumnViewColumn)
self.assertIsInstance(self.view._property.props.factory,
xfstestsdb.gtk.row.LabelFactory)
self.assertEqual(self.view._property.props.factory.property, "name")
self.assertEqual(self.view._property.props.title, "property")
self.assertFalse(self.view._property.props.expand)
def test_model(self):
"""Test setting the model property."""
self.view.model = self.model
self.assertEqual(self.view._filtermodel.props.model, self.model)
columns = self.view.props.child.get_columns()
self.assertEqual(len(columns), 3)
self.assertEqual(columns[0], self.view._property)
for i, title in enumerate(["xunit-1", "xunit-2"], start=1):
with self.subTest(i=i, title=title):
self.assertIsInstance(columns[i].props.factory,
xfstestsdb.gtk.row.PropertyFactory)
self.assertEqual(columns[i].props.factory.xunit, title)
self.assertEqual(columns[i].props.title, title)
self.assertTrue(columns[i].props.expand)
self.view.model = None
self.assertEqual(len(self.view.props.child.get_columns()), 0)
self.view.model = self.model
self.assertEqual(self.view.props.child.get_columns()[0],
self.view._property)
class TestFilterButtons(unittest.TestCase):
"""Test case for our TestCaseView FilterButtons."""
@ -237,6 +317,7 @@ class TestXfstestsView(unittest.TestCase):
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-2",
"1", str(tests.xunit.XUNIT_1)])
self.props = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 1)
self.model = xfstestsdb.gtk.model.TestCaseList(self.xfstestsdb.sql, 1)
self.summary = xfstestsdb.gtk.model.SummaryList(self.xfstestsdb.sql, 1)
self.view = xfstestsdb.gtk.view.XfstestsView()
@ -246,11 +327,19 @@ class TestXfstestsView(unittest.TestCase):
self.assertIsInstance(self.view, Gtk.Box)
self.assertEqual(self.view.props.orientation, Gtk.Orientation.VERTICAL)
def test_property_view(self):
"""Check that the XfstestsView sets up a PropertyView correctly."""
self.assertIsInstance(self.view._propertyview,
xfstestsdb.gtk.view.PropertyView)
self.assertEqual(self.view.get_first_child(), self.view._propertyview)
def test_testcase_view(self):
"""Check that the XfstestsView sets up a TestCaseView correctly."""
sep = self.view._propertyview.get_next_sibling()
self.assertIsInstance(sep, Gtk.Separator)
self.assertIsInstance(self.view._testcaseview,
xfstestsdb.gtk.view.TestCaseView)
self.assertEqual(self.view.get_first_child(), self.view._testcaseview)
self.assertEqual(sep.get_next_sibling(), self.view._testcaseview)
def test_summary_view(self):
"""Check that the XfstestsView sets up a SummaryView correctly."""
@ -260,6 +349,12 @@ class TestXfstestsView(unittest.TestCase):
xfstestsdb.gtk.view.SummaryView)
self.assertEqual(sep.get_next_sibling(), self.view._summaryview)
def test_properties(self):
"""Test the XfstestsView 'properties' property."""
self.assertIsNone(self.view.properties)
self.view.properties = self.props
self.assertEqual(self.view._propertyview.model, self.props)
def test_model(self):
"""Test the XfstestsView 'model' property."""
self.assertIsNone(self.view.model)

View File

@ -16,8 +16,8 @@ class TestWindow(unittest.TestCase):
def test_init(self):
"""Check that the Window is set up correctly."""
self.assertIsInstance(self.window, Adw.Window)
self.assertEqual(self.window.props.default_height, 800)
self.assertEqual(self.window.props.default_width, 1200)
self.assertEqual(self.window.props.default_height, 1000)
self.assertEqual(self.window.props.default_width, 1600)
self.assertTrue(self.window.has_css_class("devel"))
self.assertIsInstance(self.window.props.content, Gtk.Box)

View File

@ -27,6 +27,7 @@ class TestApplication(unittest.TestCase):
self.assertEqual(self.application.get_resource_base_path(),
xfstestsdb.gtk.gsetup.RESOURCE_PATH)
self.assertEqual(self.application.runid, 0)
self.assertIsNone(self.application.properties)
self.assertIsNone(self.application.model)
self.assertIsNone(self.application.summary)
@ -43,6 +44,7 @@ class TestApplication(unittest.TestCase):
mock_cmd.get_arguments.assert_called()
mock_activate.assert_called()
self.assertEqual(self.application.runid, 0)
self.assertIsNone(self.application.properties)
self.assertIsNone(self.application.model)
self.assertIsNone(self.application.summary)
@ -56,6 +58,8 @@ class TestApplication(unittest.TestCase):
mock_cmd.get_arguments.assert_called()
mock_activate.assert_called()
self.assertEqual(self.application.runid, 42)
self.assertIsInstance(self.application.properties,
xfstestsdb.gtk.model.PropertyList)
self.assertIsInstance(self.application.model,
xfstestsdb.gtk.model.TestCaseList)
self.assertIsInstance(self.application.summary,
@ -86,6 +90,10 @@ class TestApplication(unittest.TestCase):
self.application.runid = 42
self.assertEqual(self.application.win.runid, 42)
properties = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 42)
self.application.properties = properties
self.assertEqual(self.application.view.properties, properties)
model = xfstestsdb.gtk.model.TestCaseList(self.xfstestsdb.sql, 42)
self.application.model = model
self.assertEqual(self.application.view.model, model)

View File

@ -52,7 +52,7 @@ class TestXfstestsdb(unittest.TestCase):
def test_version(self, mock_stdout: io.StringIO):
"""Test printing version information."""
self.assertEqual(xfstestsdb.MAJOR, 1)
self.assertEqual(xfstestsdb.MINOR, 3)
self.assertEqual(xfstestsdb.MINOR, 4)
self.xfstestsdb.run(["--version"])
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.3-debug\n")
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.4-debug\n")

View File

@ -3,3 +3,4 @@
import pathlib
XUNIT_1 = pathlib.Path(__file__).parent / "test-1.xunit"
XUNIT_2 = pathlib.Path(__file__).parent / "test-2.xunit"

68
tests/xunit/test-2.xunit Normal file
View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite
xmlns="https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/tree/doc/xunit.xsd"
name="xfstests"
failures="1" skipped="3" tests="10" time="43"
hostname="myhost"
start_timestamp="2023-01-31T14:14:14-05:00"
timestamp="2023-01-31T14:14:55-05:00"
report_timestamp="2023-01-31T14:14:57-05:00"
>
<properties>
<property name="SECTION" value="-no-sections-"/>
<property name="FSTYP" value="myfs"/>
<property name="PLATFORM" value="Linux/x86_64 myhost 6.1.8-arch1"/>
<property name="MOUNT_OPTIONS" value="-o mountopt1,mountopt3"/>
<property name="HOST_OPTIONS" value="local.config"/>
<property name="CHECK_OPTIONS" value="-r -R xunit -g quick"/>
<property name="TIME_FACTOR" value="1"/>
<property name="LOAD_FACTOR" value="1"/>
<property name="TEST_DIR" value="/mnt/test2"/>
<property name="TEST_DEV" value="/dev/vdb3"/>
<property name="SCRATCH_DEV" value="/dev/vdb4"/>
<property name="SCRATCH_MNT" value="/mnt/scratch2"/>
<property name="OVL_UPPER" value="ovl-upper"/>
<property name="OVL_LOWER" value="ovl-lower"/>
<property name="OVL_WORK" value="ovl-work"/>
</properties>
<testcase classname="xfstests.global" name="test/01" time="1">
</testcase>
<testcase classname="xfstests.global" name="test/02" time="0">
<skipped message="skipped on /dev/vdb1" />
</testcase>
<testcase classname="xfstests.global" name="test/03" time="0">
<skipped message="skipped on /mnt/test too" />
</testcase>
<testcase classname="xfstests.global" name="test/04" time="4">
</testcase>
<testcase classname="xfstests.global" name="test/05" time="5">
</testcase>
<testcase classname="xfstests.global" name="test/06" time="6">
</testcase>
<testcase classname="xfstests.global" name="test/07" time="0">
<skipped message="fstype &quot;myfs&quot; gets skipped" />
</testcase>
<testcase classname="xfstests.global" name="test/08" time="8">
</testcase>
<testcase classname="xfstests.global" name="test/09" time="9">
<failure message="- output mismatch (see somefile)" type="TestFail" />
<system-out>
<![CDATA[
there was a problem with &apos;/dev/vdb2&apos;
]]>
</system-out>
<system-err>
<![CDATA[
--- test/09.out 2023-01-31 14:14:14.141414 -1414
+++ results/some/sub/dir/test/09.out.bad 2023-02-02 16:16:16.161616 -1616
there was a problem with &apos;/mnt/scratch&apos;
]]>
</system-err>
</testcase>
<testcase classname="xfstests.global" name="test/10" time="10">
</testcase>
</testsuite>

View File

@ -15,7 +15,7 @@ from . import untag
from . import xunit
MAJOR = 1
MINOR = 3
MINOR = 4
class Command:

View File

@ -18,6 +18,7 @@ class Application(Adw.Application):
"""Our Adw.Application for displaying xfstests results."""
runid = GObject.Property(type=int)
properties = GObject.Property(type=model.PropertyList)
summary = GObject.Property(type=model.SummaryList)
model = GObject.Property(type=model.TestCaseList)
win = GObject.Property(type=window.Window)
@ -40,6 +41,7 @@ class Application(Adw.Application):
match split[0]:
case "runid":
self.runid = int(split[1])
self.properties = model.PropertyList(self.sql, self.runid)
self.model = model.TestCaseList(self.sql, self.runid)
self.summary = model.SummaryList(self.sql, self.runid)
@ -56,6 +58,7 @@ class Application(Adw.Application):
self.win.headerbar.pack_end(self.view.filterbuttons)
self.bind_property("runid", self.win, "runid")
self.bind_property("properties", self.view, "properties")
self.bind_property("model", self.view, "model")
self.bind_property("summary", self.view, "summary")
self.add_window(self.win)

View File

@ -1,5 +1,6 @@
# Copyright 2023 (c) Anna Schumaker.
"""Our Testcase Gio.ListModel."""
import sqlite3
import typing
from gi.repository import GObject
from gi.repository import Gio
@ -7,91 +8,184 @@ from gi.repository import Gtk
from .. import sqlite
class XunitResult(GObject.GObject):
"""The results for a single TestCase with a specific Xunit."""
class XunitCell(GObject.GObject):
"""Holds a single value for a single Xunit."""
name = GObject.Property(type=str)
def __str__(self) -> str:
"""Get a string representation of this XunitCell."""
return self.name
class XunitRow(GObject.GObject):
"""Collects results for a single row across multiple Xunits."""
name = GObject.Property(type=str)
def __init__(self, name: str) -> None:
"""Initialize an XunitRow."""
super().__init__(name=name)
self.__xunits = {}
def __getitem__(self, xunit: str) -> XunitCell | None:
"""Get the value of a specific Xunit."""
return self.__xunits.get(xunit)
def __lt__(self, rhs: typing.Self) -> bool:
"""Compare the names of two XunitRows."""
return self.name < rhs.name
def add_xunit(self, name: str, *args, **kwargs) -> None:
"""Add an XunitCell to the XunitRow."""
self.__xunits[name] = self.do_make_xunit(name, *args, **kwargs)
def do_make_xunit(self, name: str) -> XunitCell:
"""Create and return a new XunitCell."""
return XunitCell(name=name)
def get_results(self) -> set[str]:
"""Get a set of results for each added xunit."""
return {str(xunit) for xunit in self.__xunits.values()}
class XunitList(GObject.GObject, Gio.ListModel):
"""A list of XunitRows for a specific Xfstests Run."""
runid = GObject.Property(type=int)
n_items = GObject.Property(type=int)
def __init__(self, sql: sqlite.Connection, runid: int) -> None:
"""Initialize an XunitList."""
super().__init__(runid=runid)
self.__xunits = set()
rows = {}
for row in self.do_query(sql).fetchall():
self.do_parse(rows, row)
self.__xunits.add(row["xunit"])
self.__items = sorted(rows.values())
self.n_items = len(self.__items)
def do_get_item_type(self) -> GObject.GType:
"""Get the type of the objects in the list."""
return XunitRow.__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) -> XunitRow | None:
"""Get a specific item on the list."""
return self.__items[n] if n < self.n_items else None
def do_parse(self, rows: dict[XunitRow], row: sqlite3.Row) -> None:
"""Parse a sqlite3.Row and add it to the rows dict."""
def do_query(self, sql: sqlite.Connection) -> sqlite3.Cursor:
"""Query the database."""
return sql("SELECT name AS xunit FROM xunits WHERE runid=?",
self.runid)
def get_xunits(self) -> list[str]:
"""Get a list of xunits attached to this xfstests run."""
return sorted(self.__xunits)
class PropertyValue(XunitCell):
"""A single Property for a specific Xunit."""
key = GObject.Property(type=str)
value = GObject.Property(type=str)
def __str__(self) -> str:
"""Get a string representation of this Property."""
return f"{self.key} = {self.value}"
class Property(XunitRow):
"""Collects one property across multiple xunits."""
def do_make_xunit(self, name: str, key: str, value: str) -> PropertyValue:
"""Add a PropertyValue to the Property."""
return PropertyValue(name=name, key=key, value=value)
def all_same_value(self) -> bool:
"""Check if all the xunits have the same value."""
return len(self.get_results()) == 1
class PropertyList(XunitList):
"""A list of Properties for a specific Xfstests Run."""
def do_query(self, sql: sqlite.Connection) -> sqlite3.Cursor:
"""Query the database for properties."""
return sql("""SELECT xunit, key, value FROM xunit_properties_view
WHERE runid=?""", self.runid)
def do_parse(self, rows: dict[Property], row: sqlite3.Cursor) -> None:
"""Parse the data in the row and add it to the rows dict."""
property = rows.setdefault(row["key"], Property(row["key"]))
property.add_xunit(row["xunit"], row["key"], row["value"])
class PropertyFilter(Gtk.Filter):
"""A filter for Properties."""
def do_get_strictness(self) -> Gtk.FilterMatch:
"""Get the strictness of the filter."""
return Gtk.FilterMatch.SOME
def do_match(self, property: Property) -> bool:
"""Check if a property matches the filter."""
hidden = {"CPUS", "HOST_OPTIONS", "LOAD_FACTOR", "MEM_KB",
"NUMA_NODES", "OVL_LOWER", "OVL_UPPER", "OVL_WORK",
"PLATFORM", "SECTION", "SWAP_KB", "TIME_FACTOR"}
return property.name not in hidden
class TestResult(XunitCell):
"""The results for a single TestCase with a specific Xunit."""
status = GObject.Property(type=str)
time = GObject.Property(type=int)
message = GObject.Property(type=str)
stdout = GObject.Property(type=str)
stderr = GObject.Property(type=str)
def __str__(self) -> str:
"""Get a string representation of this TestResult."""
return self.status
class TestCase(GObject.GObject):
class TestCase(XunitRow):
"""Collects results for a single TestCase with multiple Xunits."""
name = GObject.Property(type=str)
def __init__(self, name: str) -> None:
"""Initialize a TestCase object."""
super().__init__(name=name)
self.__xunits = {}
def __getitem__(self, xunit: str) -> XunitResult | None:
"""Get the results for a specific Xunit."""
return self.__xunits.get(xunit)
def __lt__(self, rhs: typing.Self) -> bool:
"""Compare the names of two TestCases."""
return self.name < rhs.name
def add_xunit(self, name: str, status: str, time: int,
message: str | None, stdout: str | None,
stderr: str | None) -> None:
def do_make_xunit(self, name: str, status: str, time: int,
message: str | None, stdout: str | None,
stderr: str | None) -> TestResult:
"""Add an xunit result to the TestCase."""
message = "" if message is None else message
stdout = "" if stdout is None else stdout
stderr = "" if stderr is None else stderr
self.__xunits[name] = XunitResult(name=name, status=status, time=time,
message=message, stdout=stdout,
stderr=stderr)
def get_results(self) -> set[str]:
"""Get a set of results for each added xunit."""
return {xunit.status for xunit in self.__xunits.values()}
return TestResult(name=name, status=status, time=time,
message=("" if message is None else message),
stdout=("" if stdout is None else stdout),
stderr=("" if stderr is None else stderr))
class TestCaseList(GObject.GObject, Gio.ListModel):
class TestCaseList(XunitList):
"""A list of TestCases for a specific Xfstests Run."""
runid = GObject.Property(type=int)
n_items = GObject.Property(type=int)
def do_query(self, sql: sqlite.Connection) -> sqlite3.Cursor:
"""Query the database for testcase results."""
return sql("""SELECT testcase, xunit, status, time,
message, stdout, stderr
FROM testcases_view WHERE runid=?""", self.runid)
def __init__(self, sql: sqlite.Connection, runid: int) -> None:
"""Initialize a TestCaseList."""
super().__init__(runid=runid)
self.__xunits = set()
cases = {}
cur = sql("""SELECT testcase, xunit, status,
time, message, stdout, stderr
FROM testcases_view WHERE runid=?""", runid)
for row in cur.fetchall():
testcase = cases.setdefault(row["testcase"],
TestCase(row["testcase"]))
testcase.add_xunit(row["xunit"], row["status"], row["time"],
row["message"], row["stdout"], row["stderr"])
self.__xunits.add(row["xunit"])
self.__items = sorted(cases.values())
self.n_items = len(self.__items)
def do_get_item_type(self) -> GObject.GType:
"""Get the type of the objects in the list."""
return TestCase.__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) -> TestCase | None:
"""Get a specific item on the list."""
return self.__items[n] if n < self.n_items else None
def get_xunits(self) -> list[str]:
"""Get a list of xunits attached to this xfstests run."""
return sorted(self.__xunits)
def do_parse(self, rows: dict[TestCase], row: sqlite3.Cursor) -> None:
"""Parse the data in the row and add it to the rows dict."""
testcase = rows.setdefault(row["testcase"], TestCase(row["testcase"]))
testcase.add_xunit(row["xunit"], row["status"], row["time"],
row["message"], row["stdout"], row["stderr"])
class TestCaseFilter(Gtk.Filter):
@ -134,7 +228,7 @@ class TestCaseFilter(Gtk.Filter):
return False
class SummaryValue(GObject.GObject):
class SummaryValue(XunitCell):
"""The summary of a single Xfstests xunit field."""
name = GObject.Property(type=str)
@ -147,70 +241,30 @@ class SummaryValue(GObject.GObject):
return f"{self.value} {self.unit}{s}"
class Summary(GObject.GObject):
class Summary(XunitRow):
"""Collects values for each summary field with multiple Xunits."""
name = GObject.Property(type=str)
def __init__(self, name: str) -> None:
"""Initialize a Summary object."""
super().__init__(name=name)
self.__xunits = {}
def __getitem__(self, xunit: str) -> SummaryValue | None:
"""Get the summary for a specific Xunit."""
return self.__xunits.get(xunit)
def __lt__(self, rhs: typing.Self) -> bool:
"""Compare the fields of two Summaries."""
order = ["passed", "failed", "skipped", "time"]
return order.index(self.name) < order.index(rhs.name)
def add_xunit(self, name: str, value: int, unit: str) -> None:
def do_make_xunit(self, name: str, value: int, unit: str) -> SummaryValue:
"""Add an xunit summary to the Summary."""
self.__xunits[name] = SummaryValue(name=name, value=value, unit=unit)
def get_results(self) -> set[str]:
"""Get a set of results for each added xunit."""
return {str(value) for value in self.__xunits.values()}
return SummaryValue(name=name, value=value, unit=unit)
class SummaryList(GObject.GObject, Gio.ListModel):
class SummaryList(XunitList):
"""A list summarizing the results of a specific Xfstests Run."""
runid = GObject.Property(type=int)
n_items = GObject.Property(type=int)
def do_query(self, sql: sqlite.Connection) -> sqlite3.Cursor:
"""Query the database for xunit summaries."""
return sql("""SELECT name AS xunit, passed, failed, skipped, time
FROM xunits_view WHERE runid=?""", self.runid)
def __init__(self, sql: sqlite.Connection, runid: int) -> None:
"""Initialize a SummaryList."""
super().__init__(runid=runid)
self.__xunits = set()
results = {}
cur = sql("""SELECT name AS xunit, passed, failed, skipped, time
FROM xunits_view WHERE runid=?""", runid)
for row in cur.fetchall():
for field in ["passed", "failed", "skipped", "time"]:
summary = results.setdefault(field, Summary(field))
summary.add_xunit(row["xunit"], row[field],
"second" if field == "time" else "testcase")
self.__xunits.add(row["xunit"])
self.__items = sorted(results.values())
self.n_items = len(self.__items)
def do_get_item_type(self) -> GObject.GType:
"""Get the type of the objects in the list."""
return Summary.__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) -> Summary | None:
"""Get a specific item on the list."""
return self.__items[n] if n < self.n_items else None
def get_xunits(self) -> list[str]:
"""Get a list of xunits attached to this xfstests run."""
return sorted(self.__xunits)
def do_parse(self, rows: dict[Summary], row: sqlite3.Row) -> None:
"""Parse the data in the row and add it to the rows dict."""
for field in ["passed", "failed", "skipped", "time"]:
summary = rows.setdefault(field, Summary(field))
summary.add_xunit(row["xunit"], row[field],
"second" if field == "time" else "testcase")

View File

@ -3,13 +3,59 @@
import typing
from gi.repository import GObject
from gi.repository import Gtk
from . import model
STYLES = {"passed": "success", "failed": "error",
"skipped": "warning", "time": "accent"}
class LabelFactory(Gtk.SignalListItemFactory):
class Factory(Gtk.SignalListItemFactory):
"""Create Gtk.Inscriptions for each Gtk.ListItem."""
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.Inscription(xalign=0.5, nat_chars=18)
child.props.text_overflow = Gtk.InscriptionOverflow.ELLIPSIZE_END
child.add_css_class("numeric")
self.do_setup(child)
listitem.set_child(child)
def __bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
"""Bind a ListItem to the child widget."""
self.do_bind(listitem.get_item(), listitem.get_child())
def __unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
"""Unbind a ListItem from the child widget."""
self.do_unbind(listitem.get_item(), listitem.get_child())
listitem.get_child().set_text(None)
def __teardown(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
self.do_teardown(listitem.get_child())
listitem.set_child(None)
def do_setup(self, child: Gtk.Inscription) -> None:
"""Extra factory-specific setup for the child widget."""
def do_bind(self, row: model.XunitRow, child: Gtk.Inscription) -> None:
"""Extra factory-specific binding work for the child widget."""
def do_unbind(self, row: model.XunitRow, child: Gtk.Inscription) -> None:
"""Extra factory-specific unbinding work for the child widget."""
def do_teardown(self, child: Gtk.Inscription) -> None:
"""Extra factory-specific teardown for the child widget."""
class LabelFactory(Factory):
"""Create Gtk.Labels for each testcase."""
property = GObject.Property(type=str)
@ -18,42 +64,46 @@ class LabelFactory(Gtk.SignalListItemFactory):
def __init__(self, property: str):
"""Initialize our InscriptionFactory."""
super().__init__(property=property)
self.connect("setup", self.do_setup)
self.connect("bind", self.do_bind)
self.connect("unbind", self.do_unbind)
self.connect("teardown", self.do_teardown)
def do_setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_setup(self, child: Gtk.Inscription) -> None:
"""Set up a ListItem child widget."""
child = Gtk.Label()
child.add_css_class("numeric")
listitem.set_child(child)
LabelFactory.group.add_widget(child)
def do_bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_bind(self, row: model.XunitRow, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
text = listitem.get_item().get_property(self.property)
child = listitem.get_child()
text = row.get_property(self.property)
if style := STYLES.get(text):
child.add_css_class(style)
child.set_text(text)
def do_unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_unbind(self, row: model.XunitRow, child: Gtk.Inscription) -> None:
"""Unbind a ListItem from the child widget."""
child = listitem.get_child()
for style in STYLES.values():
child.remove_css_class(style)
child.set_text("")
def do_teardown(self, factory: typing.Self,
listitem: Gtk.ListItem) -> None:
def do_teardown(self, child: Gtk.Inscription) -> None:
"""Clean up a ListItem child widget."""
if (child := listitem.get_child()) is not None:
if child is not None:
LabelFactory.group.remove_widget(child)
listitem.set_child(None)
class ResultFactory(Gtk.SignalListItemFactory):
class PropertyFactory(Factory):
"""Factory for making property widgets."""
xunit = GObject.Property(type=str)
def __init__(self, xunit: str):
"""Initialize our InscriptionFactory."""
super().__init__(xunit=xunit)
def do_bind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
property = row[self.xunit]
child.set_text(property.value)
child.set_tooltip_text(property.value)
class ResultFactory(Factory):
"""Factory for making test result widgets."""
xunit = GObject.Property(type=str)
@ -61,43 +111,26 @@ class ResultFactory(Gtk.SignalListItemFactory):
def __init__(self, xunit: str):
"""Initialize our ResultFactory."""
super().__init__(xunit=xunit)
self.connect("setup", self.do_setup)
self.connect("bind", self.do_bind)
self.connect("unbind", self.do_unbind)
self.connect("teardown", self.do_teardown)
def do_setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
"""Set up a ListItem child widget."""
listitem.set_child(Gtk.Label())
listitem.get_child().add_css_class("numeric")
def do_bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_bind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
if (result := listitem.get_item()[self.xunit]) is None:
if (result := row[self.xunit]) is None:
return
if (text := result.status) == "passed":
text = f"{result.time} seconds"
child = listitem.get_child()
child.set_text(text)
child.set_tooltip_text(result.message.lstrip(" -"))
child.get_parent().add_css_class(result.status)
def do_unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_unbind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Unbind a ListItem from the child widget."""
if (result := listitem.get_item()[self.xunit]) is not None:
child = listitem.get_child()
child.set_text("")
if (result := row[self.xunit]) is not None:
child.get_parent().remove_css_class(result.status)
def do_teardown(self, factory: typing.Self,
listitem: Gtk.ListItem) -> None:
"""Clean up a ListItem child widget."""
listitem.set_child(None)
class SummaryFactory(Gtk.SignalListItemFactory):
class SummaryFactory(Factory):
"""Factory for making test summary widgets."""
xunit = GObject.Property(type=str)
@ -105,31 +138,13 @@ class SummaryFactory(Gtk.SignalListItemFactory):
def __init__(self, xunit: str):
"""Initialize our ResultFactory."""
super().__init__(xunit=xunit)
self.connect("setup", self.do_setup)
self.connect("bind", self.do_bind)
self.connect("unbind", self.do_unbind)
self.connect("teardown", self.do_teardown)
def do_setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
"""Set up a ListItem child widget."""
listitem.set_child(Gtk.Label())
listitem.get_child().add_css_class("numeric")
def do_bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_bind(self, row: model.Summary, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
summary = listitem.get_item()
result = summary[self.xunit]
child = listitem.get_child()
result = row[self.xunit]
child.set_text(str(result))
child.add_css_class(STYLES[summary.name])
child.add_css_class(STYLES[row.name])
def do_unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
def do_unbind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Unbind a ListItem from the child widget."""
child = listitem.get_child()
child.set_text("")
child.remove_css_class(STYLES[listitem.get_item().name])
def do_teardown(self, factory: typing.Self,
listitem: Gtk.ListItem) -> None:
"""Clean up a ListItem child widget."""
listitem.set_child(None)
child.remove_css_class(STYLES[row.name])

View File

@ -2,6 +2,8 @@
"""A view widget used to display our TestCaseModel."""
from gi.repository import GObject
from gi.repository import Gtk
from .model import PropertyList
from .model import PropertyFilter
from .model import TestCaseList
from .model import TestCaseFilter
from .model import SummaryList
@ -9,6 +11,48 @@ from . import button
from . import row
class PropertyView(Gtk.ScrolledWindow):
"""Displays our PropertyList model to the user."""
def __init__(self):
"""Initialize a PropertyView."""
super().__init__(child=Gtk.ColumnView(model=Gtk.NoSelection(),
show_row_separators=True,
show_column_separators=True,
hexpand=True),
vscrollbar_policy=Gtk.PolicyType.NEVER)
self._property = Gtk.ColumnViewColumn(title="property",
factory=row.LabelFactory("name"))
self._propfilter = PropertyFilter()
self._filtermodel = Gtk.FilterListModel(filter=self._propfilter)
self.props.child.get_model().set_model(self._filtermodel)
self.props.child.add_css_class("data-table")
self.add_css_class("card")
def __property_column(self, xunit: str) -> None:
return Gtk.ColumnViewColumn(title=xunit, expand=True,
factory=row.PropertyFactory(xunit))
@GObject.Property(type=PropertyList)
def model(self) -> PropertyList:
"""Get the PropertyList shown by the View."""
return self._filtermodel.props.model
@model.setter
def model(self, new: PropertyList) -> None:
for col in [col for col in self.props.child.get_columns()]:
self.props.child.remove_column(col)
self._filtermodel.props.model = new
if new is not None:
self.props.child.append_column(self._property)
for xunit in new.get_xunits():
self.props.child.append_column(self.__property_column(xunit))
class FilterButtons(Gtk.Box):
"""Buttons for controlling the TestCaseFilter."""
@ -124,18 +168,23 @@ class SummaryView(Gtk.ScrolledWindow):
class XfstestsView(Gtk.Box):
"""A widget to display the results of an Xfstests runs."""
properties = GObject.Property(type=PropertyList)
model = GObject.Property(type=TestCaseList)
summary = GObject.Property(type=SummaryList)
def __init__(self):
"""Initialize an XfstestsView."""
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._propertyview = PropertyView()
self._testcaseview = TestCaseView()
self._summaryview = SummaryView()
self.bind_property("properties", self._propertyview, "model")
self.bind_property("model", self._testcaseview, "model")
self.bind_property("summary", self._summaryview, "model")
self.append(self._propertyview)
self.append(Gtk.Separator())
self.append(self._testcaseview)
self.append(Gtk.Separator())
self.append(self._summaryview)

View File

@ -16,7 +16,7 @@ class Window(Adw.Window):
def __init__(self, **kwargs):
"""Set up our Window."""
super().__init__(default_height=800, default_width=1200,
super().__init__(default_height=1000, default_width=1600,
content=Gtk.Box(orientation=Gtk.Orientation.VERTICAL),
title=Adw.WindowTitle(title="xfstestsdb gtk"),
headerbar=Adw.HeaderBar(), **kwargs)