Compare commits

..

No commits in common. "4d36a3384083e7413f0743e893598c295653da5f" and "b6f523e6226ff23f0e4c405940d981490ba9661f" have entirely different histories.

10 changed files with 341 additions and 631 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
__pycache__
*.pyc

View File

@ -2,10 +2,4 @@
set -eo pipefail
let jobs=$(nproc)-2
if [ -f .git/config ] && [ -f .git/HEAD ]; then
REV=$(git rev-parse --short HEAD)$(git diff --quiet || echo "-dirty")
scripts/config --set-str CONFIG_LOCALVERSION "-g$REV"
fi
make -j$jobs $* | python /home/anna/bin/colors/make.py

View File

@ -1,27 +1,355 @@
#!/usr/bin/python
import gi
gi.require_version("Gtk", "4.0")
import bisect
import pathlib
import reporter
import re
import sys
import xdg.BaseDirectory
import xml.etree.ElementTree
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import Gio
XFSTESTS = pathlib.Path(xdg.BaseDirectory.xdg_data_home) / "xfstests"
class Calendar(Gtk.Calendar):
def __init__(self):
Gtk.Calendar.__init__(self, show_day_names=True)
self.connect("next-month", self.view_changed)
self.connect("next-year", self.view_changed)
self.connect("prev-month", self.view_changed)
self.connect("prev-year", self.view_changed)
self.view_changed(self)
def get_month_directory(self):
date = self.get_date()
month = XFSTESTS / f"{date.get_year():04}" / f"{date.get_month():02}"
return month if month.is_dir() else None
def get_selected_day(self):
if month := self.get_month_directory():
day = month / f"{self.get_date().get_day_of_month():02}"
return day if day.is_dir() else None
def view_changed(self, calendar):
self.clear_marks()
if month := self.get_month_directory():
for day in month.iterdir():
self.mark_day(int(day.stem))
self.emit("day-selected")
class ServerRow(Gtk.TreeExpander):
def __init__(self):
Gtk.TreeExpander.__init__(self)
self.set_child(Gtk.Label(xalign=0))
self.get_child().set_state_flags(Gtk.StateFlags.CHECKED, False)
def set_item(self, item):
self.set_list_row(item)
self.get_child().set_text(item.get_item().get_name() if item else "")
class ServerSorter(Gtk.Sorter):
def do_compare(self, a, b):
if a.get_name() < b.get_name():
return Gtk.Ordering.SMALLER
elif a.get_name() > b.get_name():
return Gtk.Ordering.LARGER
return Gtk.Ordering.EQUAL
class ServerWindow(Gtk.ScrolledWindow):
def __init__(self):
Gtk.ScrolledWindow.__init__(self, vexpand=True, hscrollbar_policy=Gtk.PolicyType.NEVER)
self.servers = Gtk.TreeListModel.new(Gtk.DirectoryList(), False, True, self.create_func)
self.sorter = Gtk.TreeListRowSorter.new(ServerSorter())
self.sorted = Gtk.SortListModel.new(self.servers, self.sorter)
self.selection = Gtk.SingleSelection.new(self.sorted)
self.factory = Gtk.SignalListItemFactory()
self.view = Gtk.ListView.new(self.selection, self.factory)
self.selection.set_autoselect(False)
self.factory.connect("setup", self.setup)
self.factory.connect("bind", self.bind)
self.factory.connect("unbind", self.unbind)
self.factory.connect("teardown", self.teardown)
self.selection.connect("selection-changed", self.selection_changed)
self.set_child(self.view)
def selection_changed(self, selection, position, n_items):
treeitem = self.selection.get_selected_item()
file = treeitem.get_item().get_attribute_object("standard::file")
self.emit("test-changed", file if treeitem.get_depth() > 0 else None)
def setup(self, factory, listitem):
listitem.set_child(ServerRow())
def bind(self, factory, listitem):
listitem.get_child().set_item(listitem.get_item())
listitem.set_selectable(listitem.get_item().get_depth() > 0)
def unbind(self, factory, listitem):
listitem.get_child().set_item(None)
def teardown(self, factory, listitem):
listitem.set_child(None)
def create_func(self, item):
file = item.get_attribute_object("standard::file")
root = pathlib.Path(self.servers.get_model().get_file().get_path())
if pathlib.Path(file.get_path()).parent == root:
return Gtk.DirectoryList.new(file=file)
def set_day(self, day):
file = Gio.File.new_for_path(str(day)) if day else None
self.servers.get_model().set_file(file)
@GObject.Signal(arg_types=(Gio.File,))
def test_changed(self, file): pass
class TestCase(GObject.GObject):
def __init__(self, name):
GObject.GObject.__init__(self)
self.name = name
self.tests = dict()
def __getitem__(self, key):
if key == "Test Name":
return self.name
match len(self.tests[key]) if key in self.tests else None:
case None: return "Missing"
case 0: return "Passed"
case 1: return "Skipped"
case _: return "Failed"
def __setitem__(self, key, val):
self.tests[key] = val
def __str__(self):
return self.name
def __lt__(self, rhs):
return self.name < rhs.name
def passed(self):
return False not in [ len(elm) == 0 for elm in self.tests.values() ]
def failed(self):
return 3 in [ len(elm) for elm in self.tests.values() ]
def skipped(self):
return False not in [ len(elm) == 1 for elm in self.tests.values() ]
class TestResultsModel(GObject.GObject, Gio.ListModel):
def __init__(self):
GObject.GObject.__init__(self)
self.tests = [ ]
self.columns = [ "Test Name" ]
def do_get_item_type(self): return GObject.TYPE_PYOBJECT
def do_get_n_items(self): return len(self.tests)
def do_get_item(self, n): return self.tests[n]
def get_n_columns(self): return len(self.columns)
def get_column_name(self, n): return self.columns[n]
def find_testcase(self, name):
i = bisect.bisect(self.tests, name, key=str)
if i > 0 and self.tests[i-1].name == name:
return self.tests[i-1]
self.tests.insert(i, TestCase(name))
return self.tests[i]
def set_tests(self, path):
rm = len(self.tests)
self.columns = [ "Test Name" ]
self.tests.clear()
for file in sorted(path.iterdir()):
self.columns.append(file.stem)
tree = xml.etree.ElementTree.parse(file)
for elm in tree.getroot():
if elm.tag == "testcase":
testcase = self.find_testcase(elm.attrib["name"])
testcase[file.stem] = elm
self.emit("items-changed", 0, rm, len(self.tests))
class TestResultsFilter(Gtk.Filter):
def __init__(self):
Gtk.Filter.__init__(self)
self.show_passing = True
self.show_skipped = True
self.regex = re.compile("")
def set_regex(self, text):
change = Gtk.FilterChange.DIFFERENT
if text in self.regex.pattern:
change = Gtk.FilterChange.LESS_STRICT
elif self.regex.pattern in text:
change = Gtk.FilterChange.MORE_STRICT
self.regex = re.compile(text, re.I)
self.changed(change)
def set_show_passing(self, val):
self.show_passing = val
self.set_show_changed(self.show_passing)
def set_show_skipped(self, val):
self.show_skipped = val
self.set_show_changed(self.show_skipped)
def set_show_changed(self, newval):
match newval:
case True: self.changed(Gtk.FilterChange.LESS_STRICT)
case False: self.changed(Gtk.FilterChange.MORE_STRICT)
def do_match(self, item):
if not self.show_passing and item.passed():
return False
if not self.show_skipped and item.skipped():
return False
if not self.show_passing and not self.show_skipped and not item.failed():
return False
return self.regex.search(item.name) != None
class FilterPopover(Gtk.Popover):
def __init__(self, parent):
Gtk.Popover.__init__(self)
self.set_child(Gtk.Label())
self.set_autohide(False)
self.set_parent(parent)
def popup(self, text):
self.get_child().set_text(text)
super().popup()
class TestResultsFactory(Gtk.SignalListItemFactory):
def __init__(self, column):
Gtk.SignalListItemFactory.__init__(self)
self.column = column
self.connect("setup", self.setup)
self.connect("bind", self.bind)
self.connect("unbind", self.unbind)
self.connect("teardown", self.teardown)
def setup(self, factory, listitem):
listitem.set_child(Gtk.Label(xalign=0))
def bind(self, factory, listitem):
label = listitem.get_child()
text = listitem.get_item()[self.column]
match text:
case "Passed": label.set_markup("<span background='green'> Passed </span>")
case "Skipped": label.set_markup("<span background='orange'> Skipped </span>")
case "Failed": label.set_markup("<span background='red'> Failed </span>")
case _: label.set_text(" " + text)
def unbind(self, factory, listitem):
listitem.get_child().set_text("")
def teardown(self, factory, listitem):
listitem.set_child(None)
class TestWindow(Gtk.ScrolledWindow):
def __init__(self):
Gtk.ScrolledWindow.__init__(self, vexpand=True, hexpand=True,
hscrollbar_policy=Gtk.PolicyType.NEVER)
self.results = TestResultsModel()
self.filter = Gtk.FilterListModel.new(self.results, TestResultsFilter())
self.selection = Gtk.NoSelection.new(self.filter)
self.view = Gtk.ColumnView.new(self.selection)
self.set_child(self.view)
if len(sys.argv) > 1:
self.set_tests(pathlib.Path(sys.argv[1]))
def set_show_passing(self, show):
self.filter.get_filter().set_show_passing(show)
def set_show_skipped(self, show):
self.filter.get_filter().set_show_skipped(show)
def set_filter_regex(self, text):
self.filter.get_filter().set_regex(text)
def set_tests(self, path):
for column in [ c for c in self.view.get_columns() ]:
self.view.remove_column(column)
self.results.set_tests(path)
for n in range(self.results.get_n_columns()):
name = self.results.get_column_name(n)
self.view.append_column(Gtk.ColumnViewColumn.new(name, TestResultsFactory(name)))
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Xfstests Results")
self.chooser = reporter.TestChooser()
self.testview = reporter.TestViewer()
self.child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
self.child.append(self.chooser)
self.child.append(self.testview)
self.calendar = Calendar()
self.servers = ServerWindow()
self.passing = Gtk.Switch(halign=Gtk.Align.CENTER, active=True)
self.lpass = Gtk.Label.new("Show Passing Tests")
self.skipped = Gtk.Switch(halign=Gtk.Align.CENTER, active=True)
self.lskip = Gtk.Label.new("Show Skipped Tests")
self.search = Gtk.SearchEntry(placeholder_text="Type To Filter Tests")
self.popover = FilterPopover(parent=self.search)
self.xfstests = TestWindow()
self.set_default_size(1300, 800)
self.left = Gtk.Grid(row_spacing=5)
self.left.attach(self.calendar, 0, 0, 2, 1)
self.left.attach(self.servers, 0, 1, 2, 1)
self.left.attach(self.passing, 0, 2, 1, 1)
self.left.attach(self.lpass, 1, 2, 1, 1)
self.left.attach(self.skipped, 0, 3, 1, 1)
self.left.attach(self.lskip, 1, 3, 1, 1)
self.right = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
self.right.append(self.search)
self.right.append(self.xfstests)
self.child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
self.child.append(self.left)
self.child.append(self.right)
self.lskip.set_xalign(0)
self.lpass.set_xalign(0)
self.set_default_size(1000, 600)
self.set_child(self.child)
self.chooser.connect("test-selected", self.test_changed)
if len(sys.argv) > 1:
self.testview.set_test_result(reporter.testchooser.Path(pathlib.Path(sys.argv[1])))
self.calendar.connect("day-selected", self.date_changed)
self.servers.connect("test-changed", self.test_changed)
self.passing.connect("notify::active", self.show_passing)
self.skipped.connect("notify::active", self.show_skipped)
self.search.connect("search-changed", self.search_changed)
self.date_changed(self.calendar)
def date_changed(self, calendar):
self.servers.set_day(calendar.get_selected_day())
def test_changed(self, window, file):
self.testview.set_test_result(file)
self.xfstests.set_tests(pathlib.Path(file.get_path()))
def show_passing(self, passing, param):
self.xfstests.set_show_passing(passing.get_active())
def show_skipped(self, skipped, param):
self.xfstests.set_show_skipped(skipped.get_active())
def search_changed(self, search):
try:
self.xfstests.set_filter_regex(search.get_text())
self.search.remove_css_class("warning")
self.popover.popdown()
except re.error as e:
self.search.add_css_class("warning")
self.popover.popup(str(e))
class Application(Gtk.Application):

View File

@ -1,47 +0,0 @@
#!/usr/bin/python
from . import common
from . import testchooser
from . import testproperties
from . import testresults
from . import testviewer
from gi.repository import GObject
from gi.repository import Gtk
SizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL)
class TestChooser(Gtk.Box):
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.stack = testchooser.Stack()
self.switcher = Gtk.StackSwitcher(stack=self.stack)
SizeGroup.add_widget(self.switcher)
self.append(self.switcher)
self.append(self.stack)
self.stack.connect("test-selected", self.on_test_selected)
def on_test_selected(self, stack, path):
self.emit("test-selected", path)
@GObject.Signal(arg_types=(testchooser.Path,))
def test_selected(self, path): pass
class TestViewer(Gtk.Box):
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.stack = testproperties.Stack()
self.viewer = testviewer.Viewer()
self.switcher = Gtk.StackSwitcher(stack=self.stack, halign=Gtk.Align.CENTER)
self.append(self.switcher)
self.append(self.stack)
self.append(self.viewer)
self.append(testviewer.SearchEntry(self.viewer.get_filter()))
SizeGroup.add_widget(self.switcher)
def set_test_result(self, file):
self.stack.clear()
self.viewer.clear()
if file and file.get_is_test_result():
results = testresults.TestResults(file.path)
self.stack.show_properties(results)
self.viewer.show_results(results)

View File

@ -1,14 +0,0 @@
#!/usr/bin/python
import gi
import pathlib
import xdg.BaseDirectory
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk
XFSTESTS_BASE = pathlib.Path(xdg.BaseDirectory.xdg_data_home) / "xfstests"
XFSTESTS_DATE = XFSTESTS_BASE / "date"
XFSTESTS_TAGS = XFSTESTS_BASE / "tags"
SizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL)

View File

@ -1,163 +0,0 @@
#!/usr/bin/python
import pathlib
from . import common
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gio
class Path(GObject.GObject):
def __init__(self, path):
GObject.GObject.__init__(self)
self.path = path
def __lt__(self, rhs): return self.path < rhs.path
def get_is_test_result(self):
return False not in [ f.is_file() for f in self.path.iterdir() ]
def get_name(self): return self.path.name
def get_model(self):
if not self.get_is_test_result():
return DirectoryModel(self.path)
class DirectoryModel(GObject.GObject, Gio.ListModel):
def __init__(self, path=None):
GObject.GObject.__init__(self)
self.children = [ ]
self.set_path(path)
def do_get_item_type(self): return GObject.TYPE_PYOBJECT
def do_get_n_items(self): return len(self.children)
def do_get_item(self, n): return self.children[n]
def set_path(self, path):
rm = len(self.children)
self.children.clear()
self.path = path if path and path.is_dir() else None
if self.path:
self.children = sorted([ Path(c) for c in path.iterdir() ])
self.emit("items-changed", 0, rm, len(self.children))
class DirectoryWindow(Gtk.ScrolledWindow):
def __init__(self, path=None, autoexpand=True):
Gtk.ScrolledWindow.__init__(self, vexpand=True,
hscrollbar_policy=Gtk.PolicyType.NEVER)
self.dirtree = Gtk.TreeListModel.new(root=DirectoryModel(path),
passthrough=False,
autoexpand=autoexpand,
create_func=self.create_func)
self.selection = Gtk.SingleSelection.new(self.dirtree)
self.selection.set_autoselect(False)
self.selection.set_can_unselect(True)
self.selection.connect("selection-changed", self.selection_changed)
self.factory = Gtk.SignalListItemFactory()
self.factory.connect("setup", self.on_setup)
self.factory.connect("bind", self.on_bind)
self.factory.connect("unbind", self.on_unbind)
self.factory.connect("teardown", self.on_teardown)
self.listview = Gtk.ListView.new(self.selection, self.factory)
self.set_child(self.listview)
def create_func(self, path):
return path.get_model()
def on_setup(self, factory, listitem):
listitem.set_child(Gtk.TreeExpander(child=Gtk.Label(xalign=0)))
def on_bind(self, factory, listitem):
treerow = listitem.get_item()
filepath = treerow.get_item()
expander = listitem.get_child()
expander.set_list_row(treerow)
expander.get_child().set_text(filepath.get_name())
listitem.set_selectable(filepath.get_is_test_result())
def on_unbind(self, factory, listitem):
expander = listitem.get_child()
expander.set_list_row(None)
expander.get_child().set_text("")
def on_teardown(self, factory, treeitem):
treeitem.set_child(None)
def set_directory(self, path):
self.dirtree.get_model().set_path(path)
def selection_changed(self, selection, position, n_items):
treeitem = self.selection.get_selected_item()
self.emit("test-selected", treeitem.get_item() if treeitem else None)
def select_none(self):
self.selection.unselect_item(self.selection.get_selected())
@GObject.Signal(arg_types=(Path,))
def test_selected(self, filepath): pass
class Calendar(Gtk.Calendar):
def __init__(self):
Gtk.Calendar.__init__(self, show_day_names=True)
self.connect("next-month", self.view_changed)
self.connect("next-year", self.view_changed)
self.connect("prev-month", self.view_changed)
self.connect("prev-year", self.view_changed)
common.SizeGroup.add_widget(self)
self.view_changed(self)
def get_month_directory(self):
date = self.get_date()
month = common.XFSTESTS_DATE / f"{date.get_year():04}" / f"{date.get_month():02}"
return month if month.is_dir() else None
def get_selected_day(self):
if month := self.get_month_directory():
day = month / f"{self.get_date().get_day_of_month():02}"
return day if day.is_dir() else None
def view_changed(self, calendar):
self.clear_marks()
if month := self.get_month_directory():
for day in month.iterdir():
self.mark_day(int(day.stem))
self.emit("day-selected")
class DatePage(Gtk.Box):
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=5)
self.calendar = Calendar()
self.dirwin = DirectoryWindow(self.calendar.get_selected_day())
self.append(self.calendar)
self.append(self.dirwin)
self.get_first_child().connect("day-selected", self.on_day_selected)
def on_day_selected(self, calendar):
self.dirwin.set_directory(self.calendar.get_selected_day())
def select_none(self):
self.dirwin.select_none()
class Stack(Gtk.Stack):
def __init__(self):
Gtk.Stack.__init__(self, transition_type=Gtk.StackTransitionType.OVER_LEFT_RIGHT)
self.date = DatePage()
self.tags = DirectoryWindow(common.XFSTESTS_TAGS, autoexpand=False)
self.add_titled(self.date, "Date", "Date")
self.add_titled(self.tags, "Tags", "Tags")
self.date.dirwin.connect("test-selected", self.on_test_selected)
self.tags.connect("test-selected", self.on_test_selected)
self.connect("notify::visible-child", self.on_page_changed)
def on_test_selected(self, widget, test):
self.emit("test-selected", test)
def on_page_changed(self, stack, param):
stack.get_visible_child().select_none()
self.emit("test-selected", None)
@GObject.Signal(arg_types=(Path,))
def test_selected(self, path): pass

View File

@ -1,90 +0,0 @@
#!/usr/bin/python
from . import common
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gtk
PROPERTIES = [ "PLATFORM", "TIMESTAMP", "FSTYP", "MOUNT_OPTIONS", "CHECK_OPTIONS",
"TEST_DIR", "TEST_DEV", "SCRATCH_DEV", "SCRATCH_MNT" ] #, "RESULTS" ]
class Property(GObject.GObject):
def __init__(self, key, value):
GObject.GObject.__init__(self)
self.key = key
self.value = value
class Results(Property):
def __init__(self, properties):
total = properties["TESTS"]
time = properties["TIME"]
failed = properties["FAILURES"]
skipped = properties["SKIPPED"]
passed = int(total) - (int(skipped) + int(failed))
Property.__init__(self, "RESULTS",
f"Ran {total} tests in {time} seconds: " \
f"{passed} passed, {failed} failed, {skipped} skipped")
class Model(GObject.GObject, Gio.ListModel):
def __init__(self, properties):
GObject.GObject.__init__(self)
self.properties = [ Property(p, properties[p]) for p in PROPERTIES ]
self.properties.append(Results(properties))
def do_get_item_type(self): return GObject.TYPE_PYOBJECT
def do_get_n_items(self): return len(self.properties)
def do_get_item(self, i): return self.properties[i]
class Factory(Gtk.SignalListItemFactory):
def __init__(self, column):
Gtk.SignalListItemFactory.__init__(self)
self.column = column
self.connect("setup", self.on_setup)
self.connect("bind", self.on_bind)
self.connect("unbind", self.on_unbind)
self.connect("teardown", self.on_teardown)
def on_setup(self, factory, listitem):
listitem.set_child(Gtk.Label(xalign=0))
def on_bind(self, factory, listitem):
label = listitem.get_child()
match self.column:
case "Property": label.set_text(listitem.get_item().key)
case "Value": label.set_text(listitem.get_item().value)
case _: label.set_text("=")
def on_unbind(self, factory, listitem):
listitem.get_child().set_text("")
def on_teardown(self, factory, listitem):
listitem.set_child(None)
class View(Gtk.ColumnView):
def __init__(self, properties):
self.selection = Gtk.NoSelection.new(Model(properties))
Gtk.ColumnView.__init__(self, model=self.selection)
self.add_css_class("data-table")
for title in [ "Property", "=", "Value" ]:
self.append_column(Gtk.ColumnViewColumn.new(title, Factory(title)))
class Stack(Gtk.Stack):
def __init__(self):
Gtk.Stack.__init__(self, transition_type=Gtk.StackTransitionType.OVER_LEFT_RIGHT)
common.SizeGroup.add_widget(self)
def clear(self):
pages = self.get_pages()
children = [ pages.get_item(i).get_child() for i in range(pages.get_n_items()) ]
for child in children:
self.remove(child)
def show_properties(self, results):
for version in results.versions:
window = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER,
child=View(results.properties[version]))
self.add_titled(window, version, version)

View File

@ -1,77 +0,0 @@
#!/usr/bin/python
from gi.repository import GObject
from gi.repository import Gtk
import html
import xml.etree.ElementTree
class PassingTest:
def __init__(self, elm):
self.time = elm.attrib["time"]
def timestr(self):
(m, s) = divmod(int(self.time), 60)
res = [ f"{s} second{'' if s == 1 else 's'}" ]
if m > 0: res.insert(0, f"{m} minute{'' if m == 1 else 's'}")
return ' and '.join(res)
class SkippedTest:
def __init__(self, elm):
self.time = elm.attrib["time"]
self.message = elm[0].attrib["message"]
class FailedTest:
def __init__(self, elm):
self.time = elm.attrib["time"]
self.system_out = Gtk.TextBuffer()
self.system_err = Gtk.TextBuffer()
for e in elm:
match e.tag:
case "failure": self.message = e.attrib["message"]
case "system-out": self.system_out.set_text(html.unescape(e.text))
case "system-err": self.system_err.set_text(html.unescape(e.text))
class TestCase(GObject.GObject):
def __init__(self, name):
GObject.GObject.__init__(self)
self.name = name
self.versions = dict()
def __getitem__(self, key): return self.versions.get(key, None)
def __setitem__(self, key, value): self.versions[key] = value
class TestResults:
def __init__(self, testdir):
self.versions = [ ]
self.tests = dict()
self.properties = dict()
for file in sorted(testdir.iterdir()):
self.versions.append(file.stem)
root = xml.etree.ElementTree.parse(file).getroot()
for prop in root.attrib.keys():
self.set_property(prop, file.stem, root.attrib[prop])
for elm in root:
if elm.tag == "properties":
for prop in elm:
self.set_property(prop.attrib["name"], file.stem,
prop.attrib["value"])
elif elm.tag == "testcase":
if len(elm) == 0:
result = PassingTest(elm)
elif elm[0].tag == "skipped":
result = SkippedTest(elm)
elif elm[0].tag == "failure":
result = FailedTest(elm)
self.add_testcase(elm.attrib["name"], file.stem, result)
def set_property(self, name, vers, value):
self.properties.setdefault(vers, dict())[name.upper()] = value
def add_testcase(self, name, vers, result):
self.tests.setdefault(name, TestCase(name))[vers] = result

View File

@ -1,209 +0,0 @@
#!/usr/bin/python
import itertools
import re
from . import testresults
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gtk
CSS = """
@define-color darkorange #cc7000;
@define-color darkred #8b0000;
label.passing-test {
background-color: green;
padding: 4px;
}
label.skipped-test {
background-color: darkorange;
padding: 4px;
}
menubutton.failed-test>button {
background: darkred;
border-color: darkred;
border-radius: 0px;
padding: 0px;
}
"""
def markup_tooltip(status, color, message):
return f"<span background='{color}'> Test {status} </span> {message}"
class Label(Gtk.Label):
def __init__(self, text):
Gtk.Label.__init__(self, xalign=0)
self.set_text(text)
class PassingTest(Gtk.Label):
def __init__(self, test):
Gtk.Label.__init__(self)
self.add_css_class("passing-test")
self.set_text(f"{test.time} seconds")
self.set_tooltip_markup(markup_tooltip("Passed", "green",
f"in {test.timestr()}"))
class SkippedTest(Gtk.Label):
def __init__(self, test):
Gtk.Label.__init__(self)
self.add_css_class("skipped-test")
self.set_text("Skipped")
self.set_tooltip_markup(markup_tooltip("Skipped", "#cc7000", test.message))
class TestOutput(Gtk.Box):
def __init__(self, sysout, syserr):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.add_css_class("popover")
self.stack = Gtk.Stack(vexpand=True, transition_type=Gtk.StackTransitionType.OVER_LEFT_RIGHT)
self.prepend(self.stack)
self.prepend(Gtk.StackSwitcher(stack=self.stack))
self.add_page("system-out", sysout, visible=False)
self.add_page("system-err", syserr, visible=True)
self.set_size_request(900, 500)
def add_page(self, name, buffer, visible):
view = Gtk.TextView(monospace=True)
view.set_buffer(buffer)
self.stack.add_titled(Gtk.ScrolledWindow(child=view), name, name)
if visible:
self.stack.set_visible_child_name(name)
class FailedTest(Gtk.MenuButton):
def __init__(self, test):
Gtk.MenuButton.__init__(self, label="Failed")
self.set_tooltip_markup(markup_tooltip("Failed", "#8b0000", test.message))
self.set_popover(Gtk.Popover(child=TestOutput(test.system_out, test.system_err)))
self.add_css_class("failed-test")
class MissingTest(Gtk.Label):
def __init__(self, test):
Gtk.Label.__init__(self, sensitive=False)
self.set_name("missing-test")
self.set_text("Missing")
self.set_tooltip_text("Test Data Is Missing")
class Model(GObject.GObject, Gio.ListModel):
def __init__(self, results):
GObject.GObject.__init__(self)
self.results = results
self.columns = [ "Testcase" ] + self.results.versions
self.tests = sorted(results.tests.keys())
def get_columns(self): return self.columns
def do_get_item_type(self): return GObject.TYPE_PYOBJECT
def do_get_n_items(self): return len(self.tests)
def do_get_item(self, i): return self.results.tests[self.tests[i]]
class Filter(Gtk.Filter):
def __init__(self):
Gtk.Filter.__init__(self)
self.regex = re.compile("")
def set_regex(self, text):
change = Gtk.FilterChange.DIFFERENT
if text in self.regex.pattern:
change = Gtk.FilterChange.LESS_STRICT
elif self.regex.pattern in text:
change = Gtk.FilterChange.MORE_STRICT
self.regex = re.compile(text, re.I)
self.changed(change)
def do_match(self, testcase):
search = [ f"name={testcase.name}" ]
for key in sorted(testcase.versions.keys()):
keys = [ key ] + key.split("-")
match type(testcase[key]):
case testresults.PassingTest: res = [ "passing", "passed" ]
case testresults.SkippedTest: res = [ "skipped" ]
case testresults.FailedTest: res = [ "failed", "failing" ]
case _: res = [ "missing" ]
for k in keys:
for r in res:
search.append(f"{k}={r}")
return self.regex.search(" ".join(search)) != None
class Factory(Gtk.SignalListItemFactory):
def __init__(self, column):
Gtk.SignalListItemFactory.__init__(self)
self.column = column
self.connect("bind", self.on_bind)
self.connect("unbind", self.on_unbind)
def on_bind(self, factory, listitem):
testcase = listitem.get_item()
if self.column == "Testcase":
child = Label(testcase.name)
else:
result = testcase[self.column]
match type(result):
case testresults.PassingTest: child = PassingTest(result)
case testresults.SkippedTest: child = SkippedTest(result)
case testresults.FailedTest: child = FailedTest(result)
case _: child = child = MissingTest(result)
listitem.set_child(child)
def on_unbind(self, factory, listitem):
listitem.set_child(None)
class Viewer(Gtk.ScrolledWindow):
def __init__(self):
Gtk.ScrolledWindow.__init__(self, hscrollbar_policy=Gtk.PolicyType.NEVER,
hexpand=True, vexpand=True, margin_top=5)
self.provider = Gtk.CssProvider()
self.filter = Gtk.FilterListModel.new(filter=Filter())
self.selection = Gtk.NoSelection.new(self.filter)
self.columnview = Gtk.ColumnView.new(model=self.selection)
self.columnview.add_css_class("data-table")
self.provider.load_from_data(CSS.encode())
self.get_style_context().add_provider_for_display(self.get_display(), self.provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
self.set_child(self.columnview)
def get_filter(self):
return self.filter.get_filter()
def clear(self):
columns = self.columnview.get_columns()
columns = [ columns.get_item(i) for i in range(columns.get_n_items()) ]
for col in columns:
self.columnview.remove_column(col)
self.filter.set_model(None)
def show_results(self, results):
if results:
self.filter.set_model(Model(results))
for i, name in enumerate(self.filter.get_model().get_columns()):
column = Gtk.ColumnViewColumn.new(name, Factory(name))
column.set_expand(i > 0)
self.columnview.append_column(column)
class SearchEntry(Gtk.SearchEntry):
def __init__(self, filter):
Gtk.SearchEntry.__init__(self, placeholder_text="Type To Filter",
hexpand=True, margin_start=100, margin_end=100,
margin_top=5, margin_bottom=5)
self.popover = Gtk.Popover(child=Gtk.Label(), autohide=False)
self.popover.set_parent(self)
self.connect("search-changed", self.on_search_changed, filter)
def on_search_changed(self, search, filter):
try:
filter.set_regex(search.get_text())
search.remove_css_class("warning")
self.popover.popdown()
except re.error as e:
search.add_css_class("warning")
self.popover.get_child().set_text(str(e))
self.popover.popup()

View File

@ -23,16 +23,10 @@ COLOR=$BIN/colors/xfstests.py
RUN_XFSTESTS="sudo run-xfstests.zsh"
OPTIONS="sec=sys"
USER=$(whoami)
RESULT_BASE=$HOME/.local/share/xfstests/
DATE=$(date +%Y-%m-%d-%H:%M:%S%z)
TODAY=$(date +%Y/%m/%d)
NOW=$(date +%H:%M:%S%z)
REMOTE_RESULTS=xfstests-dev/results/
RESULTS=$RESULT_BASE/date/$TODAY/${SERVER[-1]}/$NOW
TAG=$(kernel-tag.zsh ${CLIENT[-1]})
if [ ! -z $TAG ]; then
TAGRES=$RESULT_BASE/tags/$TAG/${SERVER[-1]}
fi
RESULTS=$HOME/.local/share/xfstests/$TODAY/${SERVER[-1]}/$NOW
#
# Prepare to test
@ -63,13 +57,13 @@ run_xfs_tests() {
--version $2 \
--opts $OPTIONS \
--user $USER $testargs" | python $COLOR $1 $2
mkdir -p $RESULTS
scp -q ${CLIENT[-1]}:$REMOTE_RESULTS/$1/$2/result.xml $RESULTS/$1-$2.xml
}
#
# Run tests
#
mkdir -p $RESULTS
for proto in $PROTO; do
for vers in $VERSION; do
case $vers in
@ -82,8 +76,4 @@ done
wait
if [ ! -z "$TAG" ]; then
mkdir -p $TAGRES
ln -s $RESULTS $TAGRES/$DATE
fi
optirun report-xfstests.py $RESULTS