Compare commits
18 Commits
bea49c5eae
...
4d3425bb57
Author | SHA1 | Date | |
---|---|---|---|
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__*
|
*__pycache__*
|
||||||
|
xfstestsdb/gtk/icons/xfstestsdb.gresource
|
||||||
|
|
11
Makefile
11
Makefile
|
@ -3,6 +3,9 @@
|
||||||
export PREFIX = /usr/local
|
export PREFIX = /usr/local
|
||||||
export XFSTESTSDB_BIN = ${PREFIX}/bin
|
export XFSTESTSDB_BIN = ${PREFIX}/bin
|
||||||
export XFSTESTSDB_LIB = ${PREFIX}/lib/xfstestsdb
|
export XFSTESTSDB_LIB = ${PREFIX}/lib/xfstestsdb
|
||||||
|
export GTK_DIR = xfstestsdb/gtk/icons
|
||||||
|
|
||||||
|
all: xfstestsdb.gresource flake8
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
find . -type d -name __pycache__ -exec rm -r {} \+
|
find . -type d -name __pycache__ -exec rm -r {} \+
|
||||||
|
@ -11,8 +14,12 @@ clean:
|
||||||
flake8:
|
flake8:
|
||||||
flake8
|
flake8
|
||||||
|
|
||||||
|
.PHONY: xfstestsdb.gresource
|
||||||
|
xfstestsdb.gresource:
|
||||||
|
glib-compile-resources --sourcedir=$(GTK_DIR) $(GTK_DIR)/xfstestsdb.gresource.xml
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install: xfstestsdb.gresource
|
||||||
find ./xfstestsdb -type f -not -path "*/__pycache__/*" \
|
find ./xfstestsdb -type f -not -path "*/__pycache__/*" \
|
||||||
-exec install -v -C -D -m 755 "{}" "$(XFSTESTSDB_LIB)/{}" \;
|
-exec install -v -C -D -m 755 "{}" "$(XFSTESTSDB_LIB)/{}" \;
|
||||||
install -C -v -m 644 xfstestsdb.py $(XFSTESTSDB_LIB)/xfstestsdb.py
|
install -C -v -m 644 xfstestsdb.py $(XFSTESTSDB_LIB)/xfstestsdb.py
|
||||||
|
@ -24,7 +31,7 @@ pytest:
|
||||||
pytest
|
pytest
|
||||||
|
|
||||||
.PHONY: tests
|
.PHONY: tests
|
||||||
tests: pytest flake8
|
tests: xfstestsdb.gresource pytest flake8
|
||||||
|
|
||||||
.PHONY: uninstall
|
.PHONY: uninstall
|
||||||
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)
|
324
tests/gtk/test_model.py
Normal file
324
tests/gtk/test_model.py
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
# 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 TestXunitResult(unittest.TestCase):
|
||||||
|
"""Tests a single TestCase Xunit instance."""
|
||||||
|
|
||||||
|
def test_xunit_result(self):
|
||||||
|
"""Test creating an xunit instance."""
|
||||||
|
xunit = xfstestsdb.gtk.model.XunitResult(name="my xunit name",
|
||||||
|
status="passed", time=123,
|
||||||
|
message="my message",
|
||||||
|
stdout="my stdout",
|
||||||
|
stderr="my stderr")
|
||||||
|
self.assertIsInstance(xunit, GObject.GObject)
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
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, GObject.GObject)
|
||||||
|
self.assertEqual(self.testcase.name, "test-case")
|
||||||
|
|
||||||
|
def test_compare(self):
|
||||||
|
"""Test the less-than operator on TestCases."""
|
||||||
|
testcase2 = xfstestsdb.gtk.model.TestCase(name="test-case-2")
|
||||||
|
self.assertTrue(self.testcase < testcase2)
|
||||||
|
self.assertFalse(testcase2 < self.testcase)
|
||||||
|
self.assertFalse(self.testcase < self.testcase)
|
||||||
|
|
||||||
|
def test_xunits(self):
|
||||||
|
"""Test adding xunits to a TestCase."""
|
||||||
|
self.assertIsNone(self.testcase["xunit-1"])
|
||||||
|
|
||||||
|
self.testcase.add_xunit("xunit-1", "passed", 123, "message",
|
||||||
|
"stdout", "stderr")
|
||||||
|
xunit = self.testcase["xunit-1"]
|
||||||
|
self.assertIsInstance(xunit, xfstestsdb.gtk.model.XunitResult)
|
||||||
|
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.XunitResult)
|
||||||
|
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, "")
|
||||||
|
|
||||||
|
def test_get_results(self):
|
||||||
|
"""Test getting a set of results for this test case."""
|
||||||
|
self.testcase.add_xunit("xunit-1", "passed", 123, "", "", "")
|
||||||
|
self.assertSetEqual(self.testcase.get_results(), {"passed"})
|
||||||
|
self.testcase.add_xunit("xunit-2", "passed", 123, "", "", "")
|
||||||
|
self.assertSetEqual(self.testcase.get_results(), {"passed"})
|
||||||
|
self.testcase.add_xunit("xunit-3", "skipped", 123, "", "", "")
|
||||||
|
self.assertSetEqual(self.testcase.get_results(), {"passed", "skipped"})
|
||||||
|
self.testcase.add_xunit("xunit-4", "failure", 123, "", "", "")
|
||||||
|
self.assertSetEqual(self.testcase.get_results(),
|
||||||
|
{"passed", "skipped", "failure"})
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseList(unittest.TestCase):
|
||||||
|
"""Tests our TestCaseList Gio.ListModel."""
|
||||||
|
|
||||||
|
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, GObject.GObject)
|
||||||
|
self.assertIsInstance(self.tclist, Gio.ListModel)
|
||||||
|
self.assertEqual(self.tclist.runid, 1)
|
||||||
|
|
||||||
|
def test_get_item_type(self):
|
||||||
|
"""Test the get_item_type() function."""
|
||||||
|
self.assertEqual(self.tclist.get_item_type(),
|
||||||
|
xfstestsdb.gtk.model.TestCase.__gtype__)
|
||||||
|
|
||||||
|
def test_get_n_items(self):
|
||||||
|
"""Test the get_n_items() function."""
|
||||||
|
self.assertEqual(self.tclist.get_n_items(), 10)
|
||||||
|
self.assertEqual(self.tclist.n_items, 10)
|
||||||
|
|
||||||
|
def test_get_item(self):
|
||||||
|
"""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))
|
||||||
|
|
||||||
|
def test_get_xunits(self):
|
||||||
|
"""Test the get_xunits() function."""
|
||||||
|
self.assertListEqual(self.tclist.get_xunits(), ["xunit-1", "xunit-2"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseFilter(unittest.TestCase):
|
||||||
|
"""Tests our Gtk.Filter customized for filtering TestCases."""
|
||||||
|
|
||||||
|
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, GObject.GObject)
|
||||||
|
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, GObject.GObject)
|
||||||
|
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")
|
||||||
|
|
||||||
|
def test_get_results(self):
|
||||||
|
"""Test getting a set of results for this summary."""
|
||||||
|
self.summary.add_xunit("xunit-1", 1, "unit")
|
||||||
|
self.assertSetEqual(self.summary.get_results(), {"1 unit"})
|
||||||
|
self.summary.add_xunit("xunit-2", 2, "unit")
|
||||||
|
self.assertSetEqual(self.summary.get_results(), {"1 unit", "2 units"})
|
||||||
|
|
||||||
|
|
||||||
|
class TestSummaryList(unittest.TestCase):
|
||||||
|
"""Test case for our summary list."""
|
||||||
|
|
||||||
|
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, GObject.GObject)
|
||||||
|
self.assertIsInstance(self.summary, Gio.ListModel)
|
||||||
|
self.assertEqual(self.summary.runid, 1)
|
||||||
|
|
||||||
|
def test_get_item_type(self):
|
||||||
|
"""Test the get_item_type() function."""
|
||||||
|
self.assertEqual(self.summary.get_item_type(),
|
||||||
|
xfstestsdb.gtk.model.Summary.__gtype__)
|
||||||
|
|
||||||
|
def test_get_n_items(self):
|
||||||
|
"""Test the get_n_items() function."""
|
||||||
|
self.assertEqual(self.summary.get_n_items(), 4)
|
||||||
|
self.assertEqual(self.summary.n_items, 4)
|
||||||
|
|
||||||
|
def test_get_item(self):
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
def test_get_xunits(self):
|
||||||
|
"""Test the get_xunits() function."""
|
||||||
|
self.assertListEqual(self.summary.get_xunits(), ["xunit-1", "xunit-2"])
|
247
tests/gtk/test_row.py
Normal file
247
tests/gtk/test_row.py
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""Tests our row widgets and factories."""
|
||||||
|
import unittest
|
||||||
|
import xfstestsdb.gtk.row
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import Adw
|
||||||
|
|
||||||
|
|
||||||
|
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("name")
|
||||||
|
self.group = xfstestsdb.gtk.row.LabelFactory.group
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
"""Test that the factory was initialized correctly."""
|
||||||
|
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
|
||||||
|
self.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.assertIsInstance(self.listitem.get_child(), Gtk.Label)
|
||||||
|
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
|
||||||
|
self.assertIn(self.listitem.get_child(), self.group.get_widgets())
|
||||||
|
|
||||||
|
def test_bind(self):
|
||||||
|
"""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.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
|
||||||
|
def test_teardown(self):
|
||||||
|
"""Test that the factory implements the 'teardown' signal."""
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
child = self.listitem.get_child()
|
||||||
|
self.factory.emit("teardown", self.listitem)
|
||||||
|
self.assertIsNone(self.listitem.get_child())
|
||||||
|
self.assertNotIn(child, self.group.get_widgets())
|
||||||
|
|
||||||
|
def test_styles(self):
|
||||||
|
"""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 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-1")
|
||||||
|
self.factory.connect("setup", self.setup_parent)
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
"""Test that the factory was initialized correctly."""
|
||||||
|
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
|
||||||
|
self.assertEqual(self.factory.xunit, "xunit-1")
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
"""Test that the factory implements the 'setup' signal."""
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.assertIsInstance(self.listitem.get_child(), Gtk.Label)
|
||||||
|
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
|
||||||
|
self.assertEqual(self.parent.get_child(), self.listitem.get_child())
|
||||||
|
|
||||||
|
def test_bind_passed(self):
|
||||||
|
"""Test binding to a passing test."""
|
||||||
|
self.testcase.add_xunit("xunit-1", "passed", 3, "", None, None)
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.factory.emit("bind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "3 seconds")
|
||||||
|
self.assertIsNone(self.listitem.get_child().get_tooltip_text())
|
||||||
|
self.assertTrue(self.parent.has_css_class("passed"))
|
||||||
|
|
||||||
|
self.factory.emit("unbind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
self.assertFalse(self.parent.has_css_class("passed"))
|
||||||
|
|
||||||
|
def test_bind_skipped(self):
|
||||||
|
"""Test binding to a skipped test."""
|
||||||
|
self.testcase.add_xunit("xunit-1", "skipped", 0,
|
||||||
|
"test skipped for ... reasons", None, None)
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.factory.emit("bind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "skipped")
|
||||||
|
self.assertEqual(self.listitem.get_child().get_tooltip_text(),
|
||||||
|
"test skipped for ... reasons")
|
||||||
|
self.assertTrue(self.parent.has_css_class("skipped"))
|
||||||
|
|
||||||
|
self.factory.emit("unbind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
self.assertFalse(self.parent.has_css_class("skipped"))
|
||||||
|
|
||||||
|
def test_bind_failed(self):
|
||||||
|
"""Test binding to a failed test."""
|
||||||
|
self.testcase.add_xunit("xunit-1", "failure", 8,
|
||||||
|
"- failed. see output", None, None)
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.factory.emit("bind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "failure")
|
||||||
|
self.assertEqual(self.listitem.get_child().get_tooltip_text(),
|
||||||
|
"failed. see output")
|
||||||
|
self.assertTrue(self.parent.has_css_class("failure"))
|
||||||
|
|
||||||
|
self.factory.emit("unbind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
self.assertFalse(self.parent.has_css_class("failure"))
|
||||||
|
|
||||||
|
def test_bind_missing(self):
|
||||||
|
"""Test binding to a missing test."""
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.factory.emit("bind", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
|
||||||
|
self.factory.emit("unbind", self.listitem)
|
||||||
|
|
||||||
|
def test_teardown(self):
|
||||||
|
"""Test that the factory implements the 'teardown' signal."""
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.factory.emit("teardown", self.listitem)
|
||||||
|
self.assertIsNone(self.listitem.get_child())
|
||||||
|
|
||||||
|
|
||||||
|
class TestSummaryFactory(unittest.TestCase):
|
||||||
|
"""Tests our Gtk.Factory to show Xfstests results summaries."""
|
||||||
|
|
||||||
|
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-1")
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
"""Test that the factory was initialized correctly."""
|
||||||
|
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
|
||||||
|
self.assertEqual(self.factory.xunit, "xunit-1")
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
"""Test that the factory implements the 'setup' signal."""
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.assertIsInstance(self.listitem.get_child(), Gtk.Label)
|
||||||
|
self.assertTrue(self.listitem.get_child().has_css_class("numeric"))
|
||||||
|
|
||||||
|
def test_bind_passed(self):
|
||||||
|
"""Test binding to the passed tests summary."""
|
||||||
|
self.summary.add_xunit("xunit-1", 1, "testcase")
|
||||||
|
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.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
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.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
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.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
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.assertEqual(self.listitem.get_child().get_text(), "")
|
||||||
|
self.assertFalse(self.listitem.get_child().has_css_class("accent"))
|
||||||
|
|
||||||
|
def test_teardown(self):
|
||||||
|
"""Test that the factory implements the 'teardown' signal."""
|
||||||
|
self.factory.emit("setup", self.listitem)
|
||||||
|
self.factory.emit("teardown", self.listitem)
|
||||||
|
self.assertIsNone(self.listitem.get_child())
|
278
tests/gtk/test_view.py
Normal file
278
tests/gtk/test_view.py
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
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.xfstestsdb = xfstestsdb.Command()
|
||||||
|
with unittest.mock.patch("sys.stdout"):
|
||||||
|
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||||
|
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-1",
|
||||||
|
"1", str(tests.xunit.XUNIT_1)])
|
||||||
|
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-2",
|
||||||
|
"1", str(tests.xunit.XUNIT_1)])
|
||||||
|
|
||||||
|
self.model = xfstestsdb.gtk.model.TestCaseList(self.xfstestsdb.sql, 1)
|
||||||
|
self.view = xfstestsdb.gtk.view.TestCaseView()
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
"""Test that we create the TestCaseView correctly."""
|
||||||
|
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
|
||||||
|
self.assertTrue(self.view.has_css_class("card"))
|
||||||
|
self.assertIsNone(self.view.model)
|
||||||
|
|
||||||
|
def test_columnview(self):
|
||||||
|
"""Test that we set up the Columnview correctly."""
|
||||||
|
self.assertIsInstance(self.view.props.child, Gtk.ColumnView)
|
||||||
|
self.assertIsInstance(self.view.props.child.get_model(),
|
||||||
|
Gtk.NoSelection)
|
||||||
|
self.assertEqual(len(self.view.props.child.get_columns()), 0)
|
||||||
|
self.assertTrue(self.view.props.child.get_show_column_separators())
|
||||||
|
self.assertTrue(self.view.props.child.get_show_row_separators())
|
||||||
|
self.assertTrue(self.view.props.child.get_hexpand())
|
||||||
|
self.assertTrue(self.view.props.child.get_vexpand())
|
||||||
|
|
||||||
|
def test_filter(self):
|
||||||
|
"""Test that we set up the Gtk.FilterModel and Buttons correctly."""
|
||||||
|
self.assertIsInstance(self.view._filtermodel, Gtk.FilterListModel)
|
||||||
|
self.assertIsInstance(self.view._testfilter,
|
||||||
|
xfstestsdb.gtk.model.TestCaseFilter)
|
||||||
|
self.assertIsInstance(self.view.filterbuttons,
|
||||||
|
xfstestsdb.gtk.view.FilterButtons)
|
||||||
|
|
||||||
|
self.assertEqual(self.view.props.child.get_model().get_model(),
|
||||||
|
self.view._filtermodel)
|
||||||
|
self.assertEqual(self.view._filtermodel.props.filter,
|
||||||
|
self.view._testfilter)
|
||||||
|
|
||||||
|
self.assertFalse(self.view._testfilter.passed)
|
||||||
|
self.view.filterbuttons.passed = True
|
||||||
|
self.assertTrue(self.view._testfilter.passed)
|
||||||
|
|
||||||
|
self.assertFalse(self.view._testfilter.skipped)
|
||||||
|
self.view.filterbuttons.skipped = True
|
||||||
|
self.assertTrue(self.view._testfilter.skipped)
|
||||||
|
|
||||||
|
self.assertTrue(self.view._testfilter.failure)
|
||||||
|
self.view.filterbuttons.failure = False
|
||||||
|
self.assertFalse(self.view._testfilter.failure)
|
||||||
|
|
||||||
|
def test_testcase_column(self):
|
||||||
|
"""Test that we set up the 'testcase' column correctly."""
|
||||||
|
self.assertIsInstance(self.view._testcase, Gtk.ColumnViewColumn)
|
||||||
|
self.assertIsInstance(self.view._testcase.props.factory,
|
||||||
|
xfstestsdb.gtk.row.LabelFactory)
|
||||||
|
self.assertEqual(self.view._testcase.props.factory.property, "name")
|
||||||
|
self.assertEqual(self.view._testcase.props.title, "testcase")
|
||||||
|
self.assertFalse(self.view._testcase.props.expand)
|
||||||
|
|
||||||
|
def test_model(self):
|
||||||
|
"""Test setting the model property."""
|
||||||
|
self.view.model = self.model
|
||||||
|
self.assertEqual(self.view._filtermodel.props.model, self.model)
|
||||||
|
|
||||||
|
columns = self.view.props.child.get_columns()
|
||||||
|
self.assertEqual(len(columns), 3)
|
||||||
|
self.assertEqual(columns[0], self.view._testcase)
|
||||||
|
|
||||||
|
for i, title in enumerate(["xunit-1", "xunit-2"], start=1):
|
||||||
|
with self.subTest(i=i, title=title):
|
||||||
|
self.assertIsInstance(columns[i].props.factory,
|
||||||
|
xfstestsdb.gtk.row.ResultFactory)
|
||||||
|
self.assertEqual(columns[i].props.factory.xunit, title)
|
||||||
|
self.assertEqual(columns[i].props.title, title)
|
||||||
|
self.assertTrue(columns[i].props.expand)
|
||||||
|
|
||||||
|
self.view.model = None
|
||||||
|
self.assertEqual(len(self.view.props.child.get_columns()), 0)
|
||||||
|
|
||||||
|
self.view.model = self.model
|
||||||
|
self.assertEqual(self.view.props.child.get_columns()[0],
|
||||||
|
self.view._testcase)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSummaryView(unittest.TestCase):
|
||||||
|
"""Tests the SummaryView."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up common variables."""
|
||||||
|
self.xfstestsdb = xfstestsdb.Command()
|
||||||
|
with unittest.mock.patch("sys.stdout"):
|
||||||
|
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||||
|
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-1",
|
||||||
|
"1", str(tests.xunit.XUNIT_1)])
|
||||||
|
self.xfstestsdb.run(["xunit", "read", "--name", "xunit-2",
|
||||||
|
"1", str(tests.xunit.XUNIT_1)])
|
||||||
|
|
||||||
|
self.model = xfstestsdb.gtk.model.SummaryList(self.xfstestsdb.sql, 1)
|
||||||
|
self.view = xfstestsdb.gtk.view.SummaryView()
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
"""Test that we created the SummaryView correctly."""
|
||||||
|
self.assertIsInstance(self.view, Gtk.ScrolledWindow)
|
||||||
|
self.assertEqual(self.view.props.vscrollbar_policy,
|
||||||
|
Gtk.PolicyType.NEVER)
|
||||||
|
self.assertTrue(self.view.has_css_class("card"))
|
||||||
|
self.assertIsNone(self.view.model)
|
||||||
|
|
||||||
|
def test_columnview(self):
|
||||||
|
"""Test that we set up the Columnview correctly."""
|
||||||
|
self.assertIsInstance(self.view.props.child, Gtk.ColumnView)
|
||||||
|
self.assertIsInstance(self.view.props.child.get_model(),
|
||||||
|
Gtk.NoSelection)
|
||||||
|
self.assertEqual(len(self.view.props.child.get_columns()), 0)
|
||||||
|
self.assertTrue(self.view.props.child.get_show_column_separators())
|
||||||
|
self.assertTrue(self.view.props.child.get_show_row_separators())
|
||||||
|
self.assertTrue(self.view.props.child.get_hexpand())
|
||||||
|
|
||||||
|
def test_summary_column(self):
|
||||||
|
"""Test that we set up the 'summary' column correctly."""
|
||||||
|
self.assertIsInstance(self.view._summary, Gtk.ColumnViewColumn)
|
||||||
|
self.assertIsInstance(self.view._summary.props.factory,
|
||||||
|
xfstestsdb.gtk.row.LabelFactory)
|
||||||
|
self.assertEqual(self.view._summary.props.factory.property, "name")
|
||||||
|
self.assertEqual(self.view._summary.props.title, "summary")
|
||||||
|
self.assertFalse(self.view._summary.props.expand)
|
||||||
|
|
||||||
|
def test_model(self):
|
||||||
|
"""Test setting the model property."""
|
||||||
|
self.view.model = self.model
|
||||||
|
self.assertEqual(self.view.props.child.get_model().get_model(),
|
||||||
|
self.model)
|
||||||
|
|
||||||
|
columns = self.view.props.child.get_columns()
|
||||||
|
self.assertEqual(len(columns), 3)
|
||||||
|
self.assertEqual(columns[0], self.view._summary)
|
||||||
|
|
||||||
|
for i, title in enumerate(["xunit-1", "xunit-2"], start=1):
|
||||||
|
with self.subTest(i=i, title=title):
|
||||||
|
self.assertIsInstance(columns[i].props.factory,
|
||||||
|
xfstestsdb.gtk.row.SummaryFactory)
|
||||||
|
self.assertEqual(columns[i].props.factory.xunit, title)
|
||||||
|
self.assertEqual(columns[i].props.title, title)
|
||||||
|
self.assertTrue(columns[i].props.expand)
|
||||||
|
|
||||||
|
self.view.model = None
|
||||||
|
self.assertEqual(len(self.view.props.child.get_columns()), 0)
|
||||||
|
|
||||||
|
self.view.model = self.model
|
||||||
|
self.assertEqual(self.view.props.child.get_columns()[0],
|
||||||
|
self.view._summary)
|
||||||
|
|
||||||
|
|
||||||
|
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.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_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.get_first_child(), self.view._testcaseview)
|
||||||
|
|
||||||
|
def test_summary_view(self):
|
||||||
|
"""Check that the XfstestsView sets up a SummaryView correctly."""
|
||||||
|
sep = self.view._testcaseview.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_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)
|
66
tests/gtk/test_window.py
Normal file
66
tests/gtk/test_window.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# 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, 800)
|
||||||
|
self.assertEqual(self.window.props.default_width, 1200)
|
||||||
|
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_child(self):
|
||||||
|
"""Test the window child property."""
|
||||||
|
self.assertIsInstance(self.window.headerbar.get_next_sibling(),
|
||||||
|
Adw.Bin)
|
||||||
|
|
||||||
|
self.assertIsNone(self.window.child)
|
||||||
|
self.window.child = Gtk.Label()
|
||||||
|
self.assertEqual(self.window.headerbar.get_next_sibling().props.child,
|
||||||
|
self.window.child)
|
||||||
|
|
||||||
|
label = Gtk.Label()
|
||||||
|
window2 = xfstestsdb.gtk.window.Window(child=label)
|
||||||
|
self.assertEqual(window2.child, label)
|
||||||
|
self.assertEqual(window2.headerbar.get_next_sibling().props.child,
|
||||||
|
label)
|
||||||
|
|
||||||
|
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, "")
|
165
tests/test_gtk.py
Normal file
165
tests/test_gtk.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
# 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.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.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 = ["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.model,
|
||||||
|
xfstestsdb.gtk.model.TestCaseList)
|
||||||
|
self.assertIsInstance(self.application.summary,
|
||||||
|
xfstestsdb.gtk.model.SummaryList)
|
||||||
|
self.assertEqual(self.application.model.runid, 42)
|
||||||
|
|
||||||
|
@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.view)
|
||||||
|
|
||||||
|
self.application.emit("startup")
|
||||||
|
|
||||||
|
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)
|
||||||
|
mock_startup.assert_called_with(self.application)
|
||||||
|
mock_add_window.assert_called_with(self.application.win)
|
||||||
|
mock_add_style.assert_called()
|
||||||
|
|
||||||
|
self.application.runid = 42
|
||||||
|
self.assertEqual(self.application.win.runid, 42)
|
||||||
|
|
||||||
|
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."""
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
self.xfstestsdb.run(["gtk"])
|
||||||
|
|
||||||
|
self.assertRegex(mock_stderr.getvalue(),
|
||||||
|
"error: the following arguments are required: runid")
|
||||||
|
|
||||||
|
@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")
|
|
@ -32,7 +32,7 @@ class TestXfstestsdb(unittest.TestCase):
|
||||||
self.xfstestsdb.run([])
|
self.xfstestsdb.run([])
|
||||||
self.assertRegex(mock_stdout.getvalue(),
|
self.assertRegex(mock_stdout.getvalue(),
|
||||||
r"^usage: pytest \[\-h\] \[\-\-version\]"
|
r"^usage: pytest \[\-h\] \[\-\-version\]"
|
||||||
r"\s+\{.*?} ...$")
|
r"\s+\{.*?}\s+...$")
|
||||||
|
|
||||||
def test_run(self):
|
def test_run(self):
|
||||||
"""Test running the xfstestsdb."""
|
"""Test running the xfstestsdb."""
|
||||||
|
@ -52,7 +52,7 @@ class TestXfstestsdb(unittest.TestCase):
|
||||||
def test_version(self, mock_stdout: io.StringIO):
|
def test_version(self, mock_stdout: io.StringIO):
|
||||||
"""Test printing version information."""
|
"""Test printing version information."""
|
||||||
self.assertEqual(xfstestsdb.MAJOR, 1)
|
self.assertEqual(xfstestsdb.MAJOR, 1)
|
||||||
self.assertEqual(xfstestsdb.MINOR, 2)
|
self.assertEqual(xfstestsdb.MINOR, 3)
|
||||||
|
|
||||||
self.xfstestsdb.run(["--version"])
|
self.xfstestsdb.run(["--version"])
|
||||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.2-debug\n")
|
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.3-debug\n")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import argparse
|
||||||
from . import sqlite
|
from . import sqlite
|
||||||
from . import delete
|
from . import delete
|
||||||
from . import gc
|
from . import gc
|
||||||
|
from . import gtk
|
||||||
from . import list
|
from . import list
|
||||||
from . import new
|
from . import new
|
||||||
from . import rename
|
from . import rename
|
||||||
|
@ -14,7 +15,7 @@ from . import untag
|
||||||
from . import xunit
|
from . import xunit
|
||||||
|
|
||||||
MAJOR = 1
|
MAJOR = 1
|
||||||
MINOR = 2
|
MINOR = 3
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
|
@ -30,6 +31,7 @@ class Command:
|
||||||
self.sql = sqlite.Connection()
|
self.sql = sqlite.Connection()
|
||||||
self.commands = {"delete": delete.Command(self.subparser, self.sql),
|
self.commands = {"delete": delete.Command(self.subparser, self.sql),
|
||||||
"gc": gc.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),
|
"list": list.Command(self.subparser, self.sql),
|
||||||
"new": new.Command(self.subparser, self.sql),
|
"new": new.Command(self.subparser, self.sql),
|
||||||
"rename": rename.Command(self.subparser, self.sql),
|
"rename": rename.Command(self.subparser, self.sql),
|
||||||
|
|
95
xfstestsdb/gtk/__init__.py
Normal file
95
xfstestsdb/gtk/__init__.py
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# 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 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)
|
||||||
|
summary = GObject.Property(type=model.SummaryList)
|
||||||
|
model = GObject.Property(type=model.TestCaseList)
|
||||||
|
win = GObject.Property(type=window.Window)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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])
|
||||||
|
self.model = model.TestCaseList(self.sql, self.runid)
|
||||||
|
self.summary = model.SummaryList(self.sql, self.runid)
|
||||||
|
|
||||||
|
self.activate()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def do_startup(self) -> None:
|
||||||
|
"""Handle the Adw.Application::startup signal."""
|
||||||
|
Adw.Application.do_startup(self)
|
||||||
|
gsetup.add_style()
|
||||||
|
|
||||||
|
self.view = view.XfstestsView()
|
||||||
|
self.win = window.Window(child=self.view)
|
||||||
|
self.win.headerbar.pack_end(self.view.filterbuttons)
|
||||||
|
|
||||||
|
self.bind_property("runid", self.win, "runid")
|
||||||
|
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=1, type=int,
|
||||||
|
help="show a specific xfstests run")
|
||||||
|
|
||||||
|
def do_command(self, args: argparse.Namespace) -> None:
|
||||||
|
"""Run the Gtk UI."""
|
||||||
|
app_args = []
|
||||||
|
|
||||||
|
if self.sql("SELECT 1 FROM xfstests_runs WHERE runid=?",
|
||||||
|
args.runid[0]).fetchone():
|
||||||
|
app_args.append(f"runid={args.runid[0]}")
|
||||||
|
else:
|
||||||
|
print(f"error: run #{args.runid[0]} does not exist",
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(errno.ENOENT)
|
||||||
|
|
||||||
|
Application(self.sql).run(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/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.
8
xfstestsdb/gtk/icons/xfstestsdb.gresource.xml
Normal file
8
xfstestsdb/gtk/icons/xfstestsdb.gresource.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?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>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
216
xfstestsdb/gtk/model.py
Normal file
216
xfstestsdb/gtk/model.py
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""Our Testcase Gio.ListModel."""
|
||||||
|
import typing
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Gio
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from .. import sqlite
|
||||||
|
|
||||||
|
|
||||||
|
class XunitResult(GObject.GObject):
|
||||||
|
"""The results for a single TestCase with a specific Xunit."""
|
||||||
|
|
||||||
|
name = GObject.Property(type=str)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCase(GObject.GObject):
|
||||||
|
"""Collects results for a single TestCase with multiple Xunits."""
|
||||||
|
|
||||||
|
name = GObject.Property(type=str)
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
"""Initialize a TestCase object."""
|
||||||
|
super().__init__(name=name)
|
||||||
|
self.__xunits = {}
|
||||||
|
|
||||||
|
def __getitem__(self, xunit: str) -> XunitResult | None:
|
||||||
|
"""Get the results for a specific Xunit."""
|
||||||
|
return self.__xunits.get(xunit)
|
||||||
|
|
||||||
|
def __lt__(self, rhs: typing.Self) -> bool:
|
||||||
|
"""Compare the names of two TestCases."""
|
||||||
|
return self.name < rhs.name
|
||||||
|
|
||||||
|
def add_xunit(self, name: str, status: str, time: int,
|
||||||
|
message: str | None, stdout: str | None,
|
||||||
|
stderr: str | None) -> None:
|
||||||
|
"""Add an xunit result to the TestCase."""
|
||||||
|
message = "" if message is None else message
|
||||||
|
stdout = "" if stdout is None else stdout
|
||||||
|
stderr = "" if stderr is None else stderr
|
||||||
|
self.__xunits[name] = XunitResult(name=name, status=status, time=time,
|
||||||
|
message=message, stdout=stdout,
|
||||||
|
stderr=stderr)
|
||||||
|
|
||||||
|
def get_results(self) -> set[str]:
|
||||||
|
"""Get a set of results for each added xunit."""
|
||||||
|
return {xunit.status for xunit in self.__xunits.values()}
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseList(GObject.GObject, Gio.ListModel):
|
||||||
|
"""A list of TestCases 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 a TestCaseList."""
|
||||||
|
super().__init__(runid=runid)
|
||||||
|
self.__xunits = set()
|
||||||
|
|
||||||
|
cases = {}
|
||||||
|
cur = sql("""SELECT testcase, xunit, status,
|
||||||
|
time, message, stdout, stderr
|
||||||
|
FROM testcases_view WHERE runid=?""", runid)
|
||||||
|
for row in cur.fetchall():
|
||||||
|
testcase = cases.setdefault(row["testcase"],
|
||||||
|
TestCase(row["testcase"]))
|
||||||
|
testcase.add_xunit(row["xunit"], row["status"], row["time"],
|
||||||
|
row["message"], row["stdout"], row["stderr"])
|
||||||
|
self.__xunits.add(row["xunit"])
|
||||||
|
|
||||||
|
self.__items = sorted(cases.values())
|
||||||
|
self.n_items = len(self.__items)
|
||||||
|
|
||||||
|
def do_get_item_type(self) -> GObject.GType:
|
||||||
|
"""Get the type of the objects in the list."""
|
||||||
|
return TestCase.__gtype__
|
||||||
|
|
||||||
|
def do_get_n_items(self) -> int:
|
||||||
|
"""Get the number of items in the list."""
|
||||||
|
return self.n_items
|
||||||
|
|
||||||
|
def do_get_item(self, n: int) -> TestCase | None:
|
||||||
|
"""Get a specific item on the list."""
|
||||||
|
return self.__items[n] if n < self.n_items else None
|
||||||
|
|
||||||
|
def get_xunits(self) -> list[str]:
|
||||||
|
"""Get a list of xunits attached to this xfstests run."""
|
||||||
|
return sorted(self.__xunits)
|
||||||
|
|
||||||
|
|
||||||
|
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(GObject.GObject):
|
||||||
|
"""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(GObject.GObject):
|
||||||
|
"""Collects values for each summary field with multiple Xunits."""
|
||||||
|
|
||||||
|
name = GObject.Property(type=str)
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
"""Initialize a Summary object."""
|
||||||
|
super().__init__(name=name)
|
||||||
|
self.__xunits = {}
|
||||||
|
|
||||||
|
def __getitem__(self, xunit: str) -> SummaryValue | None:
|
||||||
|
"""Get the summary for a specific Xunit."""
|
||||||
|
return self.__xunits.get(xunit)
|
||||||
|
|
||||||
|
def __lt__(self, rhs: typing.Self) -> bool:
|
||||||
|
"""Compare the fields of two Summaries."""
|
||||||
|
order = ["passed", "failed", "skipped", "time"]
|
||||||
|
return order.index(self.name) < order.index(rhs.name)
|
||||||
|
|
||||||
|
def add_xunit(self, name: str, value: int, unit: str) -> None:
|
||||||
|
"""Add an xunit summary to the Summary."""
|
||||||
|
self.__xunits[name] = SummaryValue(name=name, value=value, unit=unit)
|
||||||
|
|
||||||
|
def get_results(self) -> set[str]:
|
||||||
|
"""Get a set of results for each added xunit."""
|
||||||
|
return {str(value) for value in self.__xunits.values()}
|
||||||
|
|
||||||
|
|
||||||
|
class SummaryList(GObject.GObject, Gio.ListModel):
|
||||||
|
"""A list summarizing the results of 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 a SummaryList."""
|
||||||
|
super().__init__(runid=runid)
|
||||||
|
self.__xunits = set()
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
cur = sql("""SELECT name AS xunit, passed, failed, skipped, time
|
||||||
|
FROM xunits_view WHERE runid=?""", runid)
|
||||||
|
for row in cur.fetchall():
|
||||||
|
for field in ["passed", "failed", "skipped", "time"]:
|
||||||
|
summary = results.setdefault(field, Summary(field))
|
||||||
|
summary.add_xunit(row["xunit"], row[field],
|
||||||
|
"second" if field == "time" else "testcase")
|
||||||
|
self.__xunits.add(row["xunit"])
|
||||||
|
|
||||||
|
self.__items = sorted(results.values())
|
||||||
|
self.n_items = len(self.__items)
|
||||||
|
|
||||||
|
def do_get_item_type(self) -> GObject.GType:
|
||||||
|
"""Get the type of the objects in the list."""
|
||||||
|
return Summary.__gtype__
|
||||||
|
|
||||||
|
def do_get_n_items(self) -> int:
|
||||||
|
"""Get the number of items in the list."""
|
||||||
|
return self.n_items
|
||||||
|
|
||||||
|
def do_get_item(self, n: int) -> Summary | None:
|
||||||
|
"""Get a specific item on the list."""
|
||||||
|
return self.__items[n] if n < self.n_items else None
|
||||||
|
|
||||||
|
def get_xunits(self) -> list[str]:
|
||||||
|
"""Get a list of xunits attached to this xfstests run."""
|
||||||
|
return sorted(self.__xunits)
|
135
xfstestsdb/gtk/row.py
Normal file
135
xfstestsdb/gtk/row.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""Our TestCase row widgets and factory."""
|
||||||
|
import typing
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
|
||||||
|
STYLES = {"passed": "success", "failed": "error",
|
||||||
|
"skipped": "warning", "time": "accent"}
|
||||||
|
|
||||||
|
|
||||||
|
class LabelFactory(Gtk.SignalListItemFactory):
|
||||||
|
"""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)
|
||||||
|
self.connect("setup", self.do_setup)
|
||||||
|
self.connect("bind", self.do_bind)
|
||||||
|
self.connect("unbind", self.do_unbind)
|
||||||
|
self.connect("teardown", self.do_teardown)
|
||||||
|
|
||||||
|
def do_setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Set up a ListItem child widget."""
|
||||||
|
child = Gtk.Label()
|
||||||
|
child.add_css_class("numeric")
|
||||||
|
listitem.set_child(child)
|
||||||
|
LabelFactory.group.add_widget(child)
|
||||||
|
|
||||||
|
def do_bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Bind a ListItem to the child widget."""
|
||||||
|
text = listitem.get_item().get_property(self.property)
|
||||||
|
child = listitem.get_child()
|
||||||
|
if style := STYLES.get(text):
|
||||||
|
child.add_css_class(style)
|
||||||
|
child.set_text(text)
|
||||||
|
|
||||||
|
def do_unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Unbind a ListItem from the child widget."""
|
||||||
|
child = listitem.get_child()
|
||||||
|
for style in STYLES.values():
|
||||||
|
child.remove_css_class(style)
|
||||||
|
child.set_text("")
|
||||||
|
|
||||||
|
def do_teardown(self, factory: typing.Self,
|
||||||
|
listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Clean up a ListItem child widget."""
|
||||||
|
if (child := listitem.get_child()) is not None:
|
||||||
|
LabelFactory.group.remove_widget(child)
|
||||||
|
listitem.set_child(None)
|
||||||
|
|
||||||
|
|
||||||
|
class ResultFactory(Gtk.SignalListItemFactory):
|
||||||
|
"""Factory for making test result widgets."""
|
||||||
|
|
||||||
|
xunit = GObject.Property(type=str)
|
||||||
|
|
||||||
|
def __init__(self, xunit: str):
|
||||||
|
"""Initialize our ResultFactory."""
|
||||||
|
super().__init__(xunit=xunit)
|
||||||
|
self.connect("setup", self.do_setup)
|
||||||
|
self.connect("bind", self.do_bind)
|
||||||
|
self.connect("unbind", self.do_unbind)
|
||||||
|
self.connect("teardown", self.do_teardown)
|
||||||
|
|
||||||
|
def do_setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Set up a ListItem child widget."""
|
||||||
|
listitem.set_child(Gtk.Label())
|
||||||
|
listitem.get_child().add_css_class("numeric")
|
||||||
|
|
||||||
|
def do_bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Bind a ListItem to the child widget."""
|
||||||
|
if (result := listitem.get_item()[self.xunit]) is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if (text := result.status) == "passed":
|
||||||
|
text = f"{result.time} seconds"
|
||||||
|
|
||||||
|
child = listitem.get_child()
|
||||||
|
child.set_text(text)
|
||||||
|
child.set_tooltip_text(result.message.lstrip(" -"))
|
||||||
|
child.get_parent().add_css_class(result.status)
|
||||||
|
|
||||||
|
def do_unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Unbind a ListItem from the child widget."""
|
||||||
|
if (result := listitem.get_item()[self.xunit]) is not None:
|
||||||
|
child = listitem.get_child()
|
||||||
|
child.set_text("")
|
||||||
|
child.get_parent().remove_css_class(result.status)
|
||||||
|
|
||||||
|
def do_teardown(self, factory: typing.Self,
|
||||||
|
listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Clean up a ListItem child widget."""
|
||||||
|
listitem.set_child(None)
|
||||||
|
|
||||||
|
|
||||||
|
class SummaryFactory(Gtk.SignalListItemFactory):
|
||||||
|
"""Factory for making test summary widgets."""
|
||||||
|
|
||||||
|
xunit = GObject.Property(type=str)
|
||||||
|
|
||||||
|
def __init__(self, xunit: str):
|
||||||
|
"""Initialize our ResultFactory."""
|
||||||
|
super().__init__(xunit=xunit)
|
||||||
|
self.connect("setup", self.do_setup)
|
||||||
|
self.connect("bind", self.do_bind)
|
||||||
|
self.connect("unbind", self.do_unbind)
|
||||||
|
self.connect("teardown", self.do_teardown)
|
||||||
|
|
||||||
|
def do_setup(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Set up a ListItem child widget."""
|
||||||
|
listitem.set_child(Gtk.Label())
|
||||||
|
listitem.get_child().add_css_class("numeric")
|
||||||
|
|
||||||
|
def do_bind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Bind a ListItem to the child widget."""
|
||||||
|
summary = listitem.get_item()
|
||||||
|
result = summary[self.xunit]
|
||||||
|
child = listitem.get_child()
|
||||||
|
child.set_text(str(result))
|
||||||
|
child.add_css_class(STYLES[summary.name])
|
||||||
|
|
||||||
|
def do_unbind(self, factory: typing.Self, listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Unbind a ListItem from the child widget."""
|
||||||
|
child = listitem.get_child()
|
||||||
|
child.set_text("")
|
||||||
|
child.remove_css_class(STYLES[listitem.get_item().name])
|
||||||
|
|
||||||
|
def do_teardown(self, factory: typing.Self,
|
||||||
|
listitem: Gtk.ListItem) -> None:
|
||||||
|
"""Clean up a ListItem child widget."""
|
||||||
|
listitem.set_child(None)
|
146
xfstestsdb/gtk/view.py
Normal file
146
xfstestsdb/gtk/view.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""A view widget used to display our TestCaseModel."""
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from .model import TestCaseList
|
||||||
|
from .model import TestCaseFilter
|
||||||
|
from .model import SummaryList
|
||||||
|
from . import button
|
||||||
|
from . import row
|
||||||
|
|
||||||
|
|
||||||
|
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(Gtk.ScrolledWindow):
|
||||||
|
"""Displays our TestCaseList model to the user."""
|
||||||
|
|
||||||
|
filterbuttons = GObject.Property(type=FilterButtons)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize a TestCaseView."""
|
||||||
|
super().__init__(child=Gtk.ColumnView(model=Gtk.NoSelection(),
|
||||||
|
show_row_separators=True,
|
||||||
|
show_column_separators=True,
|
||||||
|
hexpand=True, vexpand=True),
|
||||||
|
filterbuttons=FilterButtons())
|
||||||
|
self._testcase = Gtk.ColumnViewColumn(title="testcase",
|
||||||
|
factory=row.LabelFactory("name"))
|
||||||
|
|
||||||
|
self._testfilter = TestCaseFilter()
|
||||||
|
self._filtermodel = Gtk.FilterListModel(filter=self._testfilter)
|
||||||
|
|
||||||
|
for prop in ["passed", "skipped", "failure"]:
|
||||||
|
self.filterbuttons.bind_property(prop, self._testfilter, prop)
|
||||||
|
|
||||||
|
self.props.child.get_model().set_model(self._filtermodel)
|
||||||
|
self.add_css_class("card")
|
||||||
|
|
||||||
|
def __xunit_column(self, xunit: str) -> None:
|
||||||
|
return Gtk.ColumnViewColumn(title=xunit, expand=True,
|
||||||
|
factory=row.ResultFactory(xunit))
|
||||||
|
|
||||||
|
def make_buttons(self) -> FilterButtons:
|
||||||
|
"""Make a new FilterButtons instance connected to this View."""
|
||||||
|
return FilterButtons()
|
||||||
|
|
||||||
|
@GObject.Property(type=TestCaseList)
|
||||||
|
def model(self) -> TestCaseList:
|
||||||
|
"""Get the TestCaseList shown by the View."""
|
||||||
|
return self._filtermodel.props.model
|
||||||
|
|
||||||
|
@model.setter
|
||||||
|
def model(self, new: TestCaseList) -> None:
|
||||||
|
for col in [col for col in self.props.child.get_columns()]:
|
||||||
|
self.props.child.remove_column(col)
|
||||||
|
|
||||||
|
self._filtermodel.props.model = new
|
||||||
|
|
||||||
|
if new is not None:
|
||||||
|
self.props.child.append_column(self._testcase)
|
||||||
|
for xunit in new.get_xunits():
|
||||||
|
self.props.child.append_column(self.__xunit_column(xunit))
|
||||||
|
|
||||||
|
|
||||||
|
class SummaryView(Gtk.ScrolledWindow):
|
||||||
|
"""Displays our SummaryList model to the user."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize a SummaryView."""
|
||||||
|
super().__init__(child=Gtk.ColumnView(model=Gtk.NoSelection(),
|
||||||
|
show_row_separators=True,
|
||||||
|
show_column_separators=True,
|
||||||
|
hexpand=True),
|
||||||
|
vscrollbar_policy=Gtk.PolicyType.NEVER)
|
||||||
|
self._summary = Gtk.ColumnViewColumn(title="summary",
|
||||||
|
factory=row.LabelFactory("name"))
|
||||||
|
self.add_css_class("card")
|
||||||
|
|
||||||
|
def __summary_column(self, xunit: str) -> None:
|
||||||
|
return Gtk.ColumnViewColumn(title=xunit, expand=True,
|
||||||
|
factory=row.SummaryFactory(xunit))
|
||||||
|
|
||||||
|
@GObject.Property(type=SummaryList)
|
||||||
|
def model(self) -> SummaryList:
|
||||||
|
"""Get the SummaryList shown by the View."""
|
||||||
|
return self.props.child.get_model().get_model()
|
||||||
|
|
||||||
|
@model.setter
|
||||||
|
def model(self, new: SummaryList) -> None:
|
||||||
|
for col in [col for col in self.props.child.get_columns()]:
|
||||||
|
self.props.child.remove_column(col)
|
||||||
|
|
||||||
|
self.props.child.get_model().set_model(new)
|
||||||
|
|
||||||
|
if new is not None:
|
||||||
|
self.props.child.append_column(self._summary)
|
||||||
|
for xunit in new.get_xunits():
|
||||||
|
self.props.child.append_column(self.__summary_column(xunit))
|
||||||
|
|
||||||
|
|
||||||
|
class XfstestsView(Gtk.Box):
|
||||||
|
"""A widget to display the results of an Xfstests runs."""
|
||||||
|
|
||||||
|
model = GObject.Property(type=TestCaseList)
|
||||||
|
summary = GObject.Property(type=SummaryList)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize an XfstestsView."""
|
||||||
|
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
self._testcaseview = TestCaseView()
|
||||||
|
self._summaryview = SummaryView()
|
||||||
|
|
||||||
|
self.bind_property("model", self._testcaseview, "model")
|
||||||
|
self.bind_property("summary", self._summaryview, "model")
|
||||||
|
|
||||||
|
self.append(self._testcaseview)
|
||||||
|
self.append(Gtk.Separator())
|
||||||
|
self.append(self._summaryview)
|
||||||
|
|
||||||
|
@GObject.Property(type=FilterButtons)
|
||||||
|
def filterbuttons(self) -> FilterButtons:
|
||||||
|
"""Get the FilterButtons attached to the child TestCaseView."""
|
||||||
|
return self._testcaseview.filterbuttons
|
38
xfstestsdb/gtk/window.py
Normal file
38
xfstestsdb/gtk/window.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# 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)
|
||||||
|
headerbar = GObject.Property(type=Adw.HeaderBar)
|
||||||
|
title = GObject.Property(type=Adw.WindowTitle)
|
||||||
|
runid = GObject.Property(type=int)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Set up our Window."""
|
||||||
|
super().__init__(default_height=800, default_width=1200,
|
||||||
|
content=Gtk.Box(orientation=Gtk.Orientation.VERTICAL),
|
||||||
|
title=Adw.WindowTitle(title="xfstestsdb gtk"),
|
||||||
|
headerbar=Adw.HeaderBar(), **kwargs)
|
||||||
|
self.headerbar.props.title_widget = self.title
|
||||||
|
self.headerbar.add_css_class("flat")
|
||||||
|
|
||||||
|
self.props.content.append(self.headerbar)
|
||||||
|
self.props.content.append(Adw.Bin(child=self.child))
|
||||||
|
|
||||||
|
self.bind_property("child", self.headerbar.get_next_sibling(), "child")
|
||||||
|
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
|
28
xfstestsdb/gtk/xfstestsdb.css
Normal file
28
xfstestsdb/gtk/xfstestsdb.css
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user