208 lines
7.2 KiB
Python
208 lines
7.2 KiB
Python
#!/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, stdout, stderr):
|
|
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("stdout", stdout)
|
|
self.add_page("stderr", stderr)
|
|
self.set_size_request(400, 500)
|
|
|
|
def add_page(self, name, buffer):
|
|
view = Gtk.TextView(monospace=True)
|
|
view.set_buffer(buffer)
|
|
self.stack.add_titled(Gtk.ScrolledWindow(child=view), 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()
|