diff --git a/report-xfstests.py b/report-xfstests.py new file mode 100755 index 0000000..d938f59 --- /dev/null +++ b/report-xfstests.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +import gi +gi.require_version("Gtk", "4.0") + +import bisect +import pathlib +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 + + +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 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(" Passed ") + case "Skipped": label.set_markup(" Skipped ") + case "Failed": label.set_markup(" Failed ") + 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.selection = Gtk.NoSelection.new(self.results) + 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_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.xfstests = TestWindow() + self.grid = Gtk.Grid() + self.grid.attach(self.calendar, 0, 0, 1, 1) + self.grid.attach(self.servers, 0, 1, 1, 1) + self.grid.attach(self.xfstests, 1, 0, 1, 2) + self.set_default_size(1000, 600) + self.set_child(self.grid) + + self.calendar.connect("day-selected", self.date_changed) + self.servers.connect("test-changed", self.test_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.xfstests.set_tests(pathlib.Path(file.get_path())) + + +class Application(Gtk.Application): + def __init__(self, *args, **kwargs): + Gtk.Application.__init__(self, *args, application_id="org.gtk.report-xfstests", **kwargs) + + def do_startup(self): + Gtk.Application.do_startup(self) + self.add_window(Window()) + + def do_activate(self): + for window in self.get_windows(): + window.present() + +if __name__ == "__main__": + Application().run() diff --git a/run-xfstests.zsh b/run-xfstests.zsh index 6e53e29..e483d1a 100755 --- a/run-xfstests.zsh +++ b/run-xfstests.zsh @@ -30,5 +30,5 @@ if [ ! -z "$*" ]; then echo "XFSTESTS_ARGS -- $*" fi -#./check -nfs -r -R xunit $* -./check -nfs -R xunit $* +./check -nfs -r -R xunit $* +#./check -nfs -R xunit $* diff --git a/xfstests.zsh b/xfstests.zsh index e21ba07..b562fbf 100755 --- a/xfstests.zsh +++ b/xfstests.zsh @@ -24,9 +24,9 @@ RUN_XFSTESTS="sudo run-xfstests.zsh" OPTIONS="sec=sys" USER=$(whoami) TODAY=$(date +%Y/%m/%d) -NOW=$(date +%H:%M:%S) +NOW=$(date +%H:%M:%S%z) REMOTE_RESULTS=xfstests-dev/results/ -RESULTS=$HOME/.local/share/xfstests/${SERVER[-1]}/$TODAY +RESULTS=$HOME/.local/share/xfstests/$TODAY/${SERVER[-1]}/$NOW # # Prepare to test @@ -57,7 +57,7 @@ run_xfs_tests() { --version $2 \ --opts $OPTIONS \ --user $USER $testargs" | python $COLOR $1 $2 - scp -q ${CLIENT[-1]}:$REMOTE_RESULTS/$1/$2/result.xml $RESULTS/$NOW-$1-$2.xml + scp -q ${CLIENT[-1]}:$REMOTE_RESULTS/$1/$2/result.xml $RESULTS/$1-$2.xml } # @@ -75,3 +75,5 @@ for proto in $PROTO; do done wait + +optirun report-xfstests.py $RESULTS