Compare commits

...

12 Commits

Author SHA1 Message Date
Anna Schumaker 528444cab6 xfstestsdb 1.5
Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-31 15:52:28 -04:00
Anna Schumaker 952889687f gtk: Add a MessagesView to the XfstestsView
This is placed in a Gtk.Stack with a nice transition to show the failed
tests log.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-31 15:35:28 -04:00
Anna Schumaker ea2913429c gtk: Create a MessagesView
The MessagesView combines two MessageViews into a split-pane card. This
lets us display stdout and stderr side-by-side to the user so they can
see what is going on. I also add a 'back' button that the user can click
to signal that they are done reviewing the output.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-31 15:34:18 -04:00
Anna Schumaker 5fb9bd6221 gtk: Create a MessageView
The message view will be used to display either stdout or stderr
messages to the user. It has built-in 'diff' detection, and adds nice
colors to the diff output if we are asked to display one.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-31 15:31:09 -04:00
Anna Schumaker 54be8f5ce3 gtk: Give the TestCaseView a show-messages signal
This simply passes on the signal from the underlying factory to be used
in a higher up layer.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-31 13:16:17 -04:00
Anna Schumaker 5a25935fed gtk: Emit a signal when a specific test result is clicked
I only do this for failing tests that have text set in their stdout or
stderr properties. I also update the CSS for failed tests to give an
indication when users hover their mouse over them.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-31 13:16:16 -04:00
Anna Schumaker 40b1d1789d gtk: Add the EnvironmentView to the XfstestsView
And set the environment property from the application after creating a
property list.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 17:17:34 -04:00
Anna Schumaker f44e064c89 gtk: Create an EnvironmentView
The EnvironmentView is a ColumnView configured to display environment
properties to the user. It doesn't use the firstcol property at all, so
some extra handling of the model property was needed.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 17:16:39 -04:00
Anna Schumaker 02452069da gtk: Create an EnvironmentFactory
This is different from our other xunit factories. Instead of displaying
values for each xunit, it looks into what we determine to be environment
properties and displays those instead.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 17:15:17 -04:00
Anna Schumaker 7c7e279648 model: Give the PropertyList "environment" detection
I define the envorionment as properties that are the same across each
added xunit, as long as there are more than 1 added xunits. I add an
'environment' property to make it easy to get the current set of
environment properties.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 16:13:07 -04:00
Anna Schumaker c861c49564 gtk: Add an XunitView base class
The XunitView implements most of the work needed by our various views to
show xfstests results.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 15:34:27 -04:00
Anna Schumaker a5d1ae4607 gtk: Create an XunitFactory base class
And convert most of our Factory implementations to inherit from it. This
lets me set up the xunit property in one single place, and soon I'll be
using this factory for a XunitView base class.

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
2023-08-29 15:27:50 -04:00
11 changed files with 833 additions and 301 deletions

View File

@ -45,6 +45,7 @@ class TestXunitRow(unittest.TestCase):
self.row.add_xunit("xunit-1")
self.assertSetEqual(self.row.get_results(), {"xunit-1"})
self.assertListEqual(self.row.get_xunits(), ["xunit-1"])
xunit = self.row["xunit-1"]
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitCell)
@ -52,6 +53,7 @@ class TestXunitRow(unittest.TestCase):
self.row.add_xunit("xunit-2")
self.assertSetEqual(self.row.get_results(), {"xunit-1", "xunit-2"})
self.assertListEqual(self.row.get_xunits(), ["xunit-1", "xunit-2"])
xunit = self.row["xunit-2"]
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitCell)
@ -130,6 +132,8 @@ class TestProperty(unittest.TestCase):
def test_xunits(self):
"""Test adding xunits to a Property."""
self.assertIsNone(self.property["xunit-1"])
self.assertIsNone(self.property.get_value())
self.assertFalse(self.property.all_same_value())
self.property.add_xunit("xunit-1", "PLATFORM", "linux-123")
property = self.property["xunit-1"]
@ -137,7 +141,8 @@ class TestProperty(unittest.TestCase):
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.assertFalse(self.property.all_same_value())
self.assertIsNone(self.property.get_value())
self.property.add_xunit("xunit-2", "PLATFORM", "linux-123")
property = self.property["xunit-2"]
@ -146,9 +151,11 @@ class TestProperty(unittest.TestCase):
self.assertEqual(property.key, "PLATFORM")
self.assertEqual(property.value, "linux-123")
self.assertTrue(self.property.all_same_value())
self.assertEqual(self.property.get_value(), "linux-123")
self.property.add_xunit("xunit-3", "PLATFORM", "linux-456")
self.assertFalse(self.property.all_same_value())
self.assertIsNone(self.property.get_value())
class TestPropertyList(unittest.TestCase):
@ -172,6 +179,16 @@ class TestPropertyList(unittest.TestCase):
self.assertEqual(self.prlist.runid, 1)
self.assertEqual(self.prlist.n_items, 15)
def test_environment(self):
"""Test environment handling."""
self.assertIsInstance(self.prlist.environment, Gio.ListStore)
self.assertEqual(len(self.prlist.environment), 1)
self.assertEqual(self.prlist.environment[0], self.prlist)
env = self.prlist.get_environment()
self.assertDictEqual(env, {"FSTYP": "myfs",
"CHECK_OPTIONS": "-r -R xunit -g quick"})
class TestPropertyFilter(unittest.TestCase):
"""Tests our Gtk.Filter customized for filtering Properties."""
@ -185,14 +202,27 @@ class TestPropertyFilter(unittest.TestCase):
self.assertIsInstance(self.filter, Gtk.Filter)
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.SOME)
def test_hidden_properties(self):
"""Test the hidden properties global variable."""
self.assertSetEqual(xfstestsdb.gtk.model.HIDDEN_PROPERTIES,
{"CPUS", "HOST_OPTIONS", "LOAD_FACTOR",
"MEM_KB", "NUMA_NODES", "OVL_LOWER",
"OVL_UPPER", "OVL_WORK", "PLATFORM",
"SECTION", "SWAP_KB", "TIME_FACTOR"})
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"]:
property.add_xunit("xunit-1", "name", "my name")
self.assertTrue(self.filter.match(property))
property.add_xunit("xunit-2", "name", "my name")
self.assertFalse(self.filter.match(property))
property.add_xunit("xunit-3", "name", "my other name")
self.assertTrue(self.filter.match(property))
for prop in xfstestsdb.gtk.model.HIDDEN_PROPERTIES:
with self.subTest(property=prop):
property.name = prop
self.assertFalse(self.filter.match(property))

View File

@ -2,6 +2,7 @@
"""Tests our row widgets and factories."""
import unittest
import xfstestsdb.gtk.row
import tests.xunit
from gi.repository import Gtk
from gi.repository import Adw
@ -136,6 +137,52 @@ class TestLabelFactory(unittest.TestCase):
self.assertFalse(child.has_css_class(expected))
class TestEnvironmentFactory(unittest.TestCase):
"""Tests our Gtk.Factory to show environment properties."""
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.props = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 1)
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.props)
self.factory = xfstestsdb.gtk.row.EnvironmentFactory(property="FSTYP")
def test_init(self):
"""Test that the factory was initialized correctly."""
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.Factory)
self.assertEqual(self.factory.property, "FSTYP")
def test_bind(self):
"""Test binding to a property."""
self.factory.emit("setup", self.listitem)
self.factory.emit("bind", self.listitem)
self.assertEqual(self.listitem.get_child().get_xalign(), 0)
self.assertEqual(self.listitem.get_child().get_text(), "myfs")
self.assertEqual(self.listitem.get_child().get_tooltip_text(), "myfs")
class TestXunitFactory(unittest.TestCase):
"""Tests our XunitFactory base class."""
def setUp(self):
"""Set up common variables."""
self.factory = xfstestsdb.gtk.row.XunitFactory(xunit="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")
class TestPropertyFactory(unittest.TestCase):
"""Tests our Gtk.Factory to show xunit properties."""
@ -145,15 +192,14 @@ class TestPropertyFactory(unittest.TestCase):
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.property)
self.factory = xfstestsdb.gtk.row.PropertyFactory("xunit-1")
self.factory = xfstestsdb.gtk.row.PropertyFactory(xunit="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")
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.XunitFactory)
def test_bind_different(self):
"""Test binding to the a property when all values are different."""
def test_bind(self):
"""Test binding to a property."""
self.property.add_xunit("xunit-1", "property", "value")
self.factory.emit("setup", self.listitem)
self.factory.emit("bind", self.listitem)
@ -176,53 +222,88 @@ class TestResultFactory(unittest.TestCase):
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.testcase)
self.factory = xfstestsdb.gtk.row.ResultFactory("xunit-1")
self.factory = xfstestsdb.gtk.row.ResultFactory(xunit="xunit-1")
self.factory.connect("setup", self.setup_parent)
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")
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.XunitFactory)
def test_setup_click(self):
"""Test that we setup a GestureClick on the child widget."""
self.factory.emit("setup", self.listitem)
child = self.listitem.get_child()
click = getattr(child, "click")
self.assertIsInstance(click, Gtk.GestureClick)
self.assertIn(click, child.observe_controllers())
self.factory.emit("teardown", self.listitem)
self.assertIsNone(getattr(child, "click"))
self.assertNotIn(click, child.observe_controllers())
def test_bind_passed(self):
"""Test binding to a passing test."""
show_messages = unittest.mock.Mock()
self.testcase.add_xunit("xunit-1", "passed", 3, "", None, None)
self.factory.connect("show-messages", show_messages)
self.factory.emit("setup", self.listitem)
child = self.listitem.get_child()
self.factory.emit("bind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "3 seconds")
self.assertIsNone(self.listitem.get_child().get_tooltip_text())
self.assertEqual(child.get_text(), "3 seconds")
self.assertIsNone(child.get_tooltip_text())
self.assertTrue(self.parent.has_css_class("passed"))
child.click.emit("released", 1, 0, 0)
show_messages.assert_not_called()
self.factory.emit("unbind", self.listitem)
self.assertFalse(self.parent.has_css_class("passed"))
def test_bind_skipped(self):
"""Test binding to a skipped test."""
show_messages = unittest.mock.Mock()
self.testcase.add_xunit("xunit-1", "skipped", 0,
"test skipped for ... reasons", None, None)
self.factory.connect("show-messages", show_messages)
self.factory.emit("setup", self.listitem)
child = self.listitem.get_child()
self.factory.emit("bind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "skipped")
self.assertEqual(self.listitem.get_child().get_tooltip_text(),
self.assertEqual(child.get_text(), "skipped")
self.assertEqual(child.get_tooltip_text(),
"test skipped for ... reasons")
self.assertTrue(self.parent.has_css_class("skipped"))
child.click.emit("released", 1, 0, 0)
show_messages.assert_not_called()
self.factory.emit("unbind", self.listitem)
self.assertFalse(self.parent.has_css_class("skipped"))
def test_bind_failed(self):
"""Test binding to a failed test."""
show_messages = unittest.mock.Mock()
self.testcase.add_xunit("xunit-1", "failure", 8,
"- failed. see output", None, None)
"- failed. see output", "stdout message",
"stderr message")
self.factory.connect("show-messages", show_messages)
self.factory.emit("setup", self.listitem)
child = self.listitem.get_child()
self.factory.emit("bind", self.listitem)
self.assertEqual(self.listitem.get_child().get_text(), "failure")
self.assertEqual(self.listitem.get_child().get_tooltip_text(),
"failed. see output")
self.assertEqual(child.get_text(), "failure")
self.assertEqual(child.get_tooltip_text(), "failed. see output")
self.assertTrue(self.parent.has_css_class("failure"))
child.click.emit("released", 1, 0, 0)
show_messages.assert_called_with(self.factory, "test/case", "xunit-1",
"stdout message", "stderr message")
self.factory.emit("unbind", self.listitem)
self.assertFalse(self.parent.has_css_class("failure"))
child.click.emit("released", 1, 0, 0)
show_messages.assert_called_once()
def test_bind_missing(self):
"""Test binding to a missing test."""
@ -242,12 +323,11 @@ class TestSummaryFactory(unittest.TestCase):
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.summary)
self.factory = xfstestsdb.gtk.row.SummaryFactory("xunit-1")
self.factory = xfstestsdb.gtk.row.SummaryFactory(xunit="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")
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.XunitFactory)
def test_bind_passed(self):
"""Test binding to the passed tests summary."""

View File

@ -4,10 +4,11 @@ import unittest
import tests.xunit
import xfstestsdb.gtk.view
from gi.repository import Gtk
from gi.repository import Adw
class TestPropertyView(unittest.TestCase):
"""Tests the PropertyView."""
class TestXunitView(unittest.TestCase):
"""Tests the XunitView base class."""
def setUp(self):
"""Set up common variables."""
@ -19,19 +20,16 @@ class TestPropertyView(unittest.TestCase):
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()
self.model = xfstestsdb.gtk.model.XunitList(self.xfstestsdb.sql, 1)
self.view = xfstestsdb.gtk.view.XunitView("title")
def test_init(self):
"""Test that we created the ProeprtyView correctly."""
"""Test that we created the XunitView 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."""
"""Test that we set up the ColumnView child correctly."""
self.assertIsInstance(self.view.props.child, Gtk.ColumnView)
self.assertIsInstance(self.view.props.child.get_model(),
Gtk.NoSelection)
@ -39,51 +37,120 @@ class TestPropertyView(unittest.TestCase):
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):
def test_filtermodel(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.assertIsInstance(self.view.filtermodel, Gtk.FilterListModel)
self.assertIsNone(self.view.filtermodel.props.filter)
self.assertEqual(self.view.props.child.get_model().get_model(),
self.view._filtermodel)
self.assertEqual(self.view._filtermodel.props.filter,
self.view._propfilter)
self.view.filtermodel)
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,
def test_firstcol(self):
"""Test that we set up the first column correctly."""
self.assertIsInstance(self.view.firstcol, Gtk.ColumnViewColumn)
self.assertIsInstance(self.view.firstcol.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)
self.assertEqual(self.view.firstcol.props.factory.property, "name")
self.assertEqual(self.view.firstcol.props.title, "title")
self.assertFalse(self.view.firstcol.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)
"""Test the model property."""
columns = self.view.props.child.get_columns()
self.assertIsNone(self.view.model)
self.assertEqual(len(columns), 0)
self.view.model = self.model
self.assertEqual(self.view.filtermodel.props.model, self.model)
self.assertEqual(len(columns), 3)
self.assertEqual(columns[0], self.view._property)
self.assertEqual(columns[0], self.view.firstcol)
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)
xfstestsdb.gtk.row.XunitFactory)
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.assertEqual(len(columns), 0)
class EnvironmentView(unittest.TestCase):
"""Tests the EnvironmentView."""
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.props = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 1)
self.model = self.props.environment
self.view = xfstestsdb.gtk.view.EnvironmentView()
def test_init(self):
"""Test that we created the EnvironmentView correctly."""
self.assertIsInstance(self.view, xfstestsdb.gtk.view.XunitView)
self.assertIsNone(self.view.filtermodel.props.filter)
self.assertIsNone(self.view.firstcol)
self.assertIsNone(self.view.model)
self.assertTrue(self.view.props.child.has_css_class("data-table"))
def test_make_factory(self):
"""Test the do_make_factory() implementation."""
factory = self.view.do_make_factory("property")
self.assertIsInstance(factory, xfstestsdb.gtk.row.EnvironmentFactory)
self.assertEqual(factory.property, "property")
def test_model(self):
"""Test the model property."""
columns = self.view.props.child.get_columns()
self.assertIsNone(self.view.model)
self.assertEqual(len(columns), 0)
self.assertFalse(self.view.props.visible)
self.view.model = self.model
self.assertEqual(self.view.props.child.get_columns()[0],
self.view._property)
self.assertEqual(self.view.filtermodel.props.model, self.model)
self.assertEqual(len(columns), 7)
self.assertTrue(self.view.props.visible)
self.view.model = None
self.assertEqual(len(columns), 0)
self.assertFalse(self.view.props.visible)
class TestPropertyView(unittest.TestCase):
"""Tests the PropertyView."""
def setUp(self):
"""Set up common variables."""
self.view = xfstestsdb.gtk.view.PropertyView()
def test_init(self):
"""Test that we created the ProeprtyView correctly."""
self.assertIsInstance(self.view, xfstestsdb.gtk.view.XunitView)
self.assertIsInstance(self.view.filtermodel.props.filter,
xfstestsdb.gtk.model.PropertyFilter)
self.assertIsNone(self.view.model)
self.assertEqual(self.view.props.vscrollbar_policy,
Gtk.PolicyType.NEVER)
self.assertEqual(self.view.firstcol.props.title, "property")
self.assertTrue(self.view.props.child.has_css_class("data-table"))
def test_make_factory(self):
"""Test the do_make_factory() implementation."""
factory = self.view.do_make_factory("xunit-1")
self.assertIsInstance(factory, xfstestsdb.gtk.row.PropertyFactory)
self.assertEqual(factory.xunit, "xunit-1")
class TestFilterButtons(unittest.TestCase):
@ -148,91 +215,221 @@ class TestTestCaseView(unittest.TestCase):
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.TestCaseList(self.xfstestsdb.sql, 1)
self.view = xfstestsdb.gtk.view.TestCaseView()
def test_init(self):
"""Test that we create the TestCaseView correctly."""
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
self.assertTrue(self.view.has_css_class("card"))
self.assertIsInstance(self.view, xfstestsdb.gtk.view.XunitView)
self.assertIsInstance(self.view.filtermodel.props.filter,
xfstestsdb.gtk.model.TestCaseFilter)
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.assertEqual(self.view.firstcol.props.title, "testcase")
self.assertTrue(self.view.props.child.get_vexpand())
def test_filter(self):
"""Test that we set up the Gtk.FilterModel and Buttons correctly."""
self.assertIsInstance(self.view._filtermodel, Gtk.FilterListModel)
self.assertIsInstance(self.view._testfilter,
xfstestsdb.gtk.model.TestCaseFilter)
self.assertIsInstance(self.view.filterbuttons,
xfstestsdb.gtk.view.FilterButtons)
self.assertEqual(self.view.props.child.get_model().get_model(),
self.view._filtermodel)
self.assertEqual(self.view._filtermodel.props.filter,
self.view._testfilter)
self.assertFalse(self.view._testfilter.passed)
self.assertFalse(self.view.filtermodel.props.filter.passed)
self.view.filterbuttons.passed = True
self.assertTrue(self.view._testfilter.passed)
self.assertTrue(self.view.filtermodel.props.filter.passed)
self.assertFalse(self.view._testfilter.skipped)
self.assertFalse(self.view.filtermodel.props.filter.skipped)
self.view.filterbuttons.skipped = True
self.assertTrue(self.view._testfilter.skipped)
self.assertTrue(self.view.filtermodel.props.filter.skipped)
self.assertTrue(self.view._testfilter.failure)
self.assertTrue(self.view.filtermodel.props.filter.failure)
self.view.filterbuttons.failure = False
self.assertFalse(self.view._testfilter.failure)
self.assertFalse(self.view.filtermodel.props.filter.failure)
def test_testcase_column(self):
"""Test that we set up the 'testcase' column correctly."""
self.assertIsInstance(self.view._testcase, Gtk.ColumnViewColumn)
self.assertIsInstance(self.view._testcase.props.factory,
xfstestsdb.gtk.row.LabelFactory)
self.assertEqual(self.view._testcase.props.factory.property, "name")
self.assertEqual(self.view._testcase.props.title, "testcase")
self.assertFalse(self.view._testcase.props.expand)
def test_make_factory(self):
"""Test the do_make_factory() implementation."""
show_messages = unittest.mock.Mock()
self.view.connect("show-messages", show_messages)
def test_model(self):
"""Test setting the model property."""
self.view.model = self.model
self.assertEqual(self.view._filtermodel.props.model, self.model)
factory = self.view.do_make_factory("xunit-1")
self.assertIsInstance(factory, xfstestsdb.gtk.row.ResultFactory)
self.assertEqual(factory.xunit, "xunit-1")
columns = self.view.props.child.get_columns()
self.assertEqual(len(columns), 3)
self.assertEqual(columns[0], self.view._testcase)
factory.emit("show-messages", "testcase", "xunit", "stdout", "stderr")
show_messages.assert_called_with(self.view, "testcase", "xunit",
"stdout", "stderr")
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.ResultFactory)
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)
class TestMessageView(unittest.TestCase):
"""Test the MessageView."""
self.view.model = self.model
self.assertEqual(self.view.props.child.get_columns()[0],
self.view._testcase)
def setUp(self):
"""Set up common variables."""
self.view = xfstestsdb.gtk.view.MessageView("title")
def test_init(self):
"""Check that the MessageView was set up correctly."""
self.assertIsInstance(self.view, Gtk.Box)
self.assertEqual(self.view.props.orientation, Gtk.Orientation.VERTICAL)
self.assertTrue(self.view.has_css_class("view"))
def test_detect_diff(self):
"""Check detecting if input test looks like a diff."""
self.assertFalse(self.view.detect_diff("not a diff"))
lines = ["+++ /some/file"]
self.assertFalse(self.view.detect_diff("\n".join(lines)))
lines.append("--- /some/other/file")
self.assertFalse(self.view.detect_diff("\n".join(lines)))
lines.append("@@ 12,34,5 @@")
self.assertFalse(self.view.detect_diff("\n".join(lines)))
lines.append(" some context line")
self.assertTrue(self.view.detect_diff("\n".join(lines)))
lines[-1] = "+an added line"
self.assertTrue(self.view.detect_diff("\n".join(lines)))
lines[-1] = "-a removed line"
self.assertTrue(self.view.detect_diff("\n".join(lines)))
def test_markup_diff(self):
"""Check colorizing lines with diff colors."""
self.assertEqual(self.view.markup_diff("abcde"), "abcde")
self.assertEqual(self.view.markup_diff("+++ /some/file"),
"<span color='#26a269'>+++ /some/file</span>")
self.assertEqual(self.view.markup_diff("--- /some/other/file"),
"<span color='#c01c28'>--- /some/other/file</span>")
self.assertEqual(self.view.markup_diff("@@ 12,34,5 @@"),
"<span color='#1c71d8'>@@ 12,34,5 @@</span>")
self.assertEqual(self.view.markup_diff(" a context line"),
"<span color='#77767b'> a context line</span>")
self.assertEqual(self.view.markup_diff("+an added line"),
"<span color='#26a269'>+an added line</span>")
self.assertEqual(self.view.markup_diff("-a removed line"),
"<span color='#c01c28'>-a removed line</span>")
def test_title(self):
"""Test the title widgets."""
self.assertIsInstance(self.view._label, Gtk.Label)
self.assertIsInstance(self.view._label.get_next_sibling(),
Gtk.Separator)
self.assertEqual(self.view.get_first_child(), self.view._label)
self.assertEqual(self.view.title, "title")
self.assertEqual(self.view._label.props.label, "title")
self.assertEqual(self.view._label.props.margin_top, 6)
self.assertTrue(self.view._label.has_css_class("large-title"))
def test_text(self):
"""Test the text property."""
win = self.view.get_last_child()
self.assertIsInstance(win, Gtk.ScrolledWindow)
self.assertIsInstance(self.view._textview, Gtk.TextView)
self.assertEqual(win.props.child, self.view._textview)
self.assertTrue(win.props.vexpand)
self.assertFalse(self.view._textview.props.editable)
self.assertTrue(self.view._textview.props.monospace)
buffer = self.view._textview.get_buffer()
self.assertEqual(self.view.text, "")
with unittest.mock.patch.object(buffer, "set_text",
wraps=buffer.set_text) as mock_set:
self.view.text = "text"
self.assertEqual(buffer.get_text(buffer.get_start_iter(),
buffer.get_end_iter(), True),
"text")
mock_set.assert_called_with("text")
self.assertEqual(self.view.text, "text")
def test_text_diff(self):
"""Test setting the text property to a diff string."""
buffer = self.view._textview.get_buffer()
diff = ["+++ /some/file", "--- /some/other/file", "@@ 12,34,5 @@",
" context line", "-removed line", "+added line"]
with unittest.mock.patch.object(buffer, "set_text",
wraps=buffer.set_text) as mock_set:
self.view.text = "\n".join(diff)
mock_set.assert_not_called()
self.assertEqual(self.view.text, "\n".join(diff))
self.view.text = "\n".join(diff)
self.assertEqual(self.view.text, "\n".join(diff))
class MessagesView(unittest.TestCase):
"""Test the MessagesView."""
def setUp(self):
"""Set up common variables."""
self.view = xfstestsdb.gtk.view.MessagesView()
def test_init(self):
"""Check that the MessagesView was set up correctly."""
self.assertIsInstance(self.view, Gtk.Box)
self.assertIsInstance(self.view.get_first_child(), Gtk.CenterBox)
self.assertIsInstance(self.view.get_last_child(), Gtk.Paned)
self.assertTrue(self.view.get_first_child().has_css_class("toolbar"))
self.assertTrue(self.view.has_css_class("card"))
self.assertEqual(self.view.props.orientation, Gtk.Orientation.VERTICAL)
self.assertEqual(self.view.props.margin_start, 24)
self.assertEqual(self.view.props.margin_end, 24)
self.assertEqual(self.view.props.margin_top, 24)
self.assertEqual(self.view.props.margin_bottom, 24)
def test_back_button(self):
"""Check that the back button was set up correctly."""
self.assertIsInstance(self.view._back, Gtk.Button)
self.assertIsInstance(self.view._back.props.child, Adw.ButtonContent)
self.assertEqual(self.view.get_first_child().props.start_widget,
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")
self.assertTrue(self.view._back.has_css_class("suggested-action"))
self.assertTrue(self.view._back.has_css_class("pill"))
go_back = unittest.mock.Mock()
self.view.connect("go-back", go_back)
self.view._back.emit("clicked")
go_back.assert_called()
def test_title(self):
"""Check that the view title was set up correctly."""
self.assertIsInstance(self.view._title, Adw.WindowTitle)
self.assertEqual(self.view.get_first_child().props.center_widget,
self.view._title)
self.assertEqual(self.view.testcase, "")
self.view.testcase = "test/case"
self.assertEqual(self.view._title.props.title, "test/case")
self.assertEqual(self.view.xunit, "")
self.view.xunit = "xunit-1"
self.assertEqual(self.view._title.props.subtitle, "xunit-1")
def test_stdout(self):
"""Check that the stdout window was set up properly."""
self.assertIsInstance(self.view._stdout,
xfstestsdb.gtk.view.MessageView)
self.assertEqual(self.view.get_last_child().props.start_child,
self.view._stdout)
self.assertEqual(self.view._stdout.title, "stdout")
self.assertEqual(self.view.stdout, "")
self.view.stdout = "stdout text"
self.assertEqual(self.view._stdout.text, "stdout text")
def test_stderr(self):
"""Check that the stderr window was set up properly."""
self.assertIsInstance(self.view._stderr,
xfstestsdb.gtk.view.MessageView)
self.assertEqual(self.view.get_last_child().props.end_child,
self.view._stderr)
self.assertEqual(self.view._stderr.title, "stderr")
self.assertEqual(self.view.stderr, "")
self.view.stderr = "stderr text"
self.assertEqual(self.view._stderr.text, "stderr text")
class TestSummaryView(unittest.TestCase):
@ -240,68 +437,24 @@ class TestSummaryView(unittest.TestCase):
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.SummaryList(self.xfstestsdb.sql, 1)
self.view = xfstestsdb.gtk.view.SummaryView()
def test_init(self):
"""Test that we created the SummaryView 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.assertIsInstance(self.view, xfstestsdb.gtk.view.XunitView)
self.assertIsNone(self.view.filtermodel.props.filter)
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.assertEqual(self.view.props.vscrollbar_policy,
Gtk.PolicyType.NEVER)
self.assertEqual(self.view.firstcol.props.title, "summary")
self.assertTrue(self.view.props.child.has_css_class("data-table"))
def test_summary_column(self):
"""Test that we set up the 'summary' column correctly."""
self.assertIsInstance(self.view._summary, Gtk.ColumnViewColumn)
self.assertIsInstance(self.view._summary.props.factory,
xfstestsdb.gtk.row.LabelFactory)
self.assertEqual(self.view._summary.props.factory.property, "name")
self.assertEqual(self.view._summary.props.title, "summary")
self.assertFalse(self.view._summary.props.expand)
def test_model(self):
"""Test setting the model property."""
self.view.model = self.model
self.assertEqual(self.view.props.child.get_model().get_model(),
self.model)
columns = self.view.props.child.get_columns()
self.assertEqual(len(columns), 3)
self.assertEqual(columns[0], self.view._summary)
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.SummaryFactory)
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._summary)
def test_make_factory(self):
"""Test the do_make_factory() implementation."""
factory = self.view.do_make_factory("xunit-1")
self.assertIsInstance(factory, xfstestsdb.gtk.row.SummaryFactory)
self.assertEqual(factory.xunit, "xunit-1")
class TestXfstestsView(unittest.TestCase):
@ -327,28 +480,57 @@ class TestXfstestsView(unittest.TestCase):
self.assertIsInstance(self.view, Gtk.Box)
self.assertEqual(self.view.props.orientation, Gtk.Orientation.VERTICAL)
def test_environment_view(self):
"""Check that the XfstestsView sets up an EnvironmentView correctly."""
self.assertIsInstance(self.view._environview,
xfstestsdb.gtk.view.EnvironmentView)
self.assertEqual(self.view.get_first_child(), self.view._environview)
def test_property_view(self):
"""Check that the XfstestsView sets up a PropertyView correctly."""
sep = self.view._environview.get_next_sibling()
self.assertIsInstance(sep, Gtk.Separator)
self.assertIsInstance(self.view._propertyview,
xfstestsdb.gtk.view.PropertyView)
self.assertEqual(self.view.get_first_child(), self.view._propertyview)
self.assertEqual(sep.get_next_sibling(), self.view._propertyview)
def test_stack(self):
"""Check that the XfstestsView sets u a Gtk.Stack correctly."""
sep = self.view._propertyview.get_next_sibling()
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)
self.assertEqual(sep.get_next_sibling(), self.view._stack)
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(sep.get_next_sibling(), self.view._testcaseview)
self.assertEqual(self.view._stack.get_child_by_name("testcases"),
self.view._testcaseview)
def test_messages_view(self):
"""Check that the XfstestsView sets up a MessagesView correctly."""
self.assertIsInstance(self.view._messagesview,
xfstestsdb.gtk.view.MessagesView)
self.assertEqual(self.view._stack.get_child_by_name("messages"),
self.view._messagesview)
def test_summary_view(self):
"""Check that the XfstestsView sets up a SummaryView correctly."""
sep = self.view._testcaseview.get_next_sibling()
sep = self.view._stack.get_next_sibling()
self.assertIsInstance(sep, Gtk.Separator)
self.assertIsInstance(self.view._summaryview,
xfstestsdb.gtk.view.SummaryView)
self.assertEqual(sep.get_next_sibling(), self.view._summaryview)
def test_environment(self):
"""Test the XfstestsView 'environment' property."""
self.assertIsNone(self.view.environment)
self.view.environment = self.props.environment
self.assertEqual(self.view._environview.model, self.props.environment)
def test_properties(self):
"""Test the XfstestsView 'properties' property."""
self.assertIsNone(self.view.properties)
@ -371,3 +553,21 @@ class TestXfstestsView(unittest.TestCase):
"""Test the XfstestsView 'filterbuttons' property."""
self.assertEqual(self.view.filterbuttons,
self.view._testcaseview.filterbuttons)
def test_messages(self):
"""Test displaying messages to the user."""
self.assertEqual(self.view._stack.get_visible_child_name(),
"testcases")
self.view._testcaseview.emit("show-messages", "testcase",
"xunit", "stdout", "stderr")
self.assertEqual(self.view._stack.get_visible_child_name(),
"messages")
self.assertEqual(self.view._messagesview.testcase, "testcase")
self.assertEqual(self.view._messagesview.xunit, "xunit")
self.assertEqual(self.view._messagesview.stdout, "stdout")
self.assertEqual(self.view._messagesview.stderr, "stderr")
self.view._messagesview.emit("go-back")
self.assertEqual(self.view._stack.get_visible_child_name(),
"testcases")

View File

@ -44,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.environment)
self.assertIsNone(self.application.properties)
self.assertIsNone(self.application.model)
self.assertIsNone(self.application.summary)
@ -64,6 +65,8 @@ class TestApplication(unittest.TestCase):
xfstestsdb.gtk.model.TestCaseList)
self.assertIsInstance(self.application.summary,
xfstestsdb.gtk.model.SummaryList)
self.assertEqual(self.application.environment,
self.application.properties.environment)
self.assertEqual(self.application.model.runid, 42)
@unittest.mock.patch("xfstestsdb.gtk.gsetup.add_style")
@ -94,6 +97,10 @@ class TestApplication(unittest.TestCase):
self.application.properties = properties
self.assertEqual(self.application.view.properties, properties)
self.application.environment = properties.environment
self.assertEqual(self.application.view.environment,
properties.environment)
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, 4)
self.assertEqual(xfstestsdb.MINOR, 5)
self.xfstestsdb.run(["--version"])
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.4-debug\n")
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.5-debug\n")

View File

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

View File

@ -18,6 +18,7 @@ class Application(Adw.Application):
"""Our Adw.Application for displaying xfstests results."""
runid = GObject.Property(type=int)
environment = GObject.Property(type=Gio.ListStore)
properties = GObject.Property(type=model.PropertyList)
summary = GObject.Property(type=model.SummaryList)
model = GObject.Property(type=model.TestCaseList)
@ -42,6 +43,7 @@ class Application(Adw.Application):
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)
@ -58,6 +60,7 @@ class Application(Adw.Application):
self.win.headerbar.pack_end(self.view.filterbuttons)
self.bind_property("runid", self.win, "runid")
self.bind_property("environment", self.view, "environment")
self.bind_property("properties", self.view, "properties")
self.bind_property("model", self.view, "model")
self.bind_property("summary", self.view, "summary")

View File

@ -48,6 +48,10 @@ class XunitRow(GObject.GObject):
"""Get a set of results for each added xunit."""
return {str(xunit) for xunit in self.__xunits.values()}
def get_xunits(self) -> set[str]:
"""Get a set of xunits added to this row."""
return list(sorted(self.__xunits.keys()))
class XunitList(GObject.GObject, Gio.ListModel):
"""A list of XunitRows for a specific Xfstests Run."""
@ -93,6 +97,11 @@ class XunitList(GObject.GObject, Gio.ListModel):
return sorted(self.__xunits)
HIDDEN_PROPERTIES = {"CPUS", "HOST_OPTIONS", "LOAD_FACTOR", "MEM_KB",
"NUMA_NODES", "OVL_LOWER", "OVL_UPPER", "OVL_WORK",
"PLATFORM", "SECTION", "SWAP_KB", "TIME_FACTOR"}
class PropertyValue(XunitCell):
"""A single Property for a specific Xunit."""
@ -113,12 +122,25 @@ class Property(XunitRow):
def all_same_value(self) -> bool:
"""Check if all the xunits have the same value."""
return len(self.get_results()) == 1
return len(self.get_results()) == 1 and len(self.get_xunits()) > 1
def get_value(self) -> str | None:
"""Get the value of this row if all xunits have the same value."""
if self.all_same_value():
return self[self.get_xunits()[0]].value
class PropertyList(XunitList):
"""A list of Properties for a specific Xfstests Run."""
environment = GObject.Property(type=Gio.ListStore)
def __init__(self, sql: sqlite.Connection, runid: int) -> None:
"""Initialize an XunitList."""
super().__init__(sql=sql, runid=runid)
self.environment = Gio.ListStore()
self.environment.append(self)
def do_query(self, sql: sqlite.Connection) -> sqlite3.Cursor:
"""Query the database for properties."""
return sql("""SELECT xunit, key, value FROM xunit_properties_view
@ -129,6 +151,11 @@ class PropertyList(XunitList):
property = rows.setdefault(row["key"], Property(row["key"]))
property.add_xunit(row["xunit"], row["key"], row["value"])
def get_environment(self) -> dict[str, str]:
"""Get a dictionary of 'environment' properties."""
return {row.name: row.get_value() for row in self
if row.name not in HIDDEN_PROPERTIES and row.all_same_value()}
class PropertyFilter(Gtk.Filter):
"""A filter for Properties."""
@ -139,10 +166,8 @@ class PropertyFilter(Gtk.Filter):
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
return property.name not in HIDDEN_PROPERTIES \
and not property.all_same_value()
class TestResult(XunitCell):

View File

@ -87,15 +87,36 @@ class LabelFactory(Factory):
LabelFactory.group.remove_widget(child)
class PropertyFactory(Factory):
"""Factory for making property widgets."""
class EnvironmentFactory(Factory):
"""Factory for Environment property columns."""
property = GObject.Property(type=str)
def __init__(self, *, property: str):
"""Initialize our Environment Factory."""
super().__init__(property=property)
def do_bind(self, row: model.PropertyList, child: Gtk.Inscription) -> None:
"""Bind an Environment property to the child widget."""
text = row.get_environment()[self.property]
child.set_xalign(0)
child.set_text(text)
child.set_tooltip_text(text)
class XunitFactory(Factory):
"""Factory base class for Xunit columns."""
xunit = GObject.Property(type=str)
def __init__(self, xunit: str):
"""Initialize our InscriptionFactory."""
def __init__(self, *, xunit: str):
"""Initialize our Xunit Factory."""
super().__init__(xunit=xunit)
class PropertyFactory(XunitFactory):
"""Factory for making property widgets."""
def do_bind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
property = row[self.xunit]
@ -103,14 +124,20 @@ class PropertyFactory(Factory):
child.set_tooltip_text(property.value)
class ResultFactory(Factory):
class ResultFactory(XunitFactory):
"""Factory for making test result widgets."""
xunit = GObject.Property(type=str)
def __clicked(self, click: Gtk.GestureClick, n_press:
int, x: float, y: float, row: model.TestCase) -> None:
if (result := row[self.xunit]) is not None:
if len(result.stdout) > 0 or len(result.stderr) > 0:
self.emit("show-messages", row.name, self.xunit,
result.stdout, result.stderr)
def __init__(self, xunit: str):
"""Initialize our ResultFactory."""
super().__init__(xunit=xunit)
def do_setup(self, child: Gtk.Inscription) -> None:
"""Set up click handling on the child widget."""
child.click = Gtk.GestureClick()
child.add_controller(child.click)
def do_bind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
@ -123,22 +150,28 @@ class ResultFactory(Factory):
child.set_text(text)
child.set_tooltip_text(result.message.lstrip(" -"))
child.get_parent().add_css_class(result.status)
child.click.connect("released", self.__clicked, row)
def do_unbind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
"""Unbind a ListItem from the child widget."""
if (result := row[self.xunit]) is not None:
child.get_parent().remove_css_class(result.status)
child.click.disconnect_by_func(self.__clicked)
def do_teardown(self, child: Gtk.Inscription) -> None:
"""Clean up the GestureClick."""
child.remove_controller(child.click)
setattr(child, "click", None)
@GObject.Signal(arg_types=(str, str, str, str))
def show_messages(self, testcase: str, xunit: str,
stdout: str, stderr: str) -> None:
"""Show the selected messages to the user."""
class SummaryFactory(Factory):
class SummaryFactory(XunitFactory):
"""Factory for making test summary widgets."""
xunit = GObject.Property(type=str)
def __init__(self, xunit: str):
"""Initialize our ResultFactory."""
super().__init__(xunit=xunit)
def do_bind(self, row: model.Summary, child: Gtk.Inscription) -> None:
"""Bind a ListItem to the child widget."""
result = row[self.xunit]

View File

@ -1,56 +1,110 @@
# Copyright 2023 (c) Anna Schumaker.
"""A view widget used to display our TestCaseModel."""
import re
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gtk
from gi.repository import Adw
from .model import PropertyList
from .model import PropertyFilter
from .model import TestCaseList
from .model import TestCaseFilter
from .model import SummaryList
from .model import XunitList
from . import button
from . import row
class PropertyView(Gtk.ScrolledWindow):
class XunitView(Gtk.ScrolledWindow):
"""Our XunitView base class."""
filtermodel = GObject.Property(type=Gtk.FilterListModel)
firstcol = GObject.Property(type=Gtk.ColumnViewColumn)
def __init__(self, title: str, **kwargs):
"""Initialize an XunitView."""
factory = row.LabelFactory("name")
super().__init__(filtermodel=Gtk.FilterListModel(),
firstcol=Gtk.ColumnViewColumn(title=title,
factory=factory),
child=Gtk.ColumnView(model=Gtk.NoSelection(),
show_row_separators=True,
show_column_separators=True,
hexpand=True),
**kwargs)
self.props.child.get_model().set_model(self.filtermodel)
self.add_css_class("card")
def do_make_factory(self, xunit: str) -> row.XunitFactory:
"""Make an XunitFactory for the given xunit."""
return row.XunitFactory(xunit=xunit)
@GObject.Property(type=XunitList)
def model(self) -> XunitList | None:
"""Get the XunitList shown by the XunitView."""
return self.filtermodel.props.model
@model.setter
def model(self, new: XunitList) -> None:
for col in list(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.firstcol)
for xunit in new.get_xunits():
col = Gtk.ColumnViewColumn(title=xunit, expand=True,
factory=self.do_make_factory(xunit))
self.props.child.append_column(col)
class EnvironmentView(XunitView):
"""Displays our Environment properties to the user."""
def __init__(self):
"""Initialize an EnvironmentView."""
super().__init__("environment", visible=False)
self.firstcol = None
self.props.child.add_css_class("data-table")
def do_make_factory(self, property: str) -> row.EnvironmentFactory:
"""Make a new EnvironmentFactory instance."""
return row.EnvironmentFactory(property=property)
@GObject.Property(type=Gio.ListModel)
def model(self) -> Gio.ListModel | None:
"""Get the ListModel shown by the EnvironmentView."""
return self.filtermodel.props.model
@model.setter
def model(self, new: Gio.ListModel) -> None:
for col in list(self.props.child.get_columns()):
self.props.child.remove_column(col)
self.filtermodel.props.model = new
self.props.visible = new is not None
if new is not None:
environ = new[0].get_environment()
for prop in environ.keys():
col = Gtk.ColumnViewColumn(title=prop, expand=True,
factory=self.do_make_factory(prop))
self.props.child.append_column(col)
class PropertyView(XunitView):
"""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)
super().__init__("property", vscrollbar_policy=Gtk.PolicyType.NEVER)
self.filtermodel.set_filter(PropertyFilter())
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))
def do_make_factory(self, xunit: str) -> row.PropertyFactory:
"""Make a new PropertyFactory instance."""
return row.PropertyFactory(xunit=xunit)
class FilterButtons(Gtk.Box):
@ -79,116 +133,211 @@ class FilterButtons(Gtk.Box):
self.append(self._failure)
class TestCaseView(Gtk.ScrolledWindow):
class TestCaseView(XunitView):
"""Displays our TestCaseList model to the user."""
filterbuttons = GObject.Property(type=FilterButtons)
def __init__(self):
"""Initialize a TestCaseView."""
super().__init__(child=Gtk.ColumnView(model=Gtk.NoSelection(),
show_row_separators=True,
show_column_separators=True,
hexpand=True, vexpand=True),
filterbuttons=FilterButtons())
self._testcase = Gtk.ColumnViewColumn(title="testcase",
factory=row.LabelFactory("name"))
self._testfilter = TestCaseFilter()
self._filtermodel = Gtk.FilterListModel(filter=self._testfilter)
super().__init__("testcase", filterbuttons=FilterButtons())
self.filtermodel.props.filter = TestCaseFilter()
for prop in ["passed", "skipped", "failure"]:
self.filterbuttons.bind_property(prop, self._testfilter, prop)
self.filterbuttons.bind_property(prop,
self.filtermodel.props.filter,
prop)
self.props.child.get_model().set_model(self._filtermodel)
self.props.child.set_vexpand(True)
def __show_messages(self, factory: row.ResultFactory, testcase: str,
xunit: str, stdout: str, stderr: str) -> None:
self.emit("show-messages", testcase, xunit, stdout, stderr)
def do_make_factory(self, xunit: str) -> row.ResultFactory:
"""Make a new ResultFactory instance."""
factory = row.ResultFactory(xunit=xunit)
factory.connect("show-messages", self.__show_messages)
return factory
@GObject.Signal(arg_types=(str, str, str, str))
def show_messages(self, testcase: str, xunit: str,
stdout: str, stderr: str) -> None:
"""Signal that the user wants to inspect stdout and stderr messages."""
class MessageView(Gtk.Box):
"""A view for displaying a multiline test result message."""
title = GObject.Property(type=str)
def __init__(self, title: str):
"""Initialize a MessageView."""
super().__init__(title=title, orientation=Gtk.Orientation.VERTICAL)
self._label = Gtk.Label(label=self.title, margin_top=6)
self._textview = Gtk.TextView(monospace=True, editable=False)
self.append(self._label)
self.append(Gtk.Separator())
self.append(Gtk.ScrolledWindow(child=self._textview, vexpand=True))
self._label.add_css_class("large-title")
self.add_css_class("view")
def detect_diff(self, text: str) -> bool:
"""Detect if the given text looks like a diff."""
in_file = out_file = counts = changed = False
for line in text.split("\n"):
if re.match(r"^\+\+\+", line):
in_file = True
elif re.match(r"^---", line):
out_file = True
elif re.match(r"^@@(.*?)@@", line):
counts = True
elif re.match(r"^[\+| |-](.*?)", line):
changed = True
return in_file and out_file and counts and changed
def markup_diff(self, text: str) -> str:
"""Add Pango markup to the input string."""
if re.match(r"^\++(.*?)", text):
return f"<span color='#26a269'>{text}</span>"
elif re.match(r"^-+(.*?)", text):
return f"<span color='#c01c28'>{text}</span>"
elif re.match(r"^@@(.*?)@@", text):
return f"<span color='#1c71d8'>{text}</span>"
elif re.match(r"^ (.*?)", text):
return f"<span color='#77767b'>{text}</span>"
return text
@GObject.Property(type=str)
def text(self) -> str:
"""Get the text displayed in the view."""
buffer = self._textview.props.buffer
return buffer.get_text(buffer.get_start_iter(),
buffer.get_end_iter(), True)
@text.setter
def text(self, new_text: str) -> None:
buffer = self._textview.props.buffer
if self.detect_diff(new_text):
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
for i, line in enumerate(new_text.split("\n")):
text = self.markup_diff(line)
text = f"\n{text}" if i > 0 else text
buffer.insert_markup(buffer.get_end_iter(), text, len(text))
else:
buffer.set_text(new_text)
class MessagesView(Gtk.Box):
"""A view for displaying stdout and stderr messages."""
testcase = GObject.Property(type=str)
xunit = GObject.Property(type=str)
stdout = GObject.Property(type=str)
stderr = GObject.Property(type=str)
def __init__(self):
"""Initialize a MessagesView."""
icon = "go-previous-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"))
self._title = Adw.WindowTitle()
self._stdout = MessageView("stdout")
self._stderr = MessageView("stderr")
self.bind_property("testcase", self._title, "title")
self.bind_property("xunit", self._title, "subtitle")
self.bind_property("stdout", self._stdout, "text")
self.bind_property("stderr", self._stderr, "text")
self._back.connect("clicked", self.__back_clicked)
self.append(Gtk.CenterBox(start_widget=self._back,
center_widget=self._title))
self.append(Gtk.Paned(start_child=self._stdout,
end_child=self._stderr, vexpand=True))
self.get_first_child().add_css_class("toolbar")
self._back.add_css_class("suggested-action")
self._back.add_css_class("pill")
self.add_css_class("card")
def __xunit_column(self, xunit: str) -> None:
return Gtk.ColumnViewColumn(title=xunit, expand=True,
factory=row.ResultFactory(xunit))
def __back_clicked(self, button: Gtk.Button) -> None:
self.emit("go-back")
def make_buttons(self) -> FilterButtons:
"""Make a new FilterButtons instance connected to this View."""
return FilterButtons()
@GObject.Property(type=TestCaseList)
def model(self) -> TestCaseList:
"""Get the TestCaseList shown by the View."""
return self._filtermodel.props.model
@model.setter
def model(self, new: TestCaseList) -> 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._testcase)
for xunit in new.get_xunits():
self.props.child.append_column(self.__xunit_column(xunit))
@GObject.Signal
def go_back(self) -> None:
"""Signal that the user wants to go back."""
class SummaryView(Gtk.ScrolledWindow):
class SummaryView(XunitView):
"""Displays our SummaryList model to the user."""
def __init__(self):
"""Initialize a SummaryView."""
super().__init__(child=Gtk.ColumnView(model=Gtk.NoSelection(),
show_row_separators=True,
show_column_separators=True,
hexpand=True),
vscrollbar_policy=Gtk.PolicyType.NEVER)
self._summary = Gtk.ColumnViewColumn(title="summary",
factory=row.LabelFactory("name"))
self.add_css_class("card")
super().__init__("summary", vscrollbar_policy=Gtk.PolicyType.NEVER)
self.props.child.add_css_class("data-table")
def __summary_column(self, xunit: str) -> None:
return Gtk.ColumnViewColumn(title=xunit, expand=True,
factory=row.SummaryFactory(xunit))
@GObject.Property(type=SummaryList)
def model(self) -> SummaryList:
"""Get the SummaryList shown by the View."""
return self.props.child.get_model().get_model()
@model.setter
def model(self, new: SummaryList) -> None:
for col in [col for col in self.props.child.get_columns()]:
self.props.child.remove_column(col)
self.props.child.get_model().set_model(new)
if new is not None:
self.props.child.append_column(self._summary)
for xunit in new.get_xunits():
self.props.child.append_column(self.__summary_column(xunit))
def do_make_factory(self, xunit: str) -> row.SummaryFactory:
"""Make a new SummaryFactory instance."""
return row.SummaryFactory(xunit=xunit)
class XfstestsView(Gtk.Box):
"""A widget to display the results of an Xfstests runs."""
environment = GObject.Property(type=Gio.ListModel)
properties = GObject.Property(type=PropertyList)
model = GObject.Property(type=TestCaseList)
summary = GObject.Property(type=SummaryList)
def __init__(self):
"""Initialize an XfstestsView."""
animation = Gtk.StackTransitionType.OVER_LEFT_RIGHT
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._environview = EnvironmentView()
self._propertyview = PropertyView()
self._testcaseview = TestCaseView()
self._messagesview = MessagesView()
self._stack = Gtk.Stack(transition_type=animation)
self._summaryview = SummaryView()
self.bind_property("environment", self._environview, "model")
self.bind_property("properties", self._propertyview, "model")
self.bind_property("model", self._testcaseview, "model")
self.bind_property("summary", self._summaryview, "model")
self._testcaseview.connect("show-messages", self.__show_messages)
self._messagesview.connect("go-back", self.__show_testcases)
self._stack.add_named(self._testcaseview, "testcases")
self._stack.add_named(self._messagesview, "messages")
self.append(self._environview)
self.append(Gtk.Separator())
self.append(self._propertyview)
self.append(Gtk.Separator())
self.append(self._testcaseview)
self.append(self._stack)
self.append(Gtk.Separator())
self.append(self._summaryview)
def __show_messages(self, view: TestCaseView, testcase: str,
xunit: str, stdout: str, stderr: str) -> None:
self._messagesview.testcase = testcase
self._messagesview.xunit = xunit
self._messagesview.stdout = stdout
self._messagesview.stderr = stderr
self._stack.set_visible_child_name("messages")
def __show_testcases(self, view: MessagesView) -> None:
self._stack.set_visible_child_name("testcases")
@GObject.Property(type=FilterButtons)
def filterbuttons(self) -> FilterButtons:
"""Get the FilterButtons attached to the child TestCaseView."""

View File

@ -26,3 +26,8 @@ cell.failure {
color: @error_fg_color;
background-color: @error_bg_color;
}
cell.failure:hover {
color: @error_fg_color;
background-color: shade(@error_bg_color, 1.1);
}