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 set -eo pipefail
let jobs=$(nproc)-2 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 make -j$jobs $* | python /home/anna/bin/colors/make.py

View File

@ -1,27 +1,355 @@
#!/usr/bin/python #!/usr/bin/python
import gi
gi.require_version("Gtk", "4.0")
import bisect
import pathlib import pathlib
import reporter import re
import sys import sys
import xdg.BaseDirectory
import xml.etree.ElementTree
from gi.repository import Gtk 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): class Window(Gtk.Window):
def __init__(self): def __init__(self):
Gtk.Window.__init__(self, title="Xfstests Results") Gtk.Window.__init__(self, title="Xfstests Results")
self.chooser = reporter.TestChooser() self.calendar = Calendar()
self.testview = reporter.TestViewer() self.servers = ServerWindow()
self.child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5) self.passing = Gtk.Switch(halign=Gtk.Align.CENTER, active=True)
self.child.append(self.chooser) self.lpass = Gtk.Label.new("Show Passing Tests")
self.child.append(self.testview) 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.set_child(self.child)
self.chooser.connect("test-selected", self.test_changed) self.calendar.connect("day-selected", self.date_changed)
if len(sys.argv) > 1: self.servers.connect("test-changed", self.test_changed)
self.testview.set_test_result(reporter.testchooser.Path(pathlib.Path(sys.argv[1]))) 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): 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): 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" RUN_XFSTESTS="sudo run-xfstests.zsh"
OPTIONS="sec=sys" OPTIONS="sec=sys"
USER=$(whoami) USER=$(whoami)
RESULT_BASE=$HOME/.local/share/xfstests/
DATE=$(date +%Y-%m-%d-%H:%M:%S%z)
TODAY=$(date +%Y/%m/%d) TODAY=$(date +%Y/%m/%d)
NOW=$(date +%H:%M:%S%z) NOW=$(date +%H:%M:%S%z)
REMOTE_RESULTS=xfstests-dev/results/ REMOTE_RESULTS=xfstests-dev/results/
RESULTS=$RESULT_BASE/date/$TODAY/${SERVER[-1]}/$NOW RESULTS=$HOME/.local/share/xfstests/$TODAY/${SERVER[-1]}/$NOW
TAG=$(kernel-tag.zsh ${CLIENT[-1]})
if [ ! -z $TAG ]; then
TAGRES=$RESULT_BASE/tags/$TAG/${SERVER[-1]}
fi
# #
# Prepare to test # Prepare to test
@ -63,13 +57,13 @@ run_xfs_tests() {
--version $2 \ --version $2 \
--opts $OPTIONS \ --opts $OPTIONS \
--user $USER $testargs" | python $COLOR $1 $2 --user $USER $testargs" | python $COLOR $1 $2
mkdir -p $RESULTS
scp -q ${CLIENT[-1]}:$REMOTE_RESULTS/$1/$2/result.xml $RESULTS/$1-$2.xml scp -q ${CLIENT[-1]}:$REMOTE_RESULTS/$1/$2/result.xml $RESULTS/$1-$2.xml
} }
# #
# Run tests # Run tests
# #
mkdir -p $RESULTS
for proto in $PROTO; do for proto in $PROTO; do
for vers in $VERSION; do for vers in $VERSION; do
case $vers in case $vers in
@ -82,8 +76,4 @@ done
wait wait
if [ ! -z "$TAG" ]; then
mkdir -p $TAGRES
ln -s $RESULTS $TAGRES/$DATE
fi
optirun report-xfstests.py $RESULTS optirun report-xfstests.py $RESULTS