Compare commits
62 Commits
xfstestsdb
...
main
Author | SHA1 | Date | |
---|---|---|---|
4600258721 | |||
79fae64f37 | |||
2e85870c87 | |||
7e6f944cde | |||
dc6f5f54c3 | |||
06d10cf883 | |||
3cc3412c6d | |||
11941c3bd3 | |||
ce2c36a0dd | |||
6c8e155a44 | |||
521c96b432 | |||
704eb08091 | |||
bac99c9c54 | |||
2352bc3512 | |||
6fcb4eb5e7 | |||
70274c448c | |||
bf668fc936 | |||
64abc86fee | |||
b05a9ecc82 | |||
5621847451 | |||
f951d4c998 | |||
daae654b8e | |||
528444cab6 | |||
952889687f | |||
ea2913429c | |||
5fb9bd6221 | |||
54be8f5ce3 | |||
5a25935fed | |||
40b1d1789d | |||
f44e064c89 | |||
02452069da | |||
7c7e279648 | |||
c861c49564 | |||
a5d1ae4607 | |||
2deb484754 | |||
af1ab81ea2 | |||
3dc8179624 | |||
c145a67ae6 | |||
7ae246677b | |||
59cb699bd9 | |||
4838889c56 | |||
5dc7735ba7 | |||
c45ec1909e | |||
ad08357121 | |||
4d3425bb57 | |||
455d933fc4 | |||
8a5bc1527a | |||
d92b8148bf | |||
e14667691a | |||
b143114108 | |||
8a54cb5d98 | |||
37079ca7f5 | |||
e865728357 | |||
e0bb2d7be7 | |||
8ef06e9571 | |||
796cf6eec7 | |||
4bce147e45 | |||
7adbb5938d | |||
15cee521c2 | |||
c1b73748f1 | |||
d5413700f0 | |||
62bf603ca4 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
*__pycache__*
|
||||
xfstestsdb/gtk/icons/xfstestsdb.gresource
|
||||
|
|
11
Makefile
11
Makefile
|
@ -3,6 +3,9 @@
|
|||
export PREFIX = /usr/local
|
||||
export XFSTESTSDB_BIN = ${PREFIX}/bin
|
||||
export XFSTESTSDB_LIB = ${PREFIX}/lib/xfstestsdb
|
||||
export GTK_DIR = xfstestsdb/gtk/icons
|
||||
|
||||
all: xfstestsdb.gresource flake8
|
||||
|
||||
clean:
|
||||
find . -type d -name __pycache__ -exec rm -r {} \+
|
||||
|
@ -11,8 +14,12 @@ clean:
|
|||
flake8:
|
||||
flake8
|
||||
|
||||
.PHONY: xfstestsdb.gresource
|
||||
xfstestsdb.gresource:
|
||||
glib-compile-resources --sourcedir=$(GTK_DIR) $(GTK_DIR)/xfstestsdb.gresource.xml
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
install: xfstestsdb.gresource
|
||||
find ./xfstestsdb -type f -not -path "*/__pycache__/*" \
|
||||
-exec install -v -C -D -m 755 "{}" "$(XFSTESTSDB_LIB)/{}" \;
|
||||
install -C -v -m 644 xfstestsdb.py $(XFSTESTSDB_LIB)/xfstestsdb.py
|
||||
|
@ -24,7 +31,7 @@ pytest:
|
|||
pytest
|
||||
|
||||
.PHONY: tests
|
||||
tests: pytest flake8
|
||||
tests: xfstestsdb.gresource pytest flake8
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
|
|
2
tests/gtk/__init__.py
Normal file
2
tests/gtk/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Test package for the xfstestsdb gtk application."""
|
51
tests/gtk/test_button.py
Normal file
51
tests/gtk/test_button.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our custom button classes."""
|
||||
import unittest
|
||||
import xfstestsdb.gtk.button
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestStatusToggle(unittest.TestCase):
|
||||
"""Test our StatusToggle button."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.button = xfstestsdb.gtk.button.StatusToggle("icon-name",
|
||||
"css-class")
|
||||
|
||||
def test_button(self):
|
||||
"""Test the StatusToggle button."""
|
||||
self.assertIsInstance(self.button, Gtk.Button)
|
||||
self.assertEqual(self.button.icon_name, "icon-name")
|
||||
self.assertTrue(self.button.has_css_class("css-class"))
|
||||
|
||||
self.assertFalse(self.button.props.has_frame)
|
||||
self.assertFalse(self.button.active)
|
||||
|
||||
button2 = xfstestsdb.gtk.button.StatusToggle("icon-name", "css-class",
|
||||
active=True)
|
||||
self.assertTrue(button2.active)
|
||||
self.assertAlmostEqual(button2.props.child.props.opacity, 1.0)
|
||||
|
||||
def test_image(self):
|
||||
"""Test that the image is set up correctly, and opacity changes."""
|
||||
self.assertIsInstance(self.button.props.child, Gtk.Image)
|
||||
self.assertEqual(self.button.props.child.props.icon_name, "icon-name")
|
||||
|
||||
self.button.active = True
|
||||
self.assertAlmostEqual(self.button.props.child.props.opacity, 1.0)
|
||||
self.button.active = False
|
||||
self.assertAlmostEqual(self.button.props.child.props.opacity, 0.4)
|
||||
|
||||
def test_clicked(self):
|
||||
"""Test clicking the CSSToggle button."""
|
||||
self.assertFalse(self.button.active)
|
||||
self.assertAlmostEqual(self.button.props.child.props.opacity, 0.4)
|
||||
|
||||
self.button.emit("clicked")
|
||||
self.assertTrue(self.button.active)
|
||||
self.assertAlmostEqual(self.button.props.child.props.opacity, 1.0)
|
||||
|
||||
self.button.emit("clicked")
|
||||
self.assertFalse(self.button.active)
|
||||
self.assertAlmostEqual(self.button.props.child.props.opacity, 0.4)
|
54
tests/gtk/test_gsetup.py
Normal file
54
tests/gtk/test_gsetup.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our Gtk Setup."""
|
||||
import gi
|
||||
import pathlib
|
||||
import unittest
|
||||
import xfstestsdb.gtk.gsetup
|
||||
|
||||
|
||||
class TestGSetup(unittest.TestCase):
|
||||
"""Test our Gtk setup."""
|
||||
|
||||
def test_require_version(self):
|
||||
"""Check that we have called gi.require_version()."""
|
||||
self.assertEqual(gi.get_required_version("Adw"), "1")
|
||||
|
||||
def test_constants(self):
|
||||
"""Check that constants are configured correctly."""
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.DEBUG_STR, "-debug")
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.APPLICATION_ID,
|
||||
"com.nowheycreamery.xfstestsdb.gtk-debug")
|
||||
|
||||
@unittest.mock.patch.object(gi.repository.Gdk.Display, "get_default")
|
||||
@unittest.mock.patch.object(gi.repository.Gtk.StyleContext,
|
||||
"add_provider_for_display")
|
||||
def test_add_style(self, mock_add: unittest.mock.Mock,
|
||||
mock_get_default: unittest.mock.Mock):
|
||||
"""Check that the CSS stylesheet is loaded correctly."""
|
||||
gtk_init_py = pathlib.Path(xfstestsdb.gtk.__file__)
|
||||
stylesheet = gtk_init_py.parent / "xfstestsdb.css"
|
||||
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.CSS_FILE, stylesheet)
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.CSS_PRIORITY,
|
||||
gi.repository.Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
self.assertIsInstance(xfstestsdb.gtk.gsetup.CSS_PROVIDER,
|
||||
gi.repository.Gtk.CssProvider)
|
||||
self.assertNotEqual(xfstestsdb.gtk.gsetup.CSS_PROVIDER.to_string(), "")
|
||||
|
||||
xfstestsdb.gtk.gsetup.add_style()
|
||||
mock_add.assert_called_with(mock_get_default.return_value,
|
||||
xfstestsdb.gtk.gsetup.CSS_PROVIDER,
|
||||
xfstestsdb.gtk.gsetup.CSS_PRIORITY)
|
||||
|
||||
@unittest.mock.patch("gi.repository.Gio.resources_register")
|
||||
def test_resources(self, mock_register: unittest.mock.Mock):
|
||||
"""Test that icon resources have been added to the app."""
|
||||
gtk_init_py = pathlib.Path(xfstestsdb.gtk.__file__)
|
||||
resources = gtk_init_py.parent / "icons" / "xfstestsdb.gresource"
|
||||
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.RESOURCE_FILE, resources)
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.RESOURCE_PATH,
|
||||
"/com/nowheycreamery/xfstestsdb")
|
||||
self.assertIsInstance(xfstestsdb.gtk.gsetup.RESOURCE,
|
||||
gi.repository.Gio.Resource)
|
490
tests/gtk/test_model.py
Normal file
490
tests/gtk/test_model.py
Normal file
|
@ -0,0 +1,490 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our Testcase Gio.ListModel."""
|
||||
import random
|
||||
import unittest
|
||||
import tests.xunit
|
||||
import xfstestsdb.gtk.model
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
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"})
|
||||
self.assertListEqual(self.row.get_xunits(), ["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"})
|
||||
self.assertListEqual(self.row.get_xunits(), ["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.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"]
|
||||
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.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"]
|
||||
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.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):
|
||||
"""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)
|
||||
|
||||
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."""
|
||||
|
||||
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_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))
|
||||
|
||||
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))
|
||||
|
||||
|
||||
class TestTestResult(unittest.TestCase):
|
||||
"""Tests a single TestCase Xunit instance."""
|
||||
|
||||
def test_xunit_result(self):
|
||||
"""Test creating an xunit instance."""
|
||||
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):
|
||||
"""Tests our TestCase GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.testcase = xfstestsdb.gtk.model.TestCase(name="test-case")
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the TestCase is set up properly."""
|
||||
self.assertIsInstance(self.testcase, xfstestsdb.gtk.model.XunitRow)
|
||||
self.assertEqual(self.testcase.name, "test-case")
|
||||
|
||||
def test_xunits(self):
|
||||
"""Test adding xunits to a TestCase."""
|
||||
self.assertIsNone(self.testcase["xunit-1"])
|
||||
|
||||
self.testcase.add_xunit("xunit-1", "passed", 123, "message",
|
||||
"stdout", "stderr")
|
||||
xunit = self.testcase["xunit-1"]
|
||||
self.assertIsInstance(xunit, xfstestsdb.gtk.model.TestResult)
|
||||
self.assertEqual(xunit.name, "xunit-1")
|
||||
self.assertEqual(xunit.status, "passed")
|
||||
self.assertEqual(xunit.time, 123)
|
||||
self.assertEqual(xunit.message, "message")
|
||||
self.assertEqual(xunit.stdout, "stdout")
|
||||
self.assertEqual(xunit.stderr, "stderr")
|
||||
|
||||
self.testcase.add_xunit("xunit-2", "failed", 456, None, None, None)
|
||||
xunit = self.testcase["xunit-2"]
|
||||
self.assertIsInstance(xunit, xfstestsdb.gtk.model.TestResult)
|
||||
self.assertEqual(xunit.name, "xunit-2")
|
||||
self.assertEqual(xunit.status, "failed")
|
||||
self.assertEqual(xunit.time, 456)
|
||||
self.assertEqual(xunit.message, "")
|
||||
self.assertEqual(xunit.stdout, "")
|
||||
self.assertEqual(xunit.stderr, "")
|
||||
|
||||
|
||||
class TestCaseList(unittest.TestCase):
|
||||
"""Tests our TestCaseList Gio.ListModel."""
|
||||
|
||||
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.tclist = xfstestsdb.gtk.model.TestCaseList(self.xfstestsdb.sql, 1)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the TestCaseList was set up properly."""
|
||||
self.assertIsInstance(self.tclist, xfstestsdb.gtk.model.XunitList)
|
||||
self.assertEqual(self.tclist.runid, 1)
|
||||
self.assertEqual(self.tclist.n_items, 10)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
testcase = self.tclist.get_item(0)
|
||||
self.assertIsInstance(testcase, xfstestsdb.gtk.model.TestCase)
|
||||
self.assertEqual(testcase.name, "test/01")
|
||||
for xunit in ["xunit-1", "xunit-2"]:
|
||||
with self.subTest(xunit=xunit):
|
||||
self.assertEqual(testcase[xunit].name, xunit)
|
||||
self.assertEqual(testcase[xunit].status, "passed")
|
||||
self.assertEqual(testcase[xunit].time, 1)
|
||||
self.assertEqual(testcase[xunit].message, "")
|
||||
self.assertEqual(testcase[xunit].stdout, "")
|
||||
self.assertEqual(testcase[xunit].stderr, "")
|
||||
|
||||
self.assertIsNone(self.tclist.get_item(10))
|
||||
|
||||
|
||||
class TestCaseFilter(unittest.TestCase):
|
||||
"""Tests our Gtk.Filter customized for filtering TestCases."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.filter = xfstestsdb.gtk.model.TestCaseFilter()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the TestCaseFilter is set up properly."""
|
||||
self.assertIsInstance(self.filter, Gtk.Filter)
|
||||
|
||||
self.assertTrue(self.filter.failure)
|
||||
self.assertFalse(self.filter.passed)
|
||||
self.assertFalse(self.filter.skipped)
|
||||
|
||||
def test_strictness(self):
|
||||
"""Test the TestCaseFilter strictness."""
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.SOME)
|
||||
self.filter.passed = True
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.SOME)
|
||||
|
||||
self.filter.skipped = True
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.ALL)
|
||||
|
||||
self.filter.passed = False
|
||||
self.filter.skipped = False
|
||||
self.filter.failure = False
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.NONE)
|
||||
|
||||
def test_changed(self):
|
||||
"""Test the TestCaseFilter 'changed' signal."""
|
||||
changed = unittest.mock.Mock()
|
||||
self.filter.connect("changed", changed)
|
||||
|
||||
self.filter.skipped = True
|
||||
changed.assert_called_with(self.filter, Gtk.FilterChange.LESS_STRICT)
|
||||
self.filter.passed = False
|
||||
changed.assert_called_with(self.filter, Gtk.FilterChange.MORE_STRICT)
|
||||
|
||||
def test_match(self):
|
||||
"""Test matching TestCases with the TestCaseFilter."""
|
||||
testcase = xfstestsdb.gtk.model.TestCase(name="test-case")
|
||||
self.assertFalse(self.filter.match(testcase))
|
||||
|
||||
self.filter.passed = False
|
||||
self.filter.skipped = False
|
||||
self.filter.failure = False
|
||||
|
||||
testcase.add_xunit("xunit-1", "skipped", 123, "", "", "")
|
||||
self.assertFalse(self.filter.match(testcase))
|
||||
|
||||
self.filter.skipped = True
|
||||
self.assertTrue(self.filter.match(testcase))
|
||||
|
||||
testcase.add_xunit("xunit-2", "passed", 123, "", "", "")
|
||||
self.assertTrue(self.filter.match(testcase))
|
||||
self.filter.skipped = False
|
||||
self.assertFalse(self.filter.match(testcase))
|
||||
self.filter.passed = True
|
||||
self.assertTrue(self.filter.match(testcase))
|
||||
|
||||
testcase.add_xunit("xunit-3", "failure", 123, "", "", "")
|
||||
self.assertTrue(self.filter.match(testcase))
|
||||
self.filter.passed = False
|
||||
self.assertFalse(self.filter.match(testcase))
|
||||
self.filter.failure = True
|
||||
self.assertTrue(self.filter.match(testcase))
|
||||
|
||||
|
||||
class TestSummaryValue(unittest.TestCase):
|
||||
"""Tests a single Summary Value object."""
|
||||
|
||||
def test_init(self):
|
||||
"""Test creating a summary result instance."""
|
||||
value = xfstestsdb.gtk.model.SummaryValue(name="my xunit name",
|
||||
value=12345,
|
||||
unit="testcase")
|
||||
self.assertIsInstance(value, xfstestsdb.gtk.model.XunitCell)
|
||||
self.assertEqual(value.name, "my xunit name")
|
||||
self.assertEqual(value.unit, "testcase")
|
||||
self.assertEqual(value.value, 12345)
|
||||
|
||||
def test_str(self):
|
||||
"""Test converting a summary result to a string."""
|
||||
value = xfstestsdb.gtk.model.SummaryValue(name="my xunit name",
|
||||
value=1, unit="unit")
|
||||
self.assertEqual(str(value), "1 unit")
|
||||
|
||||
value.value = 2
|
||||
self.assertEqual(str(value), "2 units")
|
||||
|
||||
|
||||
class TestSummary(unittest.TestCase):
|
||||
"""Tests our Summary GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.summary = xfstestsdb.gtk.model.Summary(name="passed")
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the Summary is set up properly."""
|
||||
self.assertIsInstance(self.summary, xfstestsdb.gtk.model.XunitRow)
|
||||
self.assertEqual(self.summary.name, "passed")
|
||||
|
||||
def test_compare(self):
|
||||
"""Test the less-than operator on Summaries."""
|
||||
failed = xfstestsdb.gtk.model.Summary(name="failed")
|
||||
skipped = xfstestsdb.gtk.model.Summary(name="skipped")
|
||||
time = xfstestsdb.gtk.model.Summary(name="time")
|
||||
expected = [self.summary, failed, skipped, time]
|
||||
|
||||
for i in range(10):
|
||||
with self.subTest(i=i):
|
||||
shuffled = [time, skipped, failed, self.summary]
|
||||
random.shuffle(shuffled)
|
||||
self.assertListEqual(sorted(shuffled), expected)
|
||||
|
||||
def test_xunits(self):
|
||||
"""Test adding xunits to a Summary."""
|
||||
self.assertIsNone(self.summary["xunit-1"])
|
||||
|
||||
self.summary.add_xunit("xunit-1", 123, "unit")
|
||||
xunit = self.summary["xunit-1"]
|
||||
self.assertIsInstance(xunit, xfstestsdb.gtk.model.SummaryValue)
|
||||
self.assertEqual(xunit.name, "xunit-1")
|
||||
self.assertEqual(xunit.value, 123)
|
||||
self.assertEqual(xunit.unit, "unit")
|
||||
|
||||
|
||||
class TestSummaryList(unittest.TestCase):
|
||||
"""Test case for our summary list."""
|
||||
|
||||
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.summary = xfstestsdb.gtk.model.SummaryList(self.xfstestsdb.sql, 1)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the SummaryList was set up properly."""
|
||||
self.assertIsInstance(self.summary, xfstestsdb.gtk.model.XunitList)
|
||||
self.assertEqual(self.summary.runid, 1)
|
||||
self.assertEqual(self.summary.n_items, 4)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
for i, field in enumerate(["passed", "failed", "skipped", "time"]):
|
||||
with self.subTest(i=i, field=field):
|
||||
summary = self.summary[i]
|
||||
self.assertIsInstance(summary, xfstestsdb.gtk.model.Summary)
|
||||
self.assertEqual(summary.name, field)
|
||||
|
||||
match field:
|
||||
case "passed": expected = {"6 testcases"}
|
||||
case "failed": expected = {"1 testcase"}
|
||||
case "skipped": expected = {"3 testcases"}
|
||||
case "time": expected = {"43 seconds"}
|
||||
self.assertSetEqual(summary.get_results(), expected)
|
442
tests/gtk/test_row.py
Normal file
442
tests/gtk/test_row.py
Normal file
|
@ -0,0 +1,442 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our row widgets and factories."""
|
||||
import datetime
|
||||
import unittest
|
||||
import xfstestsdb.gtk.row
|
||||
import tests.xunit
|
||||
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."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.testcase = xfstestsdb.gtk.model.TestCase("test/case")
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.testcase)
|
||||
|
||||
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, xfstestsdb.gtk.row.Factory)
|
||||
self.assertEqual(self.factory.property, "name")
|
||||
|
||||
def test_size_group(self):
|
||||
"""Test the label factory global size group."""
|
||||
self.assertIsInstance(xfstestsdb.gtk.row.LabelFactory.group,
|
||||
Gtk.SizeGroup)
|
||||
self.assertEqual(xfstestsdb.gtk.row.LabelFactory.group.props.mode,
|
||||
Gtk.SizeGroupMode.HORIZONTAL)
|
||||
|
||||
def test_setup(self):
|
||||
"""Test that the factory implements the 'setup' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.assertIn(self.listitem.get_child(), self.group.get_widgets())
|
||||
|
||||
def test_bind(self):
|
||||
"""Test that the factory implements the 'bind' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_text(), "test/case")
|
||||
for style in xfstestsdb.gtk.row.STYLES.keys():
|
||||
self.assertFalse(self.listitem.get_child().has_css_class(style))
|
||||
|
||||
def test_unbind(self):
|
||||
"""Test that the factory implements the 'unbind' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.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.assertNotIn(child, self.group.get_widgets())
|
||||
|
||||
def test_styles(self):
|
||||
"""Test the column text styles."""
|
||||
self.assertDictEqual(xfstestsdb.gtk.row.STYLES,
|
||||
{"passed": "success", "failed": "error",
|
||||
"skipped": "warning", "time": "accent"})
|
||||
for style in ["passed", "failed", "skipped", "time"]:
|
||||
with self.subTest(style=style):
|
||||
self.testcase.name = style
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
|
||||
child = self.listitem.get_child()
|
||||
expected = xfstestsdb.gtk.row.STYLES[style]
|
||||
self.assertTrue(child.has_css_class(expected))
|
||||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
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."""
|
||||
|
||||
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="xunit-1")
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the factory was initialized correctly."""
|
||||
self.assertIsInstance(self.factory, xfstestsdb.gtk.row.XunitFactory)
|
||||
|
||||
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)
|
||||
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."""
|
||||
|
||||
def setup_parent(self, factory: xfstestsdb.gtk.row.ResultFactory,
|
||||
listitem: Gtk.ListItem) -> None:
|
||||
"""Set the child widget's parent for custom styling."""
|
||||
self.parent.set_child(listitem.get_child())
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.testcase = xfstestsdb.gtk.model.TestCase("test/case")
|
||||
self.parent = Adw.Bin()
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.testcase)
|
||||
|
||||
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.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(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(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", "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(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."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertIsNone(self.listitem.get_child().get_text())
|
||||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
|
||||
|
||||
class TestSummaryFactory(unittest.TestCase):
|
||||
"""Tests our Gtk.Factory to show Xfstests results summaries."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.summary = xfstestsdb.gtk.model.Summary("passed")
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.summary)
|
||||
|
||||
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.XunitFactory)
|
||||
|
||||
def test_bind_passed(self):
|
||||
"""Test binding to the passed tests summary."""
|
||||
self.summary.add_xunit("xunit-1", 1, "testcase")
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_text(), "1 testcase")
|
||||
self.assertTrue(self.listitem.get_child().has_css_class("success"))
|
||||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertFalse(self.listitem.get_child().has_css_class("success"))
|
||||
|
||||
def test_bind_failed(self):
|
||||
"""Test binding to the failed tests summary."""
|
||||
self.summary.name = "failed"
|
||||
self.summary.add_xunit("xunit-1", 1, "testcase")
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_text(), "1 testcase")
|
||||
self.assertTrue(self.listitem.get_child().has_css_class("error"))
|
||||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertFalse(self.listitem.get_child().has_css_class("error"))
|
||||
|
||||
def test_bind_skipped(self):
|
||||
"""Test binding to the skipped tests summary."""
|
||||
self.summary.name = "skipped"
|
||||
self.summary.add_xunit("xunit-1", 1, "testcase")
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_text(), "1 testcase")
|
||||
self.assertTrue(self.listitem.get_child().has_css_class("warning"))
|
||||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertFalse(self.listitem.get_child().has_css_class("warning"))
|
||||
|
||||
def test_bind_time(self):
|
||||
"""Test binding to the time summary."""
|
||||
self.summary.name = "time"
|
||||
self.summary.add_xunit("xunit-1", 1, "second")
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_text(), "1 second")
|
||||
self.assertTrue(self.listitem.get_child().has_css_class("accent"))
|
||||
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertFalse(self.listitem.get_child().has_css_class("accent"))
|
||||
|
||||
|
||||
class TestSidebarFactory(unittest.TestCase):
|
||||
"""Tests our Gtk.Factory to show Xfstsets runs in the sidebar."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.devlist = xfstestsdb.gtk.tree.DeviceRunsList("/dev/vda1")
|
||||
self.treeitem = Gtk.TreeListRow()
|
||||
self.treeitem.get_item = unittest.mock.Mock(return_value=self.devlist)
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.treeitem)
|
||||
|
||||
self.factory = xfstestsdb.gtk.row.SidebarFactory()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the factory was initialized correctly."""
|
||||
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
|
||||
|
||||
def test_setup(self):
|
||||
"""Test that thefactory implements the 'setup' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
expander = self.listitem.get_child()
|
||||
self.assertIsInstance(expander, Gtk.TreeExpander)
|
||||
self.assertIsInstance(expander.get_child(), Gtk.Label)
|
||||
self.assertEqual(expander.get_child().props.yalign, 0.75)
|
||||
self.assertTrue(expander.get_child().has_css_class("numeric"))
|
||||
|
||||
def test_bind_device_list(self):
|
||||
"""Test binding to a DeviceRunsList object."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_child().get_text(),
|
||||
"/dev/vda1")
|
||||
self.assertEqual(self.listitem.get_child().get_list_row(),
|
||||
self.treeitem)
|
||||
self.assertFalse(self.listitem.props.selectable)
|
||||
|
||||
def test_bind_xfstests_run(self):
|
||||
"""Test binding to an XfstestsRun object."""
|
||||
now = datetime.datetime.now()
|
||||
self.devlist.add_run(1, now)
|
||||
self.treeitem.get_item.return_value = self.devlist[0]
|
||||
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_child().get_text(),
|
||||
f"#1: {now.strftime('%T')}")
|
||||
self.assertEqual(self.listitem.get_child().get_list_row(),
|
||||
self.treeitem)
|
||||
self.assertTrue(self.listitem.props.selectable)
|
||||
|
||||
def test_unbind(self):
|
||||
"""Test that the factory implements the 'unbind' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.assertEqual(self.listitem.get_child().get_child().get_text(), "")
|
||||
|
||||
def test_teardown(self):
|
||||
"""Test that the factory implements the 'teardown' signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("teardown", self.listitem)
|
||||
self.assertIsNone(self.listitem.get_child())
|
246
tests/gtk/test_sidebar.py
Normal file
246
tests/gtk/test_sidebar.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our sidebar test selector."""
|
||||
import datetime
|
||||
import unittest
|
||||
import xfstestsdb.gtk.sidebar
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestRunidView(unittest.TestCase):
|
||||
"""Test the RunidView class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
|
||||
self.today = datetime.date.today()
|
||||
self.devlist = xfstestsdb.gtk.tree.DateDeviceList(self.xfstestsdb.sql,
|
||||
self.today)
|
||||
self.view = xfstestsdb.gtk.sidebar.RunidView(self.devlist)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the RunidView was set up properly."""
|
||||
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
|
||||
self.assertEqual(self.view.props.vexpand, True)
|
||||
|
||||
def test_listview(self):
|
||||
"""Test that the listview child is set up properly."""
|
||||
self.assertIsInstance(self.view._view, Gtk.ListView)
|
||||
self.assertIsInstance(self.view._selection, Gtk.SingleSelection)
|
||||
self.assertIsInstance(self.view._view.props.factory,
|
||||
xfstestsdb.gtk.row.SidebarFactory)
|
||||
|
||||
self.assertTrue(self.view._view.props.single_click_activate)
|
||||
self.assertTrue(self.view._view.has_css_class("navigation-sidebar"))
|
||||
self.assertTrue(self.view._view.has_css_class("background"))
|
||||
|
||||
self.assertEqual(self.view._view.props.model,
|
||||
self.view._selection)
|
||||
self.assertEqual(self.view.props.child, self.view._view)
|
||||
|
||||
def test_model_property(self):
|
||||
"""Test the model property."""
|
||||
self.assertEqual(self.view.model, self.devlist)
|
||||
self.assertEqual(self.view._selection.props.model,
|
||||
self.devlist.treemodel)
|
||||
|
||||
self.view.model = None
|
||||
self.assertIsNone(self.view._selection.props.model)
|
||||
|
||||
self.view.model = self.devlist
|
||||
self.assertEqual(self.view._selection.props.model,
|
||||
self.devlist.treemodel)
|
||||
|
||||
def test_runid_property(self):
|
||||
"""Test the runid property."""
|
||||
self.view.model = self.devlist
|
||||
self.assertEqual(self.view.runid, 0)
|
||||
|
||||
self.view._view.emit("activate", 1)
|
||||
self.assertEqual(self.view.runid, 2)
|
||||
self.view._view.emit("activate", 2)
|
||||
self.assertEqual(self.view.runid, 3)
|
||||
self.view._view.emit("activate", 4)
|
||||
self.assertEqual(self.view.runid, 1)
|
||||
self.view._view.emit("activate", 6)
|
||||
self.assertEqual(self.view.runid, 4)
|
||||
|
||||
def test_expand_collapse(self):
|
||||
"""Test expanding and collapsing child rows."""
|
||||
self.view.model = self.devlist
|
||||
|
||||
self.assertTrue(self.view._selection[0].get_expanded())
|
||||
self.assertEqual(self.view.runid, 0)
|
||||
|
||||
self.view._view.emit("activate", 0)
|
||||
self.assertFalse(self.view._selection[0].get_expanded())
|
||||
self.assertEqual(self.view.runid, 0)
|
||||
|
||||
self.view._view.emit("activate", 0)
|
||||
self.assertTrue(self.view._selection[0].get_expanded())
|
||||
self.assertEqual(self.view.runid, 2)
|
||||
|
||||
|
||||
class TestCalendarView(unittest.TestCase):
|
||||
"""Test the CalendarView class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda4"])
|
||||
|
||||
query = "UPDATE xfstests_runs SET timestamp=? WHERE runid=?"
|
||||
dtime = datetime.datetime.now().replace(year=2023, month=1)
|
||||
self.xfstestsdb.sql(query, dtime.replace(day=1), 1)
|
||||
self.xfstestsdb.sql(query, dtime.replace(day=8), 2)
|
||||
self.xfstestsdb.sql(query, dtime.replace(day=15), 3)
|
||||
|
||||
self.sidebar = xfstestsdb.gtk.sidebar.CalendarView(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the calendar view was set up properly."""
|
||||
self.assertIsInstance(self.sidebar, Gtk.Box)
|
||||
self.assertEqual(self.sidebar.props.spacing, 6)
|
||||
self.assertEqual(self.sidebar.props.orientation,
|
||||
Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(self.sidebar.sql, self.xfstestsdb.sql)
|
||||
|
||||
def test_calendar(self):
|
||||
"""Test the calendar widget."""
|
||||
self.assertIsInstance(self.sidebar._calendar, Gtk.Calendar)
|
||||
self.assertEqual(self.sidebar.get_first_child(),
|
||||
self.sidebar._calendar)
|
||||
|
||||
today = datetime.date.today()
|
||||
self.assertTrue(self.sidebar._calendar.get_day_is_marked(today.day))
|
||||
|
||||
def test_view(self):
|
||||
"""Test the runid view widget."""
|
||||
self.assertIsInstance(self.sidebar._view,
|
||||
xfstestsdb.gtk.sidebar.RunidView)
|
||||
self.assertIsInstance(self.sidebar._view.model,
|
||||
xfstestsdb.gtk.tree.DateDeviceList)
|
||||
self.assertEqual(self.sidebar._calendar.get_next_sibling(),
|
||||
self.sidebar._view)
|
||||
|
||||
self.assertEqual(self.sidebar._view.model.date, datetime.date.today())
|
||||
|
||||
def test_marked_days(self):
|
||||
"""Test marking days in the calendar."""
|
||||
gl_date = GLib.DateTime.new_local(2023, 1, 10, 0, 0, 0)
|
||||
self.sidebar._calendar.select_day(gl_date)
|
||||
|
||||
for signal in ["next-month", "next-year", "prev-month", "prev-year"]:
|
||||
with self.subTest(signal=signal):
|
||||
with unittest.mock.patch.object(self.sidebar._calendar,
|
||||
"clear_marks") as mock_clear:
|
||||
with unittest.mock.patch.object(self.sidebar._calendar,
|
||||
"mark_day") as mock_mark:
|
||||
self.sidebar._calendar.emit(signal)
|
||||
mock_clear.assert_called()
|
||||
mock_mark.assert_has_calls([unittest.mock.call(1),
|
||||
unittest.mock.call(8),
|
||||
unittest.mock.call(15)])
|
||||
|
||||
def test_select_day(self):
|
||||
"""Test selecting a day in the calendar."""
|
||||
gl_now = GLib.DateTime.new_now_local()
|
||||
self.sidebar._calendar.select_day(gl_now.add_days(-1))
|
||||
|
||||
today = datetime.date.today()
|
||||
self.assertEqual(self.sidebar._view.model.date,
|
||||
today - datetime.timedelta(days=1))
|
||||
|
||||
def test_runid_property(self):
|
||||
"""Test the runid property."""
|
||||
self.assertEqual(self.sidebar.runid, 0)
|
||||
self.sidebar._view.runid = 42
|
||||
self.assertEqual(self.sidebar.runid, 42)
|
||||
|
||||
|
||||
class TestSidebar(unittest.TestCase):
|
||||
"""Test the Sidebar class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
self.sidebar = xfstestsdb.gtk.sidebar.Sidebar(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test taht the sidebar was set up properly."""
|
||||
self.assertIsInstance(self.sidebar, Gtk.Box)
|
||||
self.assertEqual(self.sidebar.props.orientation,
|
||||
Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(self.sidebar.sql, self.xfstestsdb.sql)
|
||||
|
||||
def test_stack_switcher(self):
|
||||
"""Test the Gtk.StackSwitcher."""
|
||||
self.assertIsInstance(self.sidebar._switcher, Gtk.StackSwitcher)
|
||||
self.assertEqual(self.sidebar._switcher.props.stack,
|
||||
self.sidebar._stack)
|
||||
self.assertTrue(self.sidebar._switcher.has_css_class("large-icons"))
|
||||
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_top, 6)
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_bottom, 6)
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_start, 80)
|
||||
self.assertEqual(self.sidebar._switcher.props.margin_end, 80)
|
||||
|
||||
self.assertEqual(self.sidebar.get_first_child(),
|
||||
self.sidebar._switcher)
|
||||
|
||||
def test_separator(self):
|
||||
"""Test the Gtk.Separator between sidebar widgets."""
|
||||
sep = self.sidebar._switcher.get_next_sibling()
|
||||
self.assertIsInstance(sep, Gtk.Separator)
|
||||
self.assertEqual(sep.props.orientation, Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(sep.get_next_sibling(), self.sidebar._stack)
|
||||
|
||||
def test_stack(self):
|
||||
"""Test the Gtk.Stack."""
|
||||
self.assertIsInstance(self.sidebar._stack, Gtk.Stack)
|
||||
self.assertEqual(self.sidebar._stack.props.transition_type,
|
||||
Gtk.StackTransitionType.OVER_LEFT_RIGHT)
|
||||
self.assertEqual(self.sidebar.get_last_child(), self.sidebar._stack)
|
||||
|
||||
def test_calendar_page(self):
|
||||
"""Test the Sidebar calendar view page."""
|
||||
self.assertIsInstance(self.sidebar._calendar,
|
||||
xfstestsdb.gtk.sidebar.CalendarView)
|
||||
self.assertEqual(self.sidebar._calendar.sql, self.xfstestsdb.sql)
|
||||
|
||||
self.assertEqual(self.sidebar._stack.get_child_by_name("calendar"),
|
||||
self.sidebar._calendar)
|
||||
page = self.sidebar._stack.get_page(self.sidebar._calendar)
|
||||
self.assertEqual(page.props.title, "Calendar")
|
||||
self.assertEqual(page.props.icon_name, "month-symbolic")
|
||||
|
||||
def test_tag_page(self):
|
||||
"""Test the Sidebar tag view page."""
|
||||
self.assertIsInstance(self.sidebar._tags,
|
||||
xfstestsdb.gtk.sidebar.RunidView)
|
||||
|
||||
self.assertEqual(self.sidebar._stack.get_child_by_name("tags"),
|
||||
self.sidebar._tags)
|
||||
page = self.sidebar._stack.get_page(self.sidebar._tags)
|
||||
self.assertEqual(page.props.title, "Tags")
|
||||
self.assertEqual(page.props.icon_name, "tag-symbolic")
|
||||
|
||||
def test_runid(self):
|
||||
"""Test the runid property."""
|
||||
self.assertEqual(self.sidebar.runid, 0)
|
||||
|
||||
self.sidebar._calendar.runid = 1
|
||||
self.assertEqual(self.sidebar.runid, 1)
|
||||
|
||||
self.sidebar._tags.runid = 2
|
||||
self.assertEqual(self.sidebar.runid, 2)
|
330
tests/gtk/test_tree.py
Normal file
330
tests/gtk/test_tree.py
Normal file
|
@ -0,0 +1,330 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our xfstests run selector tree."""
|
||||
import datetime
|
||||
import unittest
|
||||
import xfstestsdb.gtk.tree
|
||||
import tests.xunit
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestXfstestsRun(unittest.TestCase):
|
||||
"""Test case for our XfstestsRun GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.now = datetime.datetime.now()
|
||||
self.run = xfstestsdb.gtk.tree.XfstestsRun(1, self.now)
|
||||
|
||||
def test_run(self):
|
||||
"""Test the XfsetstsRun object."""
|
||||
self.assertIsInstance(self.run, GObject.GObject)
|
||||
self.assertEqual(self.run.runid, 1)
|
||||
self.assertEqual(self.run.timestamp, self.now)
|
||||
|
||||
def test_gt(self):
|
||||
"""Test the XfstestsRun Greater-Than operator."""
|
||||
soon = self.now + datetime.timedelta(seconds=5)
|
||||
run2 = xfstestsdb.gtk.tree.XfstestsRun(2, soon)
|
||||
run3 = xfstestsdb.gtk.tree.XfstestsRun(3, soon)
|
||||
|
||||
self.assertTrue(run2 > self.run)
|
||||
self.assertFalse(self.run > run2)
|
||||
self.assertFalse(self.run > self.run)
|
||||
|
||||
self.assertTrue(run3 > run2)
|
||||
self.assertFalse(run2 > run3)
|
||||
|
||||
def test_lt(self):
|
||||
"""Test the XfstestsRun Less-Than operator."""
|
||||
soon = self.now + datetime.timedelta(seconds=5)
|
||||
run2 = xfstestsdb.gtk.tree.XfstestsRun(2, soon)
|
||||
run3 = xfstestsdb.gtk.tree.XfstestsRun(3, soon)
|
||||
|
||||
self.assertTrue(self.run < run2)
|
||||
self.assertFalse(run2 < self.run)
|
||||
self.assertFalse(self.run < self.run)
|
||||
|
||||
self.assertTrue(run2 < run3)
|
||||
self.assertFalse(run3 < run2)
|
||||
|
||||
def test_str(self):
|
||||
"""Test converting an XfstestsRun to a string."""
|
||||
self.assertEqual(self.run.ftime, "%T")
|
||||
self.assertEqual(str(self.run), f"#1: {self.now.strftime('%T')}")
|
||||
|
||||
self.run.ftime = "%c"
|
||||
self.assertEqual(str(self.run), f"#1: {self.now.strftime('%c')}")
|
||||
|
||||
|
||||
class TestDeviceRunModel(unittest.TestCase):
|
||||
"""Test case for our DeviceRow GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.device = xfstestsdb.gtk.tree.DeviceRunsList(name="/dev/vda1")
|
||||
|
||||
def test_init(self):
|
||||
"""Test creating a DeviceRow instance."""
|
||||
self.assertIsInstance(self.device, GObject.GObject)
|
||||
self.assertIsInstance(self.device, Gio.ListModel)
|
||||
self.assertEqual(self.device.name, "/dev/vda1")
|
||||
self.assertEqual(str(self.device), "/dev/vda1")
|
||||
|
||||
def test_lt(self):
|
||||
"""Test comparing DeviceRow instances."""
|
||||
dev_a = xfstestsdb.gtk.tree.DeviceRunsList(name="a")
|
||||
dev_b = xfstestsdb.gtk.tree.DeviceRunsList(name="b")
|
||||
self.assertTrue(dev_a < dev_b)
|
||||
self.assertFalse(dev_b < dev_a)
|
||||
self.assertFalse(dev_a < dev_a)
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.device.get_item_type(),
|
||||
xfstestsdb.gtk.tree.XfstestsRun.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.device.get_n_items(), 0)
|
||||
self.assertEqual(self.device.n_items, 0)
|
||||
|
||||
self.device.add_run(1, datetime.datetime.now())
|
||||
self.assertEqual(self.device.get_n_items(), 1)
|
||||
self.assertEqual(self.device.n_items, 1)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
now = datetime.datetime.now()
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
|
||||
self.device.add_run(1, now)
|
||||
self.assertIsInstance(self.device[0], xfstestsdb.gtk.tree.XfstestsRun)
|
||||
self.assertEqual(self.device[0].runid, 1)
|
||||
self.assertEqual(self.device[0].timestamp, now)
|
||||
|
||||
self.device.add_run(2, then, "%c")
|
||||
self.assertEqual(self.device[0].runid, 2)
|
||||
self.assertEqual(self.device[0].ftime, "%c")
|
||||
self.assertEqual(self.device[1].runid, 1)
|
||||
|
||||
def test_get_earliest_run(self):
|
||||
"""Test the get_earliest_run() function."""
|
||||
self.assertIsNone(self.device.get_earliest_run())
|
||||
|
||||
now = datetime.datetime.now()
|
||||
self.device.add_run(1, now)
|
||||
self.assertEqual(self.device.get_earliest_run().runid, 1)
|
||||
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
self.device.add_run(2, then)
|
||||
self.assertEqual(self.device.get_earliest_run().runid, 2)
|
||||
|
||||
|
||||
class TestDateDeviceList(unittest.TestCase):
|
||||
"""Test case for our listmodel of test devices on a specific day."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
|
||||
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-1",
|
||||
"1", str(tests.xunit.XUNIT_1)])
|
||||
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-2",
|
||||
"1", str(tests.xunit.XUNIT_1)])
|
||||
|
||||
yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
self.xfstestsdb.sql("""UPDATE xfstests_runs SET timestamp=?
|
||||
WHERE device=?""", yesterday, "/dev/vda3")
|
||||
|
||||
self.today = datetime.date.today()
|
||||
self.devlist = xfstestsdb.gtk.tree.DateDeviceList(self.xfstestsdb.sql,
|
||||
self.today)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the DateDeviceList was set up properly."""
|
||||
self.assertIsInstance(self.devlist, GObject.GObject)
|
||||
self.assertIsInstance(self.devlist, Gio.ListModel)
|
||||
self.assertEqual(self.devlist.date, datetime.date.today())
|
||||
|
||||
def test_get_range(self):
|
||||
"""Test finding the date range for the displayed entries."""
|
||||
min = datetime.datetime.combine(self.today, datetime.time())
|
||||
max = min + datetime.timedelta(days=1)
|
||||
self.assertTupleEqual(self.devlist.get_range(self.today), (min, max))
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.devlist.get_item_type(),
|
||||
xfstestsdb.gtk.tree.DeviceRunsList.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.devlist.get_n_items(), 2)
|
||||
self.assertEqual(self.devlist.n_items, 2)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
self.assertIsInstance(self.devlist.get_item(0),
|
||||
xfstestsdb.gtk.tree.DeviceRunsList)
|
||||
self.assertIsInstance(self.devlist.get_item(0).get_item(0).timestamp,
|
||||
datetime.datetime)
|
||||
self.assertEqual(self.devlist.get_item(0).name, "/dev/vda1")
|
||||
self.assertEqual(self.devlist.get_item(1).name, "/dev/vda2")
|
||||
|
||||
def test_treemodel(self):
|
||||
"""Test the treemodel property."""
|
||||
self.assertIsInstance(self.devlist.treemodel, Gtk.TreeListModel)
|
||||
self.assertFalse(self.devlist.treemodel.props.passthrough)
|
||||
self.assertTrue(self.devlist.treemodel.props.autoexpand)
|
||||
|
||||
tree = self.devlist.treemodel
|
||||
self.assertEqual(tree[0].get_item().name, "/dev/vda1")
|
||||
self.assertEqual(tree[1].get_item().runid, 2)
|
||||
self.assertEqual(tree[2].get_item().runid, 3)
|
||||
self.assertEqual(tree[3].get_item().name, "/dev/vda2")
|
||||
self.assertEqual(tree[4].get_item().runid, 1)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
self.assertIsNone(tree[5])
|
||||
|
||||
|
||||
class TestTagDeviceList(unittest.TestCase):
|
||||
"""Test case for our TagDeviceList GObject."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.tag = xfstestsdb.gtk.tree.TagDeviceList(name="mytag")
|
||||
|
||||
def test_init(self):
|
||||
"""Test creating a TagDeviceList instance."""
|
||||
self.assertIsInstance(self.tag, GObject.GObject)
|
||||
self.assertIsInstance(self.tag, Gio.ListModel)
|
||||
self.assertEqual(self.tag.name, "mytag")
|
||||
self.assertEqual(str(self.tag), "mytag")
|
||||
|
||||
def test_lt(self):
|
||||
"""Test comparing TagDeviceList instances."""
|
||||
tag_a = xfstestsdb.gtk.tree.TagDeviceList(name="a")
|
||||
tag_b = xfstestsdb.gtk.tree.TagDeviceList(name="b")
|
||||
|
||||
now = datetime.datetime.now()
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
tag_a.add_run(1, "/dev/vda1", now)
|
||||
tag_b.add_run(2, "/dev/vda2", then)
|
||||
|
||||
self.assertTrue(tag_a < tag_b)
|
||||
self.assertFalse(tag_b < tag_a)
|
||||
self.assertFalse(tag_a < tag_a)
|
||||
|
||||
tag_a.add_run(2, "/dev/vda2", then)
|
||||
self.assertTrue(tag_b < tag_a)
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.tag.get_item_type(),
|
||||
xfstestsdb.gtk.tree.DeviceRunsList.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.tag.get_n_items(), 0)
|
||||
self.assertEqual(self.tag.n_items, 0)
|
||||
|
||||
self.tag.add_run(1, "/dev/vda1", datetime.datetime.now())
|
||||
self.assertEqual(self.tag.get_n_items(), 1)
|
||||
self.assertEqual(self.tag.n_items, 1)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
now = datetime.datetime.now()
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
|
||||
self.tag.add_run(1, "/dev/vda2", now)
|
||||
self.assertIsInstance(self.tag[0], xfstestsdb.gtk.tree.DeviceRunsList)
|
||||
self.assertEqual(self.tag[0].name, "/dev/vda2")
|
||||
self.assertEqual(self.tag[0][0].runid, 1)
|
||||
self.assertEqual(self.tag[0][0].timestamp, now)
|
||||
self.assertEqual(self.tag[0][0].ftime, "%c")
|
||||
|
||||
self.tag.add_run(2, "/dev/vda1", then)
|
||||
self.assertEqual(self.tag[0].name, "/dev/vda1")
|
||||
self.assertEqual(self.tag[1].name, "/dev/vda2")
|
||||
|
||||
def test_get_earliest_run(self):
|
||||
"""Test the get_earliest_run() function."""
|
||||
self.assertIsNone(self.tag.get_earliest_run())
|
||||
|
||||
now = datetime.datetime.now()
|
||||
self.tag.add_run(1, "/dev/vda1", now)
|
||||
self.assertEqual(self.tag.get_earliest_run().runid, 1)
|
||||
|
||||
then = now - datetime.timedelta(seconds=42)
|
||||
self.tag.add_run(2, "/dev/vda2", then)
|
||||
self.assertEqual(self.tag.get_earliest_run().runid, 2)
|
||||
|
||||
|
||||
class TestTagList(unittest.TestCase):
|
||||
"""Test case for our TagList ListModel."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda2"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda3"])
|
||||
|
||||
self.xfstestsdb.run(["tag", "1", "mytag2"])
|
||||
self.xfstestsdb.run(["tag", "2", "mytag2"])
|
||||
self.xfstestsdb.run(["tag", "4", "mytag1"])
|
||||
|
||||
self.taglist = xfstestsdb.gtk.tree.TagList(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the TagList was set up properly."""
|
||||
self.assertIsInstance(self.taglist, GObject.GObject)
|
||||
self.assertIsInstance(self.taglist, Gio.ListModel)
|
||||
|
||||
def test_get_item_type(self):
|
||||
"""Test the get_item_type() function."""
|
||||
self.assertEqual(self.taglist.get_item_type(),
|
||||
xfstestsdb.gtk.tree.TagDeviceList.__gtype__)
|
||||
|
||||
def test_get_n_items(self):
|
||||
"""Test the get_n_items() function."""
|
||||
self.assertEqual(self.taglist.get_n_items(), 2)
|
||||
self.assertEqual(self.taglist.n_items, 2)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test the get_item() function."""
|
||||
self.assertIsInstance(self.taglist.get_item(0),
|
||||
xfstestsdb.gtk.tree.TagDeviceList)
|
||||
self.assertIsInstance(self.taglist[0][0][0].timestamp,
|
||||
datetime.datetime)
|
||||
self.assertEqual(self.taglist.get_item(0).name, "mytag1")
|
||||
self.assertEqual(self.taglist.get_item(1).name, "mytag2")
|
||||
|
||||
def test_treemodel(self):
|
||||
"""Test the treemodel property."""
|
||||
self.assertIsInstance(self.taglist.treemodel, Gtk.TreeListModel)
|
||||
self.assertFalse(self.taglist.treemodel.props.passthrough)
|
||||
self.assertFalse(self.taglist.treemodel.props.autoexpand)
|
||||
|
||||
tree = self.taglist.treemodel
|
||||
self.assertEqual(tree[0].get_item().name, "mytag1")
|
||||
tree[0].set_expanded(True)
|
||||
self.assertEqual(tree[1].get_item().name, "/dev/vda3")
|
||||
tree[1].set_expanded(True)
|
||||
self.assertEqual(tree[2].get_item().runid, 4)
|
||||
|
||||
self.assertEqual(tree[3].get_item().name, "mytag2")
|
||||
tree[3].set_expanded(True)
|
||||
self.assertEqual(tree[4].get_item().name, "/dev/vda1")
|
||||
self.assertEqual(tree[5].get_item().name, "/dev/vda2")
|
575
tests/gtk/test_view.py
Normal file
575
tests/gtk/test_view.py
Normal file
|
@ -0,0 +1,575 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our TestCaseView for displaying xfstests results."""
|
||||
import unittest
|
||||
import tests.xunit
|
||||
import xfstestsdb.gtk.view
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestXunitView(unittest.TestCase):
|
||||
"""Tests the XunitView base class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
with unittest.mock.patch("sys.stdout"):
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["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.XunitList(self.xfstestsdb.sql, 1)
|
||||
self.view = xfstestsdb.gtk.view.XunitView("title")
|
||||
|
||||
def test_init(self):
|
||||
"""Test that we created the XunitView correctly."""
|
||||
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
|
||||
self.assertTrue(self.view.has_css_class("undershoot-top"))
|
||||
self.assertTrue(self.view.has_css_class("undershoot-bottom"))
|
||||
self.assertTrue(self.view.has_css_class("card"))
|
||||
|
||||
def test_columnview(self):
|
||||
"""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)
|
||||
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())
|
||||
|
||||
def test_filtermodel(self):
|
||||
"""Test that we set up the Gtk.FilterModel correctly."""
|
||||
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)
|
||||
|
||||
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.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 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.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.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(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.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):
|
||||
"""Test case for our TestCaseView FilterButtons."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.buttons = xfstestsdb.gtk.view.FilterButtons()
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the buttons were created correctly."""
|
||||
self.assertIsInstance(self.buttons, Gtk.Box)
|
||||
self.assertTrue(self.buttons.has_css_class("linked"))
|
||||
|
||||
def test_passed_button(self):
|
||||
"""Test the 'passed' button."""
|
||||
self.assertIsInstance(self.buttons._passed,
|
||||
xfstestsdb.gtk.button.StatusToggle)
|
||||
self.assertEqual(self.buttons.get_first_child(), self.buttons._passed)
|
||||
|
||||
self.assertEqual(self.buttons._passed.props.icon_name, "test-pass")
|
||||
self.assertTrue(self.buttons._passed.has_css_class("passed"))
|
||||
self.assertFalse(self.buttons._passed.props.active)
|
||||
|
||||
self.assertFalse(self.buttons.passed)
|
||||
self.buttons._passed.props.active = True
|
||||
self.assertTrue(self.buttons.passed)
|
||||
|
||||
def test_skipped_button(self):
|
||||
"""Test the 'skipped' button."""
|
||||
self.assertIsInstance(self.buttons._skipped,
|
||||
xfstestsdb.gtk.button.StatusToggle)
|
||||
self.assertEqual(self.buttons._passed.get_next_sibling(),
|
||||
self.buttons._skipped)
|
||||
|
||||
self.assertEqual(self.buttons._skipped.props.icon_name, "test-skip")
|
||||
self.assertTrue(self.buttons._skipped.has_css_class("skipped"))
|
||||
self.assertFalse(self.buttons._skipped.props.active)
|
||||
|
||||
self.assertFalse(self.buttons.skipped)
|
||||
self.buttons._skipped.props.active = True
|
||||
self.assertTrue(self.buttons.skipped)
|
||||
|
||||
def test_failure_button(self):
|
||||
"""Test the 'failure' button."""
|
||||
self.assertIsInstance(self.buttons._failure,
|
||||
xfstestsdb.gtk.button.StatusToggle)
|
||||
self.assertEqual(self.buttons._skipped.get_next_sibling(),
|
||||
self.buttons._failure)
|
||||
|
||||
self.assertEqual(self.buttons._failure.props.icon_name, "test-fail")
|
||||
self.assertTrue(self.buttons._failure.has_css_class("failure"))
|
||||
self.assertTrue(self.buttons._failure.props.active)
|
||||
|
||||
self.assertTrue(self.buttons.failure)
|
||||
self.buttons._failure.props.active = False
|
||||
self.assertFalse(self.buttons.failure)
|
||||
|
||||
|
||||
class TestTestCaseView(unittest.TestCase):
|
||||
"""Tests the TestCaseView."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.view = xfstestsdb.gtk.view.TestCaseView()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that we create the TestCaseView correctly."""
|
||||
self.assertIsInstance(self.view, xfstestsdb.gtk.view.XunitView)
|
||||
self.assertIsInstance(self.view.filtermodel.props.filter,
|
||||
xfstestsdb.gtk.model.TestCaseFilter)
|
||||
self.assertIsNone(self.view.model)
|
||||
|
||||
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.filterbuttons,
|
||||
xfstestsdb.gtk.view.FilterButtons)
|
||||
|
||||
self.assertFalse(self.view.filtermodel.props.filter.passed)
|
||||
self.view.filterbuttons.passed = True
|
||||
self.assertTrue(self.view.filtermodel.props.filter.passed)
|
||||
|
||||
self.assertFalse(self.view.filtermodel.props.filter.skipped)
|
||||
self.view.filterbuttons.skipped = True
|
||||
self.assertTrue(self.view.filtermodel.props.filter.skipped)
|
||||
|
||||
self.assertTrue(self.view.filtermodel.props.filter.failure)
|
||||
self.view.filterbuttons.failure = False
|
||||
self.assertFalse(self.view.filtermodel.props.filter.failure)
|
||||
|
||||
def test_make_factory(self):
|
||||
"""Test the do_make_factory() implementation."""
|
||||
show_messages = unittest.mock.Mock()
|
||||
self.view.connect("show-messages", show_messages)
|
||||
|
||||
factory = self.view.do_make_factory("xunit-1")
|
||||
self.assertIsInstance(factory, xfstestsdb.gtk.row.ResultFactory)
|
||||
self.assertEqual(factory.xunit, "xunit-1")
|
||||
|
||||
factory.emit("show-messages", "testcase", "xunit", "stdout", "stderr")
|
||||
show_messages.assert_called_with(self.view, "testcase", "xunit",
|
||||
"stdout", "stderr")
|
||||
|
||||
|
||||
class TestMessageView(unittest.TestCase):
|
||||
"""Test the MessageView."""
|
||||
|
||||
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,
|
||||
"down-large-symbolic")
|
||||
self.assertEqual(self.view._back.props.child.props.label, "done")
|
||||
self.assertTrue(self.view._back.has_css_class("suggested-action"))
|
||||
self.assertTrue(self.view._back.has_css_class("pill"))
|
||||
|
||||
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):
|
||||
"""Tests the SummaryView."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.view = xfstestsdb.gtk.view.SummaryView()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that we created the SummaryView correctly."""
|
||||
self.assertIsInstance(self.view, xfstestsdb.gtk.view.XunitView)
|
||||
self.assertIsNone(self.view.filtermodel.props.filter)
|
||||
self.assertIsNone(self.view.model)
|
||||
|
||||
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_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):
|
||||
"""Test the XfstestsView."""
|
||||
|
||||
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 = xfstestsdb.gtk.model.TestCaseList(self.xfstestsdb.sql, 1)
|
||||
self.summary = xfstestsdb.gtk.model.SummaryList(self.xfstestsdb.sql, 1)
|
||||
self.view = xfstestsdb.gtk.view.XfstestsView()
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the XfstestsView was set up correctly."""
|
||||
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(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_UP_DOWN)
|
||||
self.assertEqual(sep.get_next_sibling(), self.view._stack)
|
||||
|
||||
def test_testcase_view(self):
|
||||
"""Check that the XfstestsView sets up a TestCaseView correctly."""
|
||||
self.assertIsInstance(self.view._testcaseview,
|
||||
xfstestsdb.gtk.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._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)
|
||||
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)
|
||||
self.view.model = self.model
|
||||
self.assertEqual(self.view._testcaseview.model, self.model)
|
||||
|
||||
def test_summary(self):
|
||||
"""Test the XfstestsView 'summary' property."""
|
||||
self.assertIsNone(self.view.summary)
|
||||
self.view.summary = self.summary
|
||||
self.assertEqual(self.view._summaryview.model, self.summary)
|
||||
|
||||
def test_filterbuttons(self):
|
||||
"""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")
|
106
tests/gtk/test_window.py
Normal file
106
tests/gtk/test_window.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our Window implementation."""
|
||||
import unittest
|
||||
import xfstestsdb.gtk.window
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestWindow(unittest.TestCase):
|
||||
"""Test case for our Window subclass."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.window = xfstestsdb.gtk.window.Window()
|
||||
|
||||
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, 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)
|
||||
self.assertEqual(self.window.props.content.get_orientation(),
|
||||
Gtk.Orientation.VERTICAL)
|
||||
|
||||
def test_headerbar(self):
|
||||
"""Test the headerbar property."""
|
||||
self.assertIsInstance(self.window.headerbar, Adw.HeaderBar)
|
||||
self.assertEqual(self.window.props.content.get_first_child(),
|
||||
self.window.headerbar)
|
||||
self.assertTrue(self.window.headerbar.has_css_class("flat"))
|
||||
|
||||
def test_title_widget(self):
|
||||
"""Test the title widget property."""
|
||||
self.assertIsInstance(self.window.title, Adw.WindowTitle)
|
||||
self.assertEqual(self.window.title.props.title, "xfstestsdb gtk")
|
||||
self.assertEqual(self.window.headerbar.props.title_widget,
|
||||
self.window.title)
|
||||
|
||||
def test_splitview(self):
|
||||
"""Check that the Window's splitview is set up correctly."""
|
||||
self.assertIsInstance(self.window._splitview, Adw.OverlaySplitView)
|
||||
self.assertEqual(self.window.headerbar.get_next_sibling(),
|
||||
self.window._splitview)
|
||||
self.assertTrue(self.window._splitview.props.collapsed)
|
||||
|
||||
def test_show_sidebar(self):
|
||||
"""Test the window show sidebar button."""
|
||||
self.assertIsInstance(self.window._show_sidebar, Gtk.ToggleButton)
|
||||
self.assertEqual(self.window._show_sidebar.get_ancestor(Adw.HeaderBar),
|
||||
self.window.headerbar)
|
||||
self.assertEqual(self.window._show_sidebar.props.icon_name,
|
||||
"sidebar-show")
|
||||
|
||||
self.assertFalse(self.window.show_sidebar)
|
||||
self.assertFalse(self.window._show_sidebar.props.active)
|
||||
self.assertFalse(self.window._splitview.props.show_sidebar)
|
||||
|
||||
self.window._show_sidebar.props.active = True
|
||||
self.assertTrue(self.window._splitview.props.show_sidebar)
|
||||
self.assertTrue(self.window.show_sidebar)
|
||||
|
||||
self.window.show_sidebar = False
|
||||
self.assertFalse(self.window._show_sidebar.props.active)
|
||||
self.assertFalse(self.window._splitview.props.show_sidebar)
|
||||
|
||||
window2 = xfstestsdb.gtk.window.Window(show_sidebar=True)
|
||||
self.assertTrue(window2.show_sidebar)
|
||||
self.assertTrue(window2._show_sidebar.props.active)
|
||||
self.assertTrue(window2._splitview.props.show_sidebar)
|
||||
|
||||
def test_content(self):
|
||||
"""Test the window content property."""
|
||||
self.assertIsNone(self.window.child)
|
||||
self.window.child = Gtk.Label()
|
||||
self.assertEqual(self.window._splitview.props.content,
|
||||
self.window.child)
|
||||
|
||||
label = Gtk.Label()
|
||||
window2 = xfstestsdb.gtk.window.Window(child=label)
|
||||
self.assertEqual(window2.child, label)
|
||||
self.assertEqual(window2._splitview.props.content, label)
|
||||
|
||||
def test_sidebar(self):
|
||||
"""Test the window sidebar property."""
|
||||
self.assertIsNone(self.window.sidebar)
|
||||
self.window.sidebar = Gtk.Label()
|
||||
self.assertEqual(self.window._splitview.props.sidebar,
|
||||
self.window.sidebar)
|
||||
|
||||
label = Gtk.Label()
|
||||
window2 = xfstestsdb.gtk.window.Window(sidebar=label)
|
||||
self.assertEqual(window2.sidebar, label)
|
||||
self.assertEqual(window2._splitview.props.sidebar, label)
|
||||
|
||||
def test_runid(self):
|
||||
"""Test the window runid property."""
|
||||
self.assertEqual(self.window.runid, 0)
|
||||
self.assertEqual(self.window.title.props.subtitle, "")
|
||||
|
||||
self.window.runid = 3
|
||||
self.assertEqual(self.window.title.props.subtitle, "runid #3")
|
||||
|
||||
self.window.runid = 0
|
||||
self.assertEqual(self.window.title.props.subtitle, "")
|
|
@ -24,9 +24,15 @@ class TestCommand(unittest.TestCase):
|
|||
self.assertEqual(self.command.parser.description, "description")
|
||||
self.assertEqual(self.command.parser._defaults["function"],
|
||||
self.command.do_command)
|
||||
self.assertEqual(self.command.parser._defaults["done"],
|
||||
self.command.do_done)
|
||||
self.assertEqual(self.command.sql, self.sql)
|
||||
|
||||
def test_do_command(self):
|
||||
"""Test the do_command() function."""
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.command.do_command(argparse.Namespace())
|
||||
|
||||
def test_do_done(self):
|
||||
"""Test the do_done() function."""
|
||||
self.command.do_done(argparse.Namespace())
|
||||
|
|
|
@ -8,6 +8,8 @@ import xfstestsdb.xunit.gc
|
|||
import tests.xunit
|
||||
|
||||
|
||||
@unittest.mock.patch("xfstestsdb.sqlite.Connection.vacuum")
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
class TestGC(unittest.TestCase):
|
||||
"""Tests the `xfstestsdb xunit gc` command."""
|
||||
|
||||
|
@ -31,24 +33,26 @@ class TestGC(unittest.TestCase):
|
|||
mock_stdout.seek(0)
|
||||
mock_stdout.truncate(0)
|
||||
|
||||
def test_init(self):
|
||||
def test_init(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Check that the gc command was set up properly."""
|
||||
self.assertIsInstance(self.gc, xfstestsdb.commands.Command)
|
||||
self.assertIsInstance(self.gc, xfstestsdb.xunit.gc.Command)
|
||||
self.assertEqual(self.xunit.subparser.choices["gc"],
|
||||
self.gc.parser)
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_empty(self, mock_stdout: io.StringIO):
|
||||
def test_gc_empty(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting an empty database."""
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_gc_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [])
|
||||
|
||||
self.xfstestsdb.run(["gc"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "")
|
||||
mock_vacuum.assert_not_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_no_testcases(self, mock_stdout: io.StringIO):
|
||||
def test_gc_no_testcases(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting runs with no testcases."""
|
||||
self.setup_runs(mock_stdout)
|
||||
|
||||
|
@ -61,9 +65,10 @@ class TestGC(unittest.TestCase):
|
|||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
|
||||
mock_vacuum.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_expired(self, mock_stdout: io.StringIO):
|
||||
def test_gc_expired(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting old runs."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
|
||||
|
@ -87,9 +92,10 @@ class TestGC(unittest.TestCase):
|
|||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [2])
|
||||
mock_vacuum.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_expired_tagged(self, mock_stdout: io.StringIO):
|
||||
def test_gc_expired_tagged(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test that we don't garbage collect expired runs that are tagged."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
|
||||
|
@ -109,9 +115,10 @@ class TestGC(unittest.TestCase):
|
|||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
|
||||
mock_vacuum.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_dry_run(self, mock_stdout: io.StringIO):
|
||||
def test_gc_dry_run(self, mock_stdout: io.StringIO,
|
||||
mock_vacuum: unittest.mock.Mock):
|
||||
"""Test garbage collecting with the --dry-run option."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["gc", "--dry-run"])
|
||||
|
@ -121,3 +128,4 @@ class TestGC(unittest.TestCase):
|
|||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()],
|
||||
[1, 2, 3])
|
||||
mock_vacuum.assert_not_called()
|
||||
|
|
200
tests/test_gtk.py
Normal file
200
tests/test_gtk.py
Normal file
|
@ -0,0 +1,200 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests the `xfstestsdb gtk` command."""
|
||||
import errno
|
||||
import io
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import xfstestsdb.gtk
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestApplication(unittest.TestCase):
|
||||
"""Tests the Gtk Application."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
self.application = xfstestsdb.gtk.Application(self.xfstestsdb.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the Gtk Application was set up properly."""
|
||||
self.assertIsInstance(self.application, Adw.Application)
|
||||
self.assertEqual(self.application.get_application_id(),
|
||||
xfstestsdb.gtk.gsetup.APPLICATION_ID)
|
||||
self.assertEqual(self.application.get_flags(),
|
||||
Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
|
||||
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)
|
||||
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.activate")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.do_command_line")
|
||||
def test_command_line(self, mock_command_line: unittest.mock.Mock,
|
||||
mock_activate: unittest.mock.Mock):
|
||||
"""Check that we handle the command-line signal."""
|
||||
mock_cmd = unittest.mock.Mock()
|
||||
mock_cmd.get_arguments.return_value = []
|
||||
|
||||
self.application.do_command_line(mock_cmd)
|
||||
mock_command_line.assert_called_with(self.application, mock_cmd)
|
||||
mock_cmd.get_arguments.assert_called()
|
||||
mock_activate.assert_called()
|
||||
self.assertEqual(self.application.runid, 0)
|
||||
self.assertFalse(self.application.show_sidebar)
|
||||
self.assertIsNone(self.application.environment)
|
||||
self.assertIsNone(self.application.properties)
|
||||
self.assertIsNone(self.application.model)
|
||||
self.assertIsNone(self.application.summary)
|
||||
|
||||
mock_command_line.reset_mock()
|
||||
mock_activate.reset_mock()
|
||||
mock_cmd.reset_mock()
|
||||
mock_cmd.get_arguments.return_value = ["show-sidebar"]
|
||||
|
||||
self.application.do_command_line(mock_cmd)
|
||||
self.assertTrue(self.application.show_sidebar)
|
||||
|
||||
self.application.show_sidebar = False
|
||||
mock_command_line.reset_mock()
|
||||
mock_activate.reset_mock()
|
||||
mock_cmd.reset_mock()
|
||||
mock_cmd.get_arguments.return_value = ["runid=42"]
|
||||
|
||||
self.application.do_command_line(mock_cmd)
|
||||
mock_command_line.assert_called_with(self.application, mock_cmd)
|
||||
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,
|
||||
xfstestsdb.gtk.model.SummaryList)
|
||||
self.assertEqual(self.application.environment,
|
||||
self.application.properties.environment)
|
||||
self.assertEqual(self.application.model.runid, 42)
|
||||
self.assertFalse(self.application.show_sidebar)
|
||||
|
||||
@unittest.mock.patch("xfstestsdb.gtk.gsetup.add_style")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.add_window")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.do_startup")
|
||||
def test_startup(self, mock_startup: unittest.mock.Mock,
|
||||
mock_add_window: unittest.mock.Mock,
|
||||
mock_add_style: unittest.mock.Mock):
|
||||
"""Check that startup sets up our application instance correctly."""
|
||||
self.assertIsNone(self.application.win)
|
||||
self.assertIsNone(self.application.sidebar)
|
||||
self.assertIsNone(self.application.view)
|
||||
|
||||
self.application.emit("startup")
|
||||
|
||||
self.assertIsInstance(self.application.sidebar,
|
||||
xfstestsdb.gtk.sidebar.Sidebar)
|
||||
self.assertIsInstance(self.application.view,
|
||||
xfstestsdb.gtk.view.XfstestsView)
|
||||
self.assertIsInstance(self.application.win,
|
||||
xfstestsdb.gtk.window.Window)
|
||||
self.assertEqual(self.application.win.child, self.application.view)
|
||||
self.assertEqual(self.application.win.sidebar,
|
||||
self.application.sidebar)
|
||||
self.assertEqual(self.application.sidebar.sql,
|
||||
self.application.sql)
|
||||
mock_startup.assert_called_with(self.application)
|
||||
mock_add_window.assert_called_with(self.application.win)
|
||||
mock_add_style.assert_called()
|
||||
|
||||
self.application.sidebar.runid = 42
|
||||
self.assertEqual(self.application.runid, 42)
|
||||
self.assertEqual(self.application.win.runid, 42)
|
||||
|
||||
self.application.show_sidebar = True
|
||||
self.assertTrue(self.application.win.show_sidebar)
|
||||
|
||||
properties = xfstestsdb.gtk.model.PropertyList(self.xfstestsdb.sql, 42)
|
||||
self.application.properties = properties
|
||||
self.assertEqual(self.application.view.properties, properties)
|
||||
|
||||
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)
|
||||
|
||||
summary = xfstestsdb.gtk.model.SummaryList(self.xfstestsdb.sql, 42)
|
||||
self.application.summary = summary
|
||||
self.assertEqual(self.application.view.summary, summary)
|
||||
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.add_window")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.do_startup")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.do_activate")
|
||||
def test_activate(self, mock_activate: unittest.mock.Mock,
|
||||
mock_startup: unittest.mock.Mock,
|
||||
mock_add_window: unittest.mock.Mock):
|
||||
"""Check that activating our application works correctly."""
|
||||
self.application.emit("startup")
|
||||
with unittest.mock.patch.object(self.application.win,
|
||||
"present") as mock_present:
|
||||
self.application.emit("activate")
|
||||
mock_activate.assert_called_with(self.application)
|
||||
mock_present.assert_called()
|
||||
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.do_shutdown")
|
||||
def test_shutdown(self, mock_shutdown: unittest.mock.Mock):
|
||||
"""Check that shutting down application cleans up global state."""
|
||||
self.application.emit("shutdown")
|
||||
mock_shutdown.assert_called_with(self.application)
|
||||
|
||||
|
||||
class TestGtk(unittest.TestCase):
|
||||
"""Tests the `xfstestsdb gtk` command."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
self.gtk = self.xfstestsdb.commands["gtk"]
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the gtk command was set up properly."""
|
||||
self.assertIsInstance(self.gtk, xfstestsdb.commands.Command)
|
||||
self.assertIsInstance(self.gtk, xfstestsdb.gtk.Command)
|
||||
self.assertEqual(self.xfstestsdb.subparser.choices["gtk"],
|
||||
self.gtk.parser)
|
||||
|
||||
@unittest.mock.patch("sys.stderr", new_callable=io.StringIO)
|
||||
@unittest.mock.patch("xfstestsdb.gtk.Application")
|
||||
def test_gtk_empty(self, mock_app: unittest.mock.Mock,
|
||||
mock_stderr: unittest.mock.Mock):
|
||||
"""Check that running `xfstestsdb gtk` without a runid."""
|
||||
self.xfstestsdb.run(["gtk"])
|
||||
mock_app.assert_called_with(self.xfstestsdb.sql)
|
||||
mock_app.return_value.run.assert_called_with(["show-sidebar"])
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
@unittest.mock.patch("xfstestsdb.gtk.Application")
|
||||
def test_gtk_runid(self, mock_app: unittest.mock.Mock,
|
||||
mock_stdout: io.StringIO):
|
||||
"""Check running `xfstestsdb gtk` with the --runid option."""
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["gtk", "1"])
|
||||
mock_app.assert_called_with(self.xfstestsdb.sql)
|
||||
mock_app.return_value.run.assert_called_with(["runid=1"])
|
||||
|
||||
@unittest.mock.patch("sys.stderr", new_callable=io.StringIO)
|
||||
@unittest.mock.patch("xfstestsdb.gtk.Application")
|
||||
def test_gtk_error(self, mock_app: unittest.mock.Mock,
|
||||
mock_stderr: io.StringIO):
|
||||
"""Check running the gtk command with an invalid runid."""
|
||||
with self.assertRaises(SystemExit) as sys_exit:
|
||||
self.xfstestsdb.run(["gtk", "2"])
|
||||
|
||||
mock_app.assert_not_called()
|
||||
self.assertEqual(sys_exit.exception.code, errno.ENOENT)
|
||||
self.assertEqual(mock_stderr.getvalue(),
|
||||
"error: run #2 does not exist\n")
|
|
@ -30,6 +30,7 @@ class TestList(unittest.TestCase):
|
|||
self.xfstestsdb.run(["tag", "1", "mytag1"])
|
||||
self.xfstestsdb.run(["tag", "1", "mytag2"])
|
||||
self.xfstestsdb.run(["tag", "3", "mytag3"])
|
||||
self.xfstestsdb.run(["tag", "3", "othertag"])
|
||||
|
||||
self.xfstestsdb.sql.executemany("""UPDATE xfstests_runs SET timestamp=?
|
||||
WHERE rowid=?""", (timestamp, 1),
|
||||
|
@ -116,13 +117,12 @@ class TestList(unittest.TestCase):
|
|||
self.xfstestsdb.run(["list", "--color", "none", "--tags"])
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"""
|
||||
+-----+---------------------+-----------+---------------+
|
||||
| run | timestamp | device | tags |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
| 1 | 2023-01-01 12:59:59 | /dev/vda1 | mytag1,mytag2 |
|
||||
| 2 | 2023-01-02 12:59:59 | /dev/vda2 | |
|
||||
| 3 | 2023-01-03 12:59:59 | /dev/vdb1 | mytag3 |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
+-----+---------------------+-----------+-----------------+
|
||||
| run | timestamp | device | tags |
|
||||
+-----+---------------------+-----------+-----------------+
|
||||
| 1 | 2023-01-01 12:59:59 | /dev/vda1 | mytag1,mytag2 |
|
||||
| 3 | 2023-01-03 12:59:59 | /dev/vdb1 | mytag3,othertag |
|
||||
+-----+---------------------+-----------+-----------------+
|
||||
""")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
|
@ -170,7 +170,6 @@ class TestList(unittest.TestCase):
|
|||
| run | timestamp | device | xunits |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
| 1 | 2023-01-01 12:59:59 | /dev/vda1 | test-1,test-2 |
|
||||
| 2 | 2023-01-02 12:59:59 | /dev/vda2 | |
|
||||
| 3 | 2023-01-03 12:59:59 | /dev/vdb1 | test-3 |
|
||||
+-----+---------------------+-----------+---------------+
|
||||
""")
|
||||
|
|
|
@ -29,6 +29,12 @@ class TestNew(unittest.TestCase):
|
|||
r"created run #1 with test device '/dev/vdb1' "
|
||||
r"\[[\d\-\: ]+\]\n")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_new_terse(self, mock_stdout: io.StringIO):
|
||||
"""Test running `xfstestsdb new --terse`."""
|
||||
self.xfstestsdb.run(["new", "--terse", "/dev/vdb1"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "1\n")
|
||||
|
||||
@unittest.mock.patch("sys.stderr", new_callable=io.StringIO)
|
||||
def test_new_error(self, mock_stderr: io.StringIO):
|
||||
"""Test running the `xfstestsdb new` command with invalid input."""
|
||||
|
|
|
@ -114,6 +114,10 @@ class TestConnection(unittest.TestCase):
|
|||
with self.assertRaises(sqlite3.OperationalError):
|
||||
self.sql("SELECT COUNT(*) FROM other_table")
|
||||
|
||||
def test_vacuum(self):
|
||||
"""Test vacuuming the database."""
|
||||
self.assertIsInstance(self.sql.vacuum(), sqlite3.Cursor)
|
||||
|
||||
def test_close(self):
|
||||
"""Check closing the connection."""
|
||||
self.sql.close()
|
||||
|
|
|
@ -32,27 +32,29 @@ class TestXfstestsdb(unittest.TestCase):
|
|||
self.xfstestsdb.run([])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
r"^usage: pytest \[\-h\] \[\-\-version\]"
|
||||
r"\s+\{.*?} ...$")
|
||||
r"\s+\{.*?}\s+...$")
|
||||
|
||||
def test_run(self):
|
||||
"""Test running the xfstestsdb."""
|
||||
parser = self.xfstestsdb.subparser.add_parser("test-run", help="help")
|
||||
test_done = unittest.mock.Mock()
|
||||
test_passed = False
|
||||
|
||||
def test_func(args: argparse.Namespace) -> None:
|
||||
nonlocal test_passed
|
||||
self.assertTrue(self.xfstestsdb.sql.sql.in_transaction)
|
||||
test_passed = True
|
||||
parser.set_defaults(function=test_func)
|
||||
parser.set_defaults(function=test_func, done=test_done)
|
||||
|
||||
self.xfstestsdb.run(["test-run"])
|
||||
self.assertTrue(test_passed)
|
||||
test_done.assert_called()
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_version(self, mock_stdout: io.StringIO):
|
||||
"""Test printing version information."""
|
||||
self.assertEqual(xfstestsdb.MAJOR, 1)
|
||||
self.assertEqual(xfstestsdb.MINOR, 2)
|
||||
self.assertEqual(xfstestsdb.MINOR, 6)
|
||||
|
||||
self.xfstestsdb.run(["--version"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.2-debug\n")
|
||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.6-debug\n")
|
||||
|
|
|
@ -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
68
tests/xunit/test-2.xunit
Normal 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 "myfs" 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 '/dev/vdb2'
|
||||
]]>
|
||||
</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 '/mnt/scratch'
|
||||
]]>
|
||||
</system-err>
|
||||
|
||||
</testcase>
|
||||
<testcase classname="xfstests.global" name="test/10" time="10">
|
||||
</testcase>
|
||||
</testsuite>
|
|
@ -4,6 +4,7 @@ import argparse
|
|||
from . import sqlite
|
||||
from . import delete
|
||||
from . import gc
|
||||
from . import gtk
|
||||
from . import list
|
||||
from . import new
|
||||
from . import rename
|
||||
|
@ -14,7 +15,7 @@ from . import untag
|
|||
from . import xunit
|
||||
|
||||
MAJOR = 1
|
||||
MINOR = 2
|
||||
MINOR = 6
|
||||
|
||||
|
||||
class Command:
|
||||
|
@ -23,13 +24,15 @@ class Command:
|
|||
def __init__(self) -> None:
|
||||
"""Initialize the xfstestsdb command."""
|
||||
self.parser = argparse.ArgumentParser()
|
||||
self.parser.set_defaults(function=lambda x: self.parser.print_usage())
|
||||
self.parser.set_defaults(function=lambda x: self.parser.print_usage(),
|
||||
done=lambda x: None)
|
||||
self.parser.add_argument("--version", action="store_true",
|
||||
help="show version number and exit")
|
||||
self.subparser = self.parser.add_subparsers(title="commands")
|
||||
self.sql = sqlite.Connection()
|
||||
self.commands = {"delete": delete.Command(self.subparser, self.sql),
|
||||
"gc": gc.Command(self.subparser, self.sql),
|
||||
"gtk": gtk.Command(self.subparser, self.sql),
|
||||
"list": list.Command(self.subparser, self.sql),
|
||||
"new": new.Command(self.subparser, self.sql),
|
||||
"rename": rename.Command(self.subparser, self.sql),
|
||||
|
@ -53,3 +56,4 @@ class Command:
|
|||
else:
|
||||
with self.sql:
|
||||
parsed.function(parsed)
|
||||
parsed.done(parsed)
|
||||
|
|
|
@ -12,8 +12,12 @@ class Command:
|
|||
"""Set up the Command."""
|
||||
self.parser = subparser.add_parser(name, help=help, **kwargs)
|
||||
self.parser.set_defaults(function=self.do_command)
|
||||
self.parser.set_defaults(done=self.do_done)
|
||||
self.sql = sql
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Do something."""
|
||||
raise NotImplementedError
|
||||
|
||||
def do_done(self, args: argparse.Namespace) -> None:
|
||||
"""Run after the main command, outside of a transaction."""
|
||||
|
|
|
@ -25,3 +25,10 @@ class Command(commands.Command):
|
|||
if not args.dry_run:
|
||||
self.sql("DELETE FROM xfstests_runs WHERE runid=?", runid)
|
||||
print(f"run #{runid} {how} deleted")
|
||||
|
||||
args.need_vacuum = len(rows) > 0
|
||||
|
||||
def do_done(self, args: argparse.Namespace) -> None:
|
||||
"""Vacuum the database after deleting."""
|
||||
if args.need_vacuum and not args.dry_run:
|
||||
self.sql.vacuum()
|
||||
|
|
118
xfstestsdb/gtk/__init__.py
Normal file
118
xfstestsdb/gtk/__init__.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""The `xfstestsdb gtk` command."""
|
||||
import argparse
|
||||
import errno
|
||||
import sys
|
||||
from . import gsetup
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Adw
|
||||
from . import model
|
||||
from . import sidebar
|
||||
from . import view
|
||||
from . import window
|
||||
from .. import commands
|
||||
from .. import sqlite
|
||||
|
||||
|
||||
class Application(Adw.Application):
|
||||
"""Our Adw.Application for displaying xfstests results."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
show_sidebar = GObject.Property(type=bool, default=False)
|
||||
environment = GObject.Property(type=Gio.ListStore)
|
||||
properties = GObject.Property(type=model.PropertyList)
|
||||
summary = GObject.Property(type=model.SummaryList)
|
||||
model = GObject.Property(type=model.TestCaseList)
|
||||
win = GObject.Property(type=window.Window)
|
||||
sidebar = GObject.Property(type=sidebar.Sidebar)
|
||||
view = GObject.Property(type=view.XfstestsView)
|
||||
sql = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection):
|
||||
"""Initialize the application."""
|
||||
super().__init__(application_id=gsetup.APPLICATION_ID,
|
||||
resource_base_path=gsetup.RESOURCE_PATH,
|
||||
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
||||
sql=sql)
|
||||
self.connect("notify::runid", self.__notify_runid)
|
||||
|
||||
def __notify_runid(self, app: Adw.Application,
|
||||
param: GObject.ParamSpec) -> None:
|
||||
self.properties = model.PropertyList(self.sql, self.runid)
|
||||
self.environment = self.properties.environment
|
||||
self.model = model.TestCaseList(self.sql, self.runid)
|
||||
self.summary = model.SummaryList(self.sql, self.runid)
|
||||
|
||||
def do_command_line(self, cmd_line: Gio.ApplicationCommandLine) -> int:
|
||||
"""Handle the Adw.Application::command-line signal."""
|
||||
Adw.Application.do_command_line(self, cmd_line)
|
||||
|
||||
for arg in cmd_line.get_arguments():
|
||||
split = arg.split("=")
|
||||
match split[0]:
|
||||
case "runid":
|
||||
self.runid = int(split[1])
|
||||
case "show-sidebar":
|
||||
self.show_sidebar = True
|
||||
|
||||
self.activate()
|
||||
return 0
|
||||
|
||||
def do_startup(self) -> None:
|
||||
"""Handle the Adw.Application::startup signal."""
|
||||
Adw.Application.do_startup(self)
|
||||
gsetup.add_style()
|
||||
|
||||
self.sidebar = sidebar.Sidebar(self.sql)
|
||||
self.view = view.XfstestsView()
|
||||
self.win = window.Window(child=self.view, sidebar=self.sidebar)
|
||||
self.win.headerbar.pack_end(self.view.filterbuttons)
|
||||
|
||||
self.sidebar.bind_property("runid", self, "runid")
|
||||
self.bind_property("runid", self.win, "runid")
|
||||
self.bind_property("show-sidebar", self.win, "show-sidebar")
|
||||
self.bind_property("environment", self.view, "environment")
|
||||
self.bind_property("properties", self.view, "properties")
|
||||
self.bind_property("model", self.view, "model")
|
||||
self.bind_property("summary", self.view, "summary")
|
||||
self.add_window(self.win)
|
||||
|
||||
def do_activate(self) -> None:
|
||||
"""Handle the Adw.Application::activate signal."""
|
||||
Adw.Application.do_activate(self)
|
||||
self.win.present()
|
||||
|
||||
def do_shutdown(self) -> None:
|
||||
"""Handle the Adw.Application::shutdown signal."""
|
||||
Adw.Application.do_shutdown(self)
|
||||
|
||||
|
||||
class Command(commands.Command):
|
||||
"""The `xfstestsdb gtk` command."""
|
||||
|
||||
def __init__(self, subparser: argparse.Action,
|
||||
sql: sqlite.Connection) -> None:
|
||||
"""Set up the gtk command."""
|
||||
super().__init__(subparser, sql, "gtk", help="show a gtk-based ui")
|
||||
self.parser.add_argument("runid", metavar="runid", nargs='?', type=int,
|
||||
help="show a specific xfstests run")
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Run the Gtk UI."""
|
||||
args.app_args = []
|
||||
|
||||
if args.runid is not None:
|
||||
if self.sql("SELECT 1 FROM xfstests_runs WHERE runid=?",
|
||||
args.runid).fetchone():
|
||||
args.app_args.append(f"runid={args.runid}")
|
||||
else:
|
||||
print(f"error: run #{args.runid} does not exist",
|
||||
file=sys.stderr)
|
||||
sys.exit(errno.ENOENT)
|
||||
else:
|
||||
args.app_args.append("show-sidebar")
|
||||
|
||||
def do_done(self, args: argparse.Namespace) -> None:
|
||||
"""Run the application outside of a transaction."""
|
||||
Application(self.sql).run(args.app_args)
|
29
xfstestsdb/gtk/button.py
Normal file
29
xfstestsdb/gtk/button.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Custom, reusable Button classes."""
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class StatusToggle(Gtk.Button):
|
||||
"""A toggle button that adds or removes a CSS class when pressed."""
|
||||
|
||||
active = GObject.Property(type=bool, default=False)
|
||||
icon_name = GObject.Property(type=str)
|
||||
|
||||
def __init__(self, icon_name: str, css_class: str,
|
||||
*, active: bool = False):
|
||||
"""Initialize a StatusToggle button."""
|
||||
super().__init__(icon_name=icon_name, has_frame=False,
|
||||
child=Gtk.Image(icon_name=icon_name, opacity=0.4))
|
||||
self.connect("notify::active", self.__notify_active)
|
||||
self.add_css_class(css_class)
|
||||
self.active = active
|
||||
|
||||
def __notify_active(self, toggle: typing.Self,
|
||||
param: GObject.ParamSpec) -> None:
|
||||
self.props.child.set_opacity(1.0 if self.active else 0.4)
|
||||
|
||||
def do_clicked(self) -> None:
|
||||
"""Adjust image opacity when the button is toggled."""
|
||||
self.active = not self.active
|
28
xfstestsdb/gtk/gsetup.py
Normal file
28
xfstestsdb/gtk/gsetup.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Early setup of Gtk modules to avoid errors later."""
|
||||
import gi
|
||||
import pathlib
|
||||
|
||||
gi.require_version("Adw", "1")
|
||||
gi.importlib.import_module("gi.repository.Adw")
|
||||
|
||||
DEBUG_STR = "-debug" if __debug__ else ""
|
||||
APPLICATION_ID = f"com.nowheycreamery.xfstestsdb.gtk{DEBUG_STR}"
|
||||
|
||||
__here = pathlib.Path(__file__).parent
|
||||
CSS_FILE = __here / "xfstestsdb.css"
|
||||
CSS_PRIORITY = gi.repository.Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
CSS_PROVIDER = gi.repository.Gtk.CssProvider()
|
||||
CSS_PROVIDER.load_from_path(str(CSS_FILE))
|
||||
|
||||
RESOURCE_FILE = __here / "icons" / "xfstestsdb.gresource"
|
||||
RESOURCE_PATH = "/com/nowheycreamery/xfstestsdb"
|
||||
RESOURCE = gi.repository.Gio.Resource.load(str(RESOURCE_FILE))
|
||||
gi.repository.Gio.resources_register(RESOURCE)
|
||||
|
||||
|
||||
def add_style():
|
||||
"""Add our custom stylesheet to the Gdk.Display."""
|
||||
style = gi.repository.Gtk.StyleContext
|
||||
style.add_provider_for_display(gi.repository.Gdk.Display.get_default(),
|
||||
CSS_PROVIDER, CSS_PRIORITY)
|
2
xfstestsdb/gtk/icons/down-large-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/down-large-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m -0.00390625 3 v 1 c 0 0.292969 0.12890625 0.558594 0.32812525 0.738281 l 7.671875 7.675781 l 7.707031 -7.707031 c 0.183594 -0.179687 0.292969 -0.429687 0.292969 -0.707031 v -1 h -1 c -0.273438 0 -0.523438 0.113281 -0.707032 0.292969 c -0.007812 0.011719 -0.019531 0.019531 -0.03125 0.03125 l -6.261718 6.261719 l -6.257813 -6.261719 c -0.183593 -0.199219 -0.445312 -0.324219 -0.742187 -0.324219 z m 0 0"/></svg>
|
After Width: | Height: | Size: 549 B |
2
xfstestsdb/gtk/icons/month-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/month-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 3.042969 1 c -1.128907 0 -2.042969 0.914062 -2.042969 2.042969 v 9.875 c 0 1.132812 0.914062 2.042969 2.042969 2.042969 h 9.914062 c 1.128907 0 2.042969 -0.910157 2.042969 -2.042969 v -9.875 c 0 -1.128907 -0.914062 -2.042969 -2.042969 -2.042969 z m -0.042969 4.960938 h 10 v 7 h -10 z m 1 1.039062 v 2 h 2 v -2 z m 3 0 v 2 h 2 v -2 z m 3 0 v 2 h 2 v -2 z m -6 3 v 2 h 2 v -2 z m 3 0 v 2 h 2 v -2 z m 0 0"/></svg>
|
After Width: | Height: | Size: 550 B |
35
xfstestsdb/gtk/icons/tag-symbolic.svg
Normal file
35
xfstestsdb/gtk/icons/tag-symbolic.svg
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7384"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1">
|
||||
<title
|
||||
id="title9167">Gnome Symbolic Icon Theme</title>
|
||||
<defs
|
||||
id="defs9" />
|
||||
<metadata
|
||||
id="metadata90">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Gnome Symbolic Icon Theme</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
id="path855"
|
||||
d="m 1,1 v 6.0008052 l 7.9783226,7.9783228 c 0.5857862,0.585786 1.5355344,0.585786 2.1213204,0 l 0.0097,-0.0097 3.87942,-3.87942 0.0097,-0.0097 c 0.585786,-0.585786 0.585786,-1.535534 0,-2.1213204 L 7.0394752,1 Z m 2.4775289,2.4403054 c 0.271447,-0.271447 0.6464468,-0.4391799 1.0606602,-0.4391797 0.8284268,3e-7 1.4998395,0.671413 1.4998398,1.4998398 4e-7,0.8284269 -0.6714131,1.4998403 -1.4998399,1.49984 -0.8284275,4e-7 -1.4998402,-0.6714123 -1.4998398,-1.4998399 -2e-7,-0.4142134 0.1677334,-0.7892139 0.4391797,-1.0606602 z"
|
||||
style="display:inline;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
|
||||
<title
|
||||
id="title864">tag-symbolic</title>
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
2
xfstestsdb/gtk/icons/test-fail-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/test-fail-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.96875 1 c -3.851562 0 -6.96875 3.117188 -6.96875 6.96875 s 3.117188 6.96875 6.96875 6.96875 s 6.96875 -3.117188 6.96875 -6.96875 s -3.117188 -6.96875 -6.96875 -6.96875 z m -3 3.96875 h 1 h 0.03125 c 0.253906 0.011719 0.511719 0.128906 0.6875 0.3125 l 1.28125 1.28125 l 1.3125 -1.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -1.28125 1.28125 l 1.25 1.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -1.28125 -1.28125 l -1.28125 1.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -1 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 1.28125 -1.25 l -1.28125 -1.28125 c -0.210938 -0.195312 -0.304688 -0.46875 -0.28125 -0.75 z m 0 0"/></svg>
|
After Width: | Height: | Size: 927 B |
2
xfstestsdb/gtk/icons/test-pass-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/test-pass-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8.234375 1.003906 c -2.15625 -0.070312 -4.277344 0.859375 -5.683594 2.601563 c -1.871093 2.316406 -2.066406 5.582031 -0.484375 8.105469 c 1.582032 2.527343 4.601563 3.777343 7.503906 3.109374 c 2.90625 -0.667968 5.074219 -3.117187 5.390626 -6.082031 c 0.046874 -0.359375 -0.101563 -0.71875 -0.394532 -0.933593 c -0.292968 -0.21875 -0.679687 -0.257813 -1.011718 -0.109376 c -0.332032 0.152344 -0.554688 0.472657 -0.582032 0.835938 c -0.226562 2.121094 -1.769531 3.863281 -3.851562 4.34375 c -2.078125 0.476562 -4.226563 -0.414062 -5.359375 -2.222656 c -1.132813 -1.808594 -0.992188 -4.128906 0.347656 -5.792969 s 3.578125 -2.289063 5.585937 -1.5625 c 0.339844 0.128906 0.71875 0.066406 0.996094 -0.167969 c 0.28125 -0.230468 0.410156 -0.59375 0.34375 -0.949218 c -0.0625 -0.355469 -0.316406 -0.648438 -0.660156 -0.761719 c -0.699219 -0.253907 -1.421875 -0.390625 -2.140625 -0.414063 z m 0 0"/><path d="m 13.167969 2.542969 l -5.292969 5.292969 l -2.417969 -2.417969 l -1.414062 1.414062 l 3.832031 3.832031 l 6.707031 -6.707031 z m 0 0"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
2
xfstestsdb/gtk/icons/test-skip-symbolic.svg
Normal file
2
xfstestsdb/gtk/icons/test-skip-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 1 c -3.855469 0 -7 3.144531 -7 7 s 3.144531 7 7 7 s 7 -3.144531 7 -7 s -3.144531 -7 -7 -7 z m 0 2 c 2.753906 0 5 2.246094 5 5 s -2.246094 5 -5 5 s -5 -2.246094 -5 -5 s 2.246094 -5 5 -5 z m 0 0"/></svg>
|
After Width: | Height: | Size: 341 B |
BIN
xfstestsdb/gtk/icons/xfstestsdb.gresource
Normal file
BIN
xfstestsdb/gtk/icons/xfstestsdb.gresource
Normal file
Binary file not shown.
11
xfstestsdb/gtk/icons/xfstestsdb.gresource.xml
Normal file
11
xfstestsdb/gtk/icons/xfstestsdb.gresource.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/com/nowheycreamery/xfstestsdb/icons/scalable/actions">
|
||||
<file preprocess="xml-stripblanks">test-pass-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">test-skip-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">test-fail-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">down-large-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">month-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">tag-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
295
xfstestsdb/gtk/model.py
Normal file
295
xfstestsdb/gtk/model.py
Normal file
|
@ -0,0 +1,295 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Our Testcase Gio.ListModel."""
|
||||
import sqlite3
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from .. import sqlite
|
||||
|
||||
|
||||
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()}
|
||||
|
||||
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."""
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
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 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
|
||||
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"])
|
||||
|
||||
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."""
|
||||
|
||||
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."""
|
||||
return property.name not in HIDDEN_PROPERTIES \
|
||||
and not property.all_same_value()
|
||||
|
||||
|
||||
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(XunitRow):
|
||||
"""Collects results for a single TestCase with multiple Xunits."""
|
||||
|
||||
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."""
|
||||
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(XunitList):
|
||||
"""A list of TestCases for a specific Xfstests Run."""
|
||||
|
||||
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 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):
|
||||
"""A filter for TestCases."""
|
||||
|
||||
passed = GObject.Property(type=bool, default=False)
|
||||
skipped = GObject.Property(type=bool, default=False)
|
||||
failure = GObject.Property(type=bool, default=True)
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize a TestCaseFilter."""
|
||||
super().__init__()
|
||||
self.connect("notify", self.__notify)
|
||||
|
||||
def __notify(self, filter: typing.Self, param: GObject.ParamSpec) -> None:
|
||||
match param.name:
|
||||
case "passed" | "skipped" | "failure":
|
||||
if self.get_property(param.name):
|
||||
change = Gtk.FilterChange.LESS_STRICT
|
||||
else:
|
||||
change = Gtk.FilterChange.MORE_STRICT
|
||||
self.changed(change)
|
||||
|
||||
def do_get_strictness(self) -> Gtk.FilterMatch:
|
||||
"""Get the current strictness of the filter."""
|
||||
match (self.passed, self.skipped, self.failure):
|
||||
case (True, True, True): return Gtk.FilterMatch.ALL
|
||||
case (False, False, False): return Gtk.FilterMatch.NONE
|
||||
case (_, _, _): return Gtk.FilterMatch.SOME
|
||||
|
||||
def do_match(self, testcase: TestCase) -> bool:
|
||||
"""Check if a testcase matches the current filter."""
|
||||
results = testcase.get_results()
|
||||
if self.passed and "passed" in results:
|
||||
return True
|
||||
if self.skipped and "skipped" in results:
|
||||
return True
|
||||
if self.failure and "failure" in results:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class SummaryValue(XunitCell):
|
||||
"""The summary of a single Xfstests xunit field."""
|
||||
|
||||
name = GObject.Property(type=str)
|
||||
unit = GObject.Property(type=str)
|
||||
value = GObject.Property(type=int)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Convert a Summary Value to a string."""
|
||||
s = '' if self.value == 1 else 's'
|
||||
return f"{self.value} {self.unit}{s}"
|
||||
|
||||
|
||||
class Summary(XunitRow):
|
||||
"""Collects values for each summary field with multiple Xunits."""
|
||||
|
||||
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 do_make_xunit(self, name: str, value: int, unit: str) -> SummaryValue:
|
||||
"""Add an xunit summary to the Summary."""
|
||||
return SummaryValue(name=name, value=value, unit=unit)
|
||||
|
||||
|
||||
class SummaryList(XunitList):
|
||||
"""A list summarizing the results of a specific Xfstests Run."""
|
||||
|
||||
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 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")
|
221
xfstestsdb/gtk/row.py
Normal file
221
xfstestsdb/gtk/row.py
Normal file
|
@ -0,0 +1,221 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Our TestCase row widgets and factory."""
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import tree
|
||||
|
||||
|
||||
STYLES = {"passed": "success", "failed": "error",
|
||||
"skipped": "warning", "time": "accent"}
|
||||
|
||||
|
||||
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)
|
||||
group = Gtk.SizeGroup()
|
||||
|
||||
def __init__(self, property: str):
|
||||
"""Initialize our InscriptionFactory."""
|
||||
super().__init__(property=property)
|
||||
|
||||
def do_setup(self, child: Gtk.Inscription) -> None:
|
||||
"""Set up a ListItem child widget."""
|
||||
LabelFactory.group.add_widget(child)
|
||||
|
||||
def do_bind(self, row: model.XunitRow, child: Gtk.Inscription) -> None:
|
||||
"""Bind a ListItem to the child widget."""
|
||||
text = row.get_property(self.property)
|
||||
if style := STYLES.get(text):
|
||||
child.add_css_class(style)
|
||||
child.set_text(text)
|
||||
|
||||
def do_unbind(self, row: model.XunitRow, child: Gtk.Inscription) -> None:
|
||||
"""Unbind a ListItem from the child widget."""
|
||||
for style in STYLES.values():
|
||||
child.remove_css_class(style)
|
||||
|
||||
def do_teardown(self, child: Gtk.Inscription) -> None:
|
||||
"""Clean up a ListItem child widget."""
|
||||
if child is not None:
|
||||
LabelFactory.group.remove_widget(child)
|
||||
|
||||
|
||||
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 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]
|
||||
child.set_text(property.value)
|
||||
child.set_tooltip_text(property.value)
|
||||
|
||||
|
||||
class ResultFactory(XunitFactory):
|
||||
"""Factory for making test result widgets."""
|
||||
|
||||
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 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."""
|
||||
if (result := row[self.xunit]) is None:
|
||||
return
|
||||
|
||||
if (text := result.status) == "passed":
|
||||
text = f"{result.time} seconds"
|
||||
|
||||
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(XunitFactory):
|
||||
"""Factory for making test summary widgets."""
|
||||
|
||||
def do_bind(self, row: model.Summary, child: Gtk.Inscription) -> None:
|
||||
"""Bind a ListItem to the child widget."""
|
||||
result = row[self.xunit]
|
||||
child.set_text(str(result))
|
||||
child.add_css_class(STYLES[row.name])
|
||||
|
||||
def do_unbind(self, row: model.TestCase, child: Gtk.Inscription) -> None:
|
||||
"""Unbind a ListItem from the child widget."""
|
||||
child.remove_css_class(STYLES[row.name])
|
||||
|
||||
|
||||
class SidebarFactory(Gtk.SignalListItemFactory):
|
||||
"""Factory for making sidebar widgets."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize our InscriptionFactory."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.connect("setup", self.__setup)
|
||||
self.connect("bind", self.__bind)
|
||||
self.connect("unbind", self.__unbind)
|
||||
self.connect("teardown", self.__teardown)
|
||||
|
||||
def __setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
"""Set up a ListItem child widget."""
|
||||
child = Gtk.Label(yalign=0.75)
|
||||
child.add_css_class("numeric")
|
||||
expander = Gtk.TreeExpander(child=child)
|
||||
listitem.set_child(expander)
|
||||
|
||||
def __bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
"""Bind a ListItem to the child widget."""
|
||||
treeitem = listitem.get_item()
|
||||
expander = listitem.get_child()
|
||||
expander.set_list_row(treeitem)
|
||||
|
||||
row = treeitem.get_item()
|
||||
child = expander.get_child()
|
||||
child.set_text(str(row))
|
||||
listitem.props.selectable = isinstance(row, tree.XfstestsRun)
|
||||
|
||||
def __unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
"""Unbind a ListItem from the child widget."""
|
||||
listitem.get_child().get_child().set_text("")
|
||||
|
||||
def __teardown(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||
listitem.set_child(None)
|
131
xfstestsdb/gtk/sidebar.py
Normal file
131
xfstestsdb/gtk/sidebar.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Our sidebar for selecting a specific xfstests run to view."""
|
||||
import datetime
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from .. import sqlite
|
||||
from . import row
|
||||
from . import tree
|
||||
|
||||
|
||||
class RunidView(Gtk.ScrolledWindow):
|
||||
"""Our RunidView class."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
model = GObject.Property(type=Gio.ListModel)
|
||||
|
||||
def __init__(self, model: tree.DateDeviceList) -> None:
|
||||
"""Initialize a RunidView instance."""
|
||||
super().__init__(model=model, vexpand=True)
|
||||
self._selection = Gtk.SingleSelection(model=model.treemodel)
|
||||
self._view = Gtk.ListView(model=self._selection,
|
||||
factory=row.SidebarFactory(),
|
||||
single_click_activate=True)
|
||||
|
||||
self._view.connect("activate", self.__activate)
|
||||
self.connect("notify::model", self.__notify_model)
|
||||
|
||||
self._view.add_css_class("navigation-sidebar")
|
||||
self._view.add_css_class("background")
|
||||
self.set_child(self._view)
|
||||
|
||||
def __activate(self, view: Gtk.ListView, position: int) -> None:
|
||||
treeitem = self._selection[position]
|
||||
item = treeitem.get_item()
|
||||
if isinstance(item, tree.XfstestsRun):
|
||||
self.runid = item.runid
|
||||
elif treeitem.props.expanded:
|
||||
treeitem.props.expanded = False
|
||||
else:
|
||||
treeitem.props.expanded = True
|
||||
self.runid = item.get_earliest_run().runid
|
||||
|
||||
def __notify_model(self, sidebar: typing.Self,
|
||||
param: GObject.ParamSpec) -> None:
|
||||
model = None if self.model is None else self.model.treemodel
|
||||
self._selection.props.model = model
|
||||
|
||||
|
||||
class CalendarView(Gtk.Box):
|
||||
"""Our calendar view for seleting an xfstests run by date."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
sql = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection) -> None:
|
||||
"""Initialize a CalendarView instance."""
|
||||
super().__init__(sql=sql, spacing=6,
|
||||
orientation=Gtk.Orientation.VERTICAL)
|
||||
today = datetime.date.today()
|
||||
self._calendar = Gtk.Calendar()
|
||||
self._view = RunidView(model=tree.DateDeviceList(sql, today))
|
||||
|
||||
self._view.bind_property("runid", self, "runid")
|
||||
self._calendar.connect("day-selected", self.__day_selected)
|
||||
self._calendar.connect("next-month", self.__date_changed)
|
||||
self._calendar.connect("next-year", self.__date_changed)
|
||||
self._calendar.connect("prev-month", self.__date_changed)
|
||||
self._calendar.connect("prev-year", self.__date_changed)
|
||||
|
||||
self.__mark_days(today)
|
||||
|
||||
self.append(self._calendar)
|
||||
self.append(self._view)
|
||||
|
||||
def __day_selected(self, calendar: Gtk.Calendar) -> None:
|
||||
self.__select_day(datetime.date(*calendar.get_date().get_ymd()))
|
||||
|
||||
def __date_changed(self, calendar: Gtk.Calendar) -> None:
|
||||
date = datetime.date(*calendar.get_date().get_ymd())
|
||||
self.__mark_days(date)
|
||||
self.__select_day(date)
|
||||
|
||||
def __select_day(self, date: datetime.date) -> None:
|
||||
self._view.model = tree.DateDeviceList(self.sql, date)
|
||||
|
||||
def __mark_days(self, date: datetime.date) -> None:
|
||||
min = datetime.datetime.combine(date.replace(day=1), datetime.time())
|
||||
max = (min + datetime.timedelta(days=40)).replace(day=1)
|
||||
|
||||
self._calendar.clear_marks()
|
||||
for stamp in self.sql("""SELECT DISTINCT timestamp FROM tagged_runs
|
||||
WHERE timestamp >= ? AND timestamp < ?""",
|
||||
min, max).fetchall():
|
||||
ts = datetime.datetime.fromisoformat(stamp['timestamp'])
|
||||
self._calendar.mark_day(ts.day)
|
||||
|
||||
|
||||
class Sidebar(Gtk.Box):
|
||||
"""Our sidebar for seleting an xfstests run."""
|
||||
|
||||
runid = GObject.Property(type=int)
|
||||
sql = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection) -> None:
|
||||
"""Initialize a CalendarView instance."""
|
||||
animation = Gtk.StackTransitionType.OVER_LEFT_RIGHT
|
||||
super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL)
|
||||
self._stack = Gtk.Stack(transition_type=animation)
|
||||
self._switcher = Gtk.StackSwitcher(stack=self._stack,
|
||||
margin_top=6, margin_bottom=6,
|
||||
margin_start=80, margin_end=80)
|
||||
self._calendar = CalendarView(sql)
|
||||
self._tags = RunidView(model=tree.TagList(sql))
|
||||
|
||||
self._calendar.bind_property("runid", self, "runid")
|
||||
self._tags.bind_property("runid", self, "runid")
|
||||
|
||||
self.__add_page(self._calendar, "Calendar", "month-symbolic")
|
||||
self.__add_page(self._tags, "Tags", "tag-symbolic")
|
||||
|
||||
self._switcher.add_css_class("large-icons")
|
||||
|
||||
self.append(self._switcher)
|
||||
self.append(Gtk.Separator())
|
||||
self.append(self._stack)
|
||||
|
||||
def __add_page(self, page: Gtk.Widget, title: str, icon: str) -> None:
|
||||
pg = self._stack.add_titled(page, title.lower(), title)
|
||||
pg.set_icon_name(icon)
|
224
xfstestsdb/gtk/tree.py
Normal file
224
xfstestsdb/gtk/tree.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Our tree model for selecting an xfstests run to view."""
|
||||
import bisect
|
||||
import datetime
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from .. import sqlite
|
||||
|
||||
|
||||
class XfstestsRun(GObject.GObject):
|
||||
"""A single XfstessRun."""
|
||||
|
||||
timestamp = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
runid = GObject.Property(type=int)
|
||||
ftime = GObject.Property(type=str)
|
||||
|
||||
def __init__(self, runid: int, timestamp: datetime.datetime,
|
||||
ftime: str = "%T") -> None:
|
||||
"""Initialize an XfstestsRun instance."""
|
||||
super().__init__(timestamp=timestamp, runid=runid, ftime=ftime)
|
||||
|
||||
def __gt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare the timestamps an runids of two XfstestRun objects."""
|
||||
return (self.timestamp, self.runid) > (rhs.timestamp, rhs.runid)
|
||||
|
||||
def __lt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare the timestamps and runids of two XfstestsRun objects."""
|
||||
return (self.timestamp, self.runid) < (rhs.timestamp, rhs.runid)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get a string representation of this run."""
|
||||
return f"#{self.runid}: {self.timestamp.strftime(self.ftime)}"
|
||||
|
||||
|
||||
class DeviceRunsList(GObject.GObject, Gio.ListModel):
|
||||
"""Contains a single test device with multiple runs."""
|
||||
|
||||
name = GObject.Property(type=str)
|
||||
n_items = GObject.Property(type=int)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
"""Initialize our DeviceRunsList."""
|
||||
super().__init__(name=name)
|
||||
self.__runs = []
|
||||
|
||||
def __lt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare two DeviceRunsList objects."""
|
||||
return self.name < rhs.name
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the name of this device."""
|
||||
return self.name
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return XfstestsRun.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of runs added to this device."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> XfstestsRun:
|
||||
"""Get the run at the nth position."""
|
||||
return self.__runs[n]
|
||||
|
||||
def add_run(self, runid: int, timestamp: datetime.datetime,
|
||||
ftime: str = "%T") -> None:
|
||||
"""Add a run to the Device's list."""
|
||||
bisect.insort(self.__runs, XfstestsRun(runid, timestamp, ftime))
|
||||
self.n_items = len(self.__runs)
|
||||
|
||||
def get_earliest_run(self) -> XfstestsRun | None:
|
||||
"""Get the earliest XfstestsRun that we know about."""
|
||||
return self.__runs[0] if self.n_items > 0 else None
|
||||
|
||||
|
||||
class DateDeviceList(GObject.GObject, Gio.ListModel):
|
||||
"""A list containing test devices used on a specific day."""
|
||||
|
||||
date = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
n_items = GObject.Property(type=int)
|
||||
treemodel = GObject.Property(type=Gtk.TreeListModel)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection, date: datetime.date) -> None:
|
||||
"""Initialize our DateDeviceList."""
|
||||
super().__init__(date=date)
|
||||
|
||||
devices = {}
|
||||
for row in sql("""SELECT DISTINCT runid, device, timestamp
|
||||
FROM tagged_runs
|
||||
WHERE timestamp >= ? AND timestamp < ?""",
|
||||
*self.get_range(date)).fetchall():
|
||||
if (dev := devices.get(row['device'])) is None:
|
||||
devices[row['device']] = dev = DeviceRunsList(row['device'])
|
||||
ts = datetime.datetime.fromisoformat(row['timestamp'])
|
||||
dev.add_run(row['runid'], ts)
|
||||
|
||||
self.__items = sorted(devices.values())
|
||||
self.n_items = len(self.__items)
|
||||
self.treemodel = Gtk.TreeListModel.new(root=self, passthrough=False,
|
||||
autoexpand=True,
|
||||
create_func=self.__create_func)
|
||||
|
||||
def __create_func(self, item: GObject.GObject) -> Gio.ListModel:
|
||||
if isinstance(item, DeviceRunsList):
|
||||
return item
|
||||
return None
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return DeviceRunsList.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of items in the list."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> DeviceRunsList:
|
||||
"""Get a specific item in the list."""
|
||||
return self.__items[n]
|
||||
|
||||
def get_range(self, date: datetime.date) -> tuple[datetime.datetime,
|
||||
datetime.datetime]:
|
||||
"""Get the minimum and maximum timestamps for the date."""
|
||||
min = datetime.datetime.combine(date, datetime.time())
|
||||
max = min + datetime.timedelta(days=1)
|
||||
return (min, max)
|
||||
|
||||
|
||||
class TagDeviceList(GObject.GObject, Gio.ListModel):
|
||||
"""Contains a single tag with multiple devices and runs."""
|
||||
|
||||
name = GObject.Property(type=str)
|
||||
n_items = GObject.Property(type=int)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
"""Initialize our TagDeviceList."""
|
||||
super().__init__(name=name)
|
||||
self.__devices = []
|
||||
|
||||
def __lt__(self, rhs: typing.Self) -> bool:
|
||||
"""Compare two TagDeviceList objects."""
|
||||
lhs_run = self.get_earliest_run()
|
||||
rhs_run = rhs.get_earliest_run()
|
||||
if lhs_run.runid == rhs_run.runid:
|
||||
return self.name > rhs.name
|
||||
return lhs_run > rhs_run
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the name of this tag."""
|
||||
return self.name
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return DeviceRunsList.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of devices added to this tag."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> DeviceRunsList:
|
||||
"""Get the device at the nth position."""
|
||||
return self.__devices[n]
|
||||
|
||||
def add_run(self, runid: int, device: str,
|
||||
timestamp: datetime.datetime) -> None:
|
||||
"""Add a run to the Tag's list."""
|
||||
pos = bisect.bisect_left(self.__devices, device, key=str)
|
||||
if pos < len(self.__devices) and self.__devices[pos].name == device:
|
||||
dev = self.__devices[pos]
|
||||
else:
|
||||
dev = DeviceRunsList(device)
|
||||
self.__devices.insert(pos, dev)
|
||||
|
||||
dev.add_run(runid, timestamp, "%c")
|
||||
self.n_items = len(self.__devices)
|
||||
|
||||
def get_earliest_run(self) -> XfstestsRun | None:
|
||||
"""Get the earliest run added to the tag."""
|
||||
runs = [d.get_earliest_run() for d in self.__devices]
|
||||
return min(runs, default=None)
|
||||
|
||||
|
||||
class TagList(GObject.GObject, Gio.ListModel):
|
||||
"""A list containing all tagged xfstests runs."""
|
||||
|
||||
n_items = GObject.Property(type=int)
|
||||
treemodel = GObject.Property(type=Gtk.TreeListModel)
|
||||
|
||||
def __init__(self, sql: sqlite.Connection) -> None:
|
||||
"""Initialize our TagList."""
|
||||
super().__init__()
|
||||
|
||||
tags = {}
|
||||
for row in sql("""SELECT DISTINCT runid, device, timestamp, tag
|
||||
FROM tagged_runs WHERE tag IS NOT NULL""").fetchall():
|
||||
if (tag := tags.get(row['tag'])) is None:
|
||||
tags[row['tag']] = tag = TagDeviceList(row['tag'])
|
||||
ts = datetime.datetime.fromisoformat(row['timestamp'])
|
||||
tag.add_run(row['runid'], row['device'], ts)
|
||||
|
||||
self.__items = sorted(tags.values())
|
||||
self.n_items = len(self.__items)
|
||||
self.treemodel = Gtk.TreeListModel.new(root=self, passthrough=False,
|
||||
autoexpand=False,
|
||||
create_func=self.__create_func)
|
||||
|
||||
def __create_func(self, item: GObject.GObject) -> Gio.ListModel:
|
||||
if isinstance(item, TagDeviceList | DeviceRunsList):
|
||||
return item
|
||||
return None
|
||||
|
||||
def do_get_item_type(self) -> GObject.GType:
|
||||
"""Get the type of objects in the list."""
|
||||
return TagDeviceList.__gtype__
|
||||
|
||||
def do_get_n_items(self) -> int:
|
||||
"""Get the number of items in the list."""
|
||||
return self.n_items
|
||||
|
||||
def do_get_item(self, n: int) -> TagDeviceList:
|
||||
"""Get a specific item in the list."""
|
||||
return self.__items[n]
|
346
xfstestsdb/gtk/view.py
Normal file
346
xfstestsdb/gtk/view.py
Normal file
|
@ -0,0 +1,346 @@
|
|||
# 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 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("undershoot-top")
|
||||
self.add_css_class("undershoot-bottom")
|
||||
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__("property", vscrollbar_policy=Gtk.PolicyType.NEVER)
|
||||
self.filtermodel.set_filter(PropertyFilter())
|
||||
self.props.child.add_css_class("data-table")
|
||||
|
||||
def do_make_factory(self, xunit: str) -> row.PropertyFactory:
|
||||
"""Make a new PropertyFactory instance."""
|
||||
return row.PropertyFactory(xunit=xunit)
|
||||
|
||||
|
||||
class FilterButtons(Gtk.Box):
|
||||
"""Buttons for controlling the TestCaseFilter."""
|
||||
|
||||
passed = GObject.Property(type=bool, default=False)
|
||||
skipped = GObject.Property(type=bool, default=False)
|
||||
failure = GObject.Property(type=bool, default=True)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the FilterButtons."""
|
||||
super().__init__()
|
||||
self.add_css_class("linked")
|
||||
|
||||
self._passed = button.StatusToggle("test-pass", "passed")
|
||||
self._skipped = button.StatusToggle("test-skip", "skipped")
|
||||
self._failure = button.StatusToggle("test-fail", "failure",
|
||||
active=True)
|
||||
|
||||
self._passed.bind_property("active", self, "passed")
|
||||
self._skipped.bind_property("active", self, "skipped")
|
||||
self._failure.bind_property("active", self, "failure")
|
||||
|
||||
self.append(self._passed)
|
||||
self.append(self._skipped)
|
||||
self.append(self._failure)
|
||||
|
||||
|
||||
class TestCaseView(XunitView):
|
||||
"""Displays our TestCaseList model to the user."""
|
||||
|
||||
filterbuttons = GObject.Property(type=FilterButtons)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize a TestCaseView."""
|
||||
super().__init__("testcase", filterbuttons=FilterButtons())
|
||||
self.filtermodel.props.filter = TestCaseFilter()
|
||||
|
||||
for prop in ["passed", "skipped", "failure"]:
|
||||
self.filterbuttons.bind_property(prop,
|
||||
self.filtermodel.props.filter,
|
||||
prop)
|
||||
|
||||
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 = "down-large-symbolic"
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL, margin_top=24,
|
||||
margin_start=24, margin_end=24, margin_bottom=24)
|
||||
self._back = Gtk.Button(child=Adw.ButtonContent(icon_name=icon,
|
||||
label="done"))
|
||||
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 __back_clicked(self, button: Gtk.Button) -> None:
|
||||
self.emit("go-back")
|
||||
|
||||
@GObject.Signal
|
||||
def go_back(self) -> None:
|
||||
"""Signal that the user wants to go back."""
|
||||
|
||||
|
||||
class SummaryView(XunitView):
|
||||
"""Displays our SummaryList model to the user."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize a SummaryView."""
|
||||
super().__init__("summary", vscrollbar_policy=Gtk.PolicyType.NEVER)
|
||||
self.props.child.add_css_class("data-table")
|
||||
|
||||
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_UP_DOWN
|
||||
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._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."""
|
||||
return self._testcaseview.filterbuttons
|
53
xfstestsdb/gtk/window.py
Normal file
53
xfstestsdb/gtk/window.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""The main Adw.Window implementation for our application."""
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class Window(Adw.Window):
|
||||
"""Our customized Window displayed to the user."""
|
||||
|
||||
child = GObject.Property(type=Gtk.Widget)
|
||||
sidebar = GObject.Property(type=Gtk.Widget)
|
||||
headerbar = GObject.Property(type=Adw.HeaderBar)
|
||||
title = GObject.Property(type=Adw.WindowTitle)
|
||||
runid = GObject.Property(type=int)
|
||||
show_sidebar = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Set up our Window."""
|
||||
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)
|
||||
self._splitview = Adw.OverlaySplitView(content=self.child,
|
||||
sidebar=self.sidebar,
|
||||
collapsed=True,
|
||||
show_sidebar=self.show_sidebar)
|
||||
self._show_sidebar = Gtk.ToggleButton(icon_name="sidebar-show",
|
||||
active=self.show_sidebar)
|
||||
|
||||
self.headerbar.pack_start(self._show_sidebar)
|
||||
self.headerbar.props.title_widget = self.title
|
||||
self.headerbar.add_css_class("flat")
|
||||
|
||||
self.props.content.append(self.headerbar)
|
||||
self.props.content.append(self._splitview)
|
||||
|
||||
self._show_sidebar.bind_property("active", self, "show-sidebar",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("show-sidebar", self._splitview, "show-sidebar")
|
||||
self.bind_property("sidebar", self._splitview, "sidebar")
|
||||
self.bind_property("child", self._splitview, "content")
|
||||
|
||||
self.connect("notify::runid", self.__notify_runid)
|
||||
|
||||
if __debug__:
|
||||
self.add_css_class("devel")
|
||||
|
||||
def __notify_runid(self, window: typing.Self,
|
||||
param: GObject.ParamSpec) -> None:
|
||||
text = f"runid #{self.runid}" if self.runid > 0 else ""
|
||||
self.title.props.subtitle = text
|
33
xfstestsdb/gtk/xfstestsdb.css
Normal file
33
xfstestsdb/gtk/xfstestsdb.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* Copyright 2023 (c) Anna Schumaker. */
|
||||
|
||||
button.passed > image {
|
||||
color: @success_color;
|
||||
}
|
||||
|
||||
cell.passed {
|
||||
color: @success_fg_color;
|
||||
background-color: @success_bg_color;
|
||||
}
|
||||
|
||||
button.skipped > image {
|
||||
color: @warning_color;
|
||||
}
|
||||
|
||||
cell.skipped {
|
||||
color: @warning_fg_color;
|
||||
background-color: @warning_bg_color;
|
||||
}
|
||||
|
||||
button.failure > image {
|
||||
color: @error_color;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
|
@ -52,6 +52,8 @@ class Command(commands.Command):
|
|||
if isinstance(args.tags, str):
|
||||
filter.append("tag GLOB ?")
|
||||
sql_args.append(args.tags)
|
||||
else:
|
||||
filter.append("tag IS NOT NULL")
|
||||
|
||||
if args.timestamp:
|
||||
filter.append("timestamp GLOB ?")
|
||||
|
@ -62,6 +64,8 @@ class Command(commands.Command):
|
|||
if isinstance(args.xunits, str):
|
||||
filter.append("xunit GLOB ?")
|
||||
sql_args.append(args.xunits)
|
||||
else:
|
||||
filter.append("xunit IS NOT NULL")
|
||||
|
||||
if len(filter) > 0:
|
||||
sql_where = " WHERE " + " AND ".join(filter) + " "
|
||||
|
|
|
@ -16,6 +16,8 @@ class Command(commands.Command):
|
|||
self.parser.add_argument("device", metavar="device", nargs=1,
|
||||
help="the test device used for"
|
||||
"this xfstests run")
|
||||
self.parser.add_argument("--terse", action="store_true",
|
||||
help="output only the new runid")
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Create a new row in the xfstestsdb_runs table."""
|
||||
|
@ -24,6 +26,9 @@ class Command(commands.Command):
|
|||
datetime(timestamp, 'localtime') as timestamp""",
|
||||
args.device[0])
|
||||
row = cur.fetchone()
|
||||
print(f"created run #{row['runid']}", end=" ")
|
||||
print(f"with test device '{row['device']}'", end=" ")
|
||||
print(f"[{row['timestamp']}]")
|
||||
if args.terse:
|
||||
print(row['runid'])
|
||||
else:
|
||||
print(f"created run #{row['runid']}", end=" ")
|
||||
print(f"with test device '{row['device']}'", end=" ")
|
||||
print(f"[{row['timestamp']}]")
|
||||
|
|
|
@ -76,3 +76,7 @@ class Connection:
|
|||
cur = self.sql.executescript(f.read())
|
||||
self.sql.commit()
|
||||
return cur
|
||||
|
||||
def vacuum(self) -> sqlite3.Cursor | None:
|
||||
"""Vacuum the database."""
|
||||
return self.sql.execute("VACUUM")
|
||||
|
|
Loading…
Reference in New Issue
Block a user