Compare commits
8 Commits
b6f523e622
...
4d36a33840
Author | SHA1 | Date |
---|---|---|
Anna Schumaker | 4d36a33840 | |
Anna Schumaker | 55f3239edc | |
Anna Schumaker | 6f3c37e568 | |
Anna Schumaker | b47955fe5a | |
Anna Schumaker | f1b6f42009 | |
Anna Schumaker | 51f50a54b4 | |
Anna Schumaker | e2c8b731d7 | |
Anna Schumaker | 58b9083908 |
|
@ -0,0 +1,2 @@
|
|||
__pycache__
|
||||
*.pyc
|
|
@ -2,4 +2,10 @@
|
|||
|
||||
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
|
||||
|
|
|
@ -1,355 +1,27 @@
|
|||
#!/usr/bin/python
|
||||
import gi
|
||||
gi.require_version("Gtk", "4.0")
|
||||
|
||||
import bisect
|
||||
import pathlib
|
||||
import re
|
||||
import reporter
|
||||
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.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.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.chooser = reporter.TestChooser()
|
||||
self.testview = reporter.TestViewer()
|
||||
self.child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
|
||||
self.child.append(self.left)
|
||||
self.child.append(self.right)
|
||||
self.child.append(self.chooser)
|
||||
self.child.append(self.testview)
|
||||
|
||||
self.lskip.set_xalign(0)
|
||||
self.lpass.set_xalign(0)
|
||||
self.set_default_size(1000, 600)
|
||||
self.set_default_size(1300, 800)
|
||||
self.set_child(self.child)
|
||||
|
||||
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())
|
||||
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])))
|
||||
|
||||
def test_changed(self, window, 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))
|
||||
self.testview.set_test_result(file)
|
||||
|
||||
|
||||
class Application(Gtk.Application):
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
#!/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)
|
|
@ -0,0 +1,14 @@
|
|||
#!/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)
|
|
@ -0,0 +1,163 @@
|
|||
#!/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
|
|
@ -0,0 +1,90 @@
|
|||
#!/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)
|
|
@ -0,0 +1,77 @@
|
|||
#!/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
|
|
@ -0,0 +1,209 @@
|
|||
#!/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()
|
14
xfstests.zsh
14
xfstests.zsh
|
@ -23,10 +23,16 @@ 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=$HOME/.local/share/xfstests/$TODAY/${SERVER[-1]}/$NOW
|
||||
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
|
||||
|
||||
#
|
||||
# Prepare to test
|
||||
|
@ -57,13 +63,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
|
||||
|
@ -76,4 +82,8 @@ done
|
|||
|
||||
wait
|
||||
|
||||
if [ ! -z "$TAG" ]; then
|
||||
mkdir -p $TAGRES
|
||||
ln -s $RESULTS $TAGRES/$DATE
|
||||
fi
|
||||
optirun report-xfstests.py $RESULTS
|
||||
|
|
Loading…
Reference in New Issue