495 lines
19 KiB
Python
495 lines
19 KiB
Python
import py
|
|
from py.__.test.tkinter import backend
|
|
from py.__.test.tkinter import util
|
|
from py.__.test.tkinter import event
|
|
Null = util.Null
|
|
Event = event.Event
|
|
|
|
import ScrolledText
|
|
from Tkinter import PhotoImage
|
|
import Tkinter
|
|
import re
|
|
import os
|
|
|
|
myfont = ('Helvetica', 12, 'normal')
|
|
|
|
class StatusBar(Tkinter.Frame):
|
|
|
|
font = ('Helvetica', 14, 'normal')
|
|
|
|
def __init__(self, master=None, **kw):
|
|
if master is None:
|
|
master = Tk()
|
|
Tkinter.Frame.__init__(self, master, **kw)
|
|
self.labels = {}
|
|
self.callback = Null()
|
|
self.tkvariable_dict = {'failed': Tkinter.BooleanVar(),
|
|
'skipped': Tkinter.BooleanVar(),
|
|
'passed_item': Tkinter.BooleanVar()}
|
|
self.init_widgets()
|
|
|
|
def init_widgets(self):
|
|
def get_button(text, var):
|
|
b = Tkinter.Checkbutton(self, text=text,
|
|
variable=var,
|
|
onvalue=True, offvalue=False,
|
|
anchor=Tkinter.W,
|
|
font=self.font, indicatoron = False,
|
|
command = self.do_callback)
|
|
b.select()
|
|
return b
|
|
|
|
self.add_widget('Failed',
|
|
get_button('Failed', self.tkvariable_dict['failed']))
|
|
self._get_widget('Failed').configure(bg = 'Red3', fg = 'White',
|
|
highlightcolor = 'Red',
|
|
selectcolor = 'Red',
|
|
activebackground= 'Red',
|
|
activeforeground = 'White')
|
|
self.add_widget('Skipped',
|
|
get_button('Skipped', self.tkvariable_dict['skipped']))
|
|
self._get_widget('Skipped').configure(bg = 'Yellow3',
|
|
highlightcolor = 'Yellow',
|
|
selectcolor = 'Yellow',
|
|
activebackground= 'Yellow')
|
|
b = get_button('Passed', self.tkvariable_dict['passed_item'])
|
|
b.deselect()
|
|
self.add_widget('Passed', b)
|
|
self._get_widget('Passed').configure(bg = 'Green3', fg = 'Black',
|
|
highlightcolor = 'Black',
|
|
selectcolor = 'Green',
|
|
activeforeground = 'Black',
|
|
activebackground= 'Green')
|
|
|
|
def set_filter_args_callback(self, callback):
|
|
self.callback = callback
|
|
|
|
def do_callback(self,):
|
|
kwargs = dict([(key, var.get())
|
|
for key, var in self.tkvariable_dict.items()])
|
|
self.callback(**kwargs)
|
|
|
|
def set_label(self, name, text='', side=Tkinter.LEFT):
|
|
if not self.labels.has_key(name):
|
|
label = Tkinter.Label(self, bd=1, relief=Tkinter.SUNKEN,
|
|
anchor=Tkinter.W,
|
|
font=self.font)
|
|
self.add_widget(name, label, side)
|
|
else:
|
|
label = self.labels[name]
|
|
label.config(text='%s:\t%s' % (name,text))
|
|
|
|
def add_widget(self, name, widget, side=Tkinter.LEFT):
|
|
widget.pack(side=side)
|
|
self.labels[name] = widget
|
|
|
|
def _get_widget(self, name):
|
|
if not self.labels.has_key(name):
|
|
self.set_label(name, 'NoText')
|
|
return self.labels[name]
|
|
|
|
def update_all(self, reportstore = Null(), ids = []):
|
|
self.set_label('Failed', str(len(reportstore.get(failed = True))))
|
|
self.set_label('Skipped', str(len(reportstore.get(skipped = True))))
|
|
self.set_label('Passed',
|
|
str(len(reportstore.get(passed_item = True))))
|
|
root_report = reportstore.root
|
|
if root_report.time < 7*24*60*60:
|
|
self.set_label('Time', '%0.2f seconds' % root_report.time)
|
|
else:
|
|
self.set_label('Time', '%0.2f seconds' % 0.0)
|
|
|
|
class ReportListBox(Tkinter.LabelFrame):
|
|
|
|
font = myfont
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
Tkinter.LabelFrame.__init__(self, *args, **kwargs)
|
|
self.callback = Null()
|
|
self.data = {}
|
|
self.filter_kwargs = {'skipped': True, 'failed': True}
|
|
self.label = Tkinter.Label(self)
|
|
self.label.configure(font = self.font, width = 80, anchor = Tkinter.W)
|
|
self.configure(labelwidget=self.label)
|
|
self.createwidgets()
|
|
self.label.configure(text = 'Idle')
|
|
self.configure(font = myfont)
|
|
|
|
|
|
def createwidgets(self):
|
|
self.listbox = Tkinter.Listbox(self, foreground='red',
|
|
selectmode=Tkinter.SINGLE, font = self.font)
|
|
|
|
self.scrollbar = Tkinter.Scrollbar(self, command=self.listbox.yview)
|
|
self.scrollbar.pack(side = Tkinter.RIGHT, fill = Tkinter.Y,
|
|
anchor = Tkinter.N)
|
|
self.listbox.pack(side = Tkinter.LEFT,
|
|
fill = Tkinter.BOTH, expand = Tkinter.YES,
|
|
anchor = Tkinter.NW)
|
|
self.listbox.configure(yscrollcommand = self.scrollbar.set,
|
|
bg = 'White',selectbackground= 'Red',
|
|
takefocus= Tkinter.YES)
|
|
|
|
def set_callback(self, callback):
|
|
self.callback = callback
|
|
self.listbox.bind('<Double-1>', self.do_callback)
|
|
|
|
def do_callback(self, *args):
|
|
report_ids = [self.data[self.listbox.get(int(item))]
|
|
for item in self.listbox.curselection()]
|
|
for report_id in report_ids:
|
|
self.callback(report_id)
|
|
|
|
def set_filter_kwargs(self, **kwargs):
|
|
self.filter_kwargs = kwargs
|
|
|
|
def update_label(self, report):
|
|
label = report.path
|
|
if not label:
|
|
label = 'Idle'
|
|
self.label.configure(text = label)
|
|
|
|
def update_list(self, reportstore):
|
|
reports = reportstore.get(**self.filter_kwargs)
|
|
old_selections = [self.listbox.get(int(sel))
|
|
for sel in self.listbox.curselection()]
|
|
self.listbox.delete(0, Tkinter.END)
|
|
self.data = {}
|
|
for report in reports:
|
|
label = '%s: %s' % (report.status, report.label)
|
|
self.data[label] = report.full_id
|
|
self.listbox.insert(Tkinter.END, label)
|
|
if label in old_selections:
|
|
self.listbox.select_set(Tkinter.END)
|
|
self.listbox.see(Tkinter.END)
|
|
|
|
|
|
|
|
|
|
class LabelEntry(Tkinter.Frame):
|
|
|
|
font = myfont
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
Tkinter.Frame.__init__(self, *args, **kwargs)
|
|
self.label = Tkinter.Label(self)
|
|
self.label.configure(font = self.font)
|
|
self.label.pack(side = Tkinter.LEFT)
|
|
self.entry = Tkinter.Entry(self)
|
|
self.entry.configure(font = self.font)
|
|
self.entry.pack(side = Tkinter.LEFT, expand = Tkinter.YES,
|
|
fill = Tkinter.X)
|
|
|
|
def update(self, reportstore = Null(), ids = []):
|
|
bgcolor = 'White'
|
|
fgcolor = 'Black'
|
|
root_status = reportstore.root.status
|
|
if root_status == reportstore.ReportClass.Status.Passed():
|
|
bgcolor = 'Green'
|
|
elif root_status == reportstore.ReportClass.Status.Failed():
|
|
bgcolor = 'Red'
|
|
fgcolor = 'White'
|
|
elif root_status == reportstore.ReportClass.Status.Skipped() :
|
|
bgcolor = 'Yellow'
|
|
self.entry.configure(bg = bgcolor, fg = fgcolor)
|
|
|
|
class ReportFrame(Tkinter.LabelFrame):
|
|
font = myfont
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
Tkinter.LabelFrame.__init__(self, *args, **kwargs)
|
|
self.report = Null()
|
|
self.label = Tkinter.Label(self,foreground="red", justify=Tkinter.LEFT)
|
|
self.label.pack(anchor=Tkinter.W)
|
|
self.label.configure(font = self.font)
|
|
self.configure(labelwidget = self.label)
|
|
self.text = ScrolledText.ScrolledText(self)
|
|
self.text.configure(bg = 'White', font = ('Helvetica', 11, 'normal'))
|
|
self.text.tag_config('sel', relief=Tkinter.FLAT)
|
|
self.text.pack(expand=Tkinter.YES, fill=Tkinter.BOTH,
|
|
side = Tkinter.TOP)
|
|
|
|
def set_report(self, report):
|
|
self.report = report
|
|
self.label.configure(text ='%s: %s' % (self.report.status,
|
|
self.report.label),
|
|
foreground="red", justify=Tkinter.LEFT)
|
|
self.text['state'] = Tkinter.NORMAL
|
|
self.text.delete(1.0, Tkinter.END)
|
|
self.text.insert(Tkinter.END, self.report.error_report)
|
|
self.text.yview_pickplace(Tkinter.END)
|
|
self.text['state'] = Tkinter.DISABLED
|
|
self.attacheditorhotspots(self.text)
|
|
|
|
def clear(self):
|
|
self.label.configure(text = '')
|
|
self.text['state'] = Tkinter.NORMAL
|
|
self.text.delete(1.0, Tkinter.END)
|
|
self.text['state'] = Tkinter.DISABLED
|
|
|
|
def launch_editor(self, file, line):
|
|
editor = (py.std.os.environ.get('PYTHON_EDITOR', None) or
|
|
py.std.os.environ.get('EDITOR_REMOTE', None) or
|
|
os.environ.get('EDITOR', None) or "emacsclient --no-wait ")
|
|
if editor:
|
|
print "%s +%s %s" % (editor, line, file)
|
|
#py.process.cmdexec('%s +%s %s' % (editor, line, file))
|
|
os.system('%s +%s %s' % (editor, line, file))
|
|
|
|
def attacheditorhotspots(self, text):
|
|
# Attach clickable regions to a Text widget.
|
|
filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""")
|
|
skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""")
|
|
lines = text.get('1.0', Tkinter.END).splitlines(1)
|
|
if not lines:
|
|
return
|
|
tagname = ''
|
|
start, end = 0,0
|
|
for index, line in enumerate(lines):
|
|
match = filelink.search(line)
|
|
if match is None:
|
|
match = skippedlink.search(line)
|
|
if match is None:
|
|
continue
|
|
file, line = match.group(1, 2)
|
|
start, end = match.span()
|
|
tagname = "ref%d" % index
|
|
text.tag_add(tagname,
|
|
"%d.%d" % (index + 1, start),
|
|
"%d.%d" % (index + 1, end))
|
|
text.tag_bind(tagname, "<Enter>",
|
|
lambda e, n=tagname:
|
|
e.widget.tag_config(n, underline=1))
|
|
text.tag_bind(tagname, "<Leave>",
|
|
lambda e, n=tagname:
|
|
e.widget.tag_config(n, underline=0))
|
|
text.tag_bind(tagname, "<Button-1>",
|
|
lambda e, self=self, f=file, l=line:
|
|
self.launch_editor(f, l))
|
|
|
|
|
|
class MinimalButton(Tkinter.Button):
|
|
|
|
format_string = '%dF / %dS / %dP'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
Tkinter.Button.__init__(self, *args, **kwargs)
|
|
self.configure(text = 'Start/Stop') #self.format_string % (0,0,0))
|
|
|
|
def update_label(self, reportstore):
|
|
failed = len(reportstore.get(failed = True))
|
|
skipped = len(reportstore.get(skipped = True))
|
|
passed = len(reportstore.get(passed_item = True))
|
|
#self.configure(text = self.format_string % (failed, skipped, passed))
|
|
bgcolor = 'Green'
|
|
fgcolor = 'Black'
|
|
highlightcolor = 'Green',
|
|
activebackground= 'Green',
|
|
activeforeground = 'Black'
|
|
|
|
if skipped > 0:
|
|
bgcolor = 'Yellow'
|
|
activebackground = 'Yellow'
|
|
if failed > 0:
|
|
bgcolor = 'Red'
|
|
fgcolor = 'White'
|
|
activebackground= 'Red',
|
|
activeforeground = 'White'
|
|
|
|
self.configure(bg = bgcolor, fg = fgcolor,
|
|
highlightcolor = highlightcolor,
|
|
activeforeground = activeforeground,
|
|
activebackground = activebackground)
|
|
|
|
class LayoutManager:
|
|
|
|
def __init__(self):
|
|
self.current_layout = 0
|
|
self.layout_dict = {}
|
|
|
|
def add(self, widget, layout_ids, **kwargs):
|
|
for id in layout_ids:
|
|
list = self.layout_dict.setdefault(id, [])
|
|
list.append((widget, kwargs))
|
|
|
|
def set_layout(self, layout = 0):
|
|
if self.current_layout != layout:
|
|
widget_list = self.layout_dict.get(self.current_layout, [])
|
|
for widget, kwargs in widget_list:
|
|
widget.pack_forget()
|
|
self.current_layout = layout
|
|
widget_list = self.layout_dict.get(self.current_layout, [])
|
|
for widget, kwargs in widget_list:
|
|
widget.pack(**kwargs)
|
|
|
|
|
|
class TkGui:
|
|
|
|
font = ('Helvetica', 9, 'normal')
|
|
|
|
def __init__(self, parent, config):
|
|
self._parent = parent
|
|
self._config = config
|
|
self._should_stop = False
|
|
#XXX needed?
|
|
self._paths = []
|
|
self.backend = backend.ReportBackend(config)
|
|
self._layoutmanager = LayoutManager()
|
|
|
|
self._layout_toggle_var = Tkinter.StringVar()
|
|
self._layout_toggle_var.set('<')
|
|
# events
|
|
self.on_report_updated = Event()
|
|
self.on_report_store_updated = Event()
|
|
self.on_show_report = Event()
|
|
self.on_run_tests = Event()
|
|
|
|
self.createwidgets()
|
|
self.timer_update()
|
|
self.report_window = Null()
|
|
self.report_frame = Null()
|
|
|
|
|
|
def createwidgets(self):
|
|
add = self._layoutmanager.add
|
|
self._buttonframe = Tkinter.Frame(self._parent)
|
|
self._layoutbutton = Tkinter.Button(self._buttonframe, padx=0,
|
|
relief=Tkinter.GROOVE,
|
|
textvariable=self._layout_toggle_var,
|
|
command=self.toggle_layout)
|
|
add(self._layoutbutton, [0,1], side= Tkinter.LEFT)
|
|
self._entry = LabelEntry(self._buttonframe)
|
|
self._entry.label.configure(text = 'Enter test name:')
|
|
self._entry.entry.bind('<Return>', self.start_tests)
|
|
add(self._entry, [0], side = Tkinter.LEFT, fill = Tkinter.X,
|
|
expand = Tkinter.YES)
|
|
|
|
self._run_stop_button = Tkinter.Button(self._buttonframe, text = 'Run',
|
|
command = self.toggle_action, font = self.font)
|
|
add(self._run_stop_button, [0], side = Tkinter.RIGHT)
|
|
|
|
self._minimalbutton = MinimalButton(self._buttonframe, font = self.font,
|
|
command = self.toggle_action)
|
|
add(self._minimalbutton, [1], side = Tkinter.RIGHT,
|
|
fill = Tkinter.X, expand = Tkinter.YES)
|
|
add(self._buttonframe, [0,1], side = Tkinter.TOP, fill = Tkinter.X)
|
|
|
|
self._statusbar = StatusBar(self._parent)
|
|
self._statusbar.set_filter_args_callback(self.filter_args_changed)
|
|
add(self._statusbar, [0,1], side= Tkinter.BOTTOM, fill=Tkinter.X)
|
|
|
|
self._reportlist = ReportListBox(self._parent)
|
|
add(self._reportlist, [0], side = Tkinter.TOP, fill = Tkinter.BOTH,
|
|
expand = Tkinter.YES)
|
|
self._reportlist.set_callback(self.show_report)
|
|
self._report_frame = ReportFrame(self._parent)
|
|
add(self._report_frame, [0], side = Tkinter.TOP, fill = Tkinter.BOTH,
|
|
expand = Tkinter.YES)
|
|
|
|
#self.on_show_report.subscribe(self.show_report)
|
|
self.on_report_updated.subscribe(self._reportlist.update_label)
|
|
self.on_report_store_updated.subscribe(self._reportlist.update_list)
|
|
self.on_report_store_updated.subscribe(self._minimalbutton.update_label)
|
|
self.on_report_store_updated.subscribe(self.update_status)
|
|
|
|
self._layoutmanager.set_layout(0)
|
|
self.update_status(self.backend.get_store())
|
|
self.messages_callback(None)
|
|
|
|
def toggle_layout(self):
|
|
if self._layout_toggle_var.get() == '>':
|
|
self._layout_toggle_var.set('<')
|
|
self._layoutmanager.set_layout(0)
|
|
else:
|
|
self._layout_toggle_var.set('>')
|
|
self._layoutmanager.set_layout(1)
|
|
self._parent.geometry('')
|
|
|
|
def filter_args_changed(self, **kwargs):
|
|
self._reportlist.set_filter_kwargs(**kwargs)
|
|
self._reportlist.update_list(self.backend.get_store())
|
|
|
|
def show_report(self, report_id):
|
|
if report_id is None:
|
|
self._report_frame.clear()
|
|
else:
|
|
report = self.backend.get_store().get(id = report_id)[0]
|
|
self._report_frame.set_report(report)
|
|
|
|
def show_error_window(self, report_id):
|
|
report = self.backend.get_store().get(id = report_id)[0]
|
|
if not self.report_window:
|
|
self.report_window = Tkinter.Toplevel(self._parent)
|
|
self.report_window.protocol('WM_DELETE_WINDOW',
|
|
self.report_window.withdraw)
|
|
b = Tkinter.Button(self.report_window, text="Close",
|
|
command=self.report_window.withdraw)
|
|
b.pack(side=Tkinter.BOTTOM)
|
|
#b.focus_set()
|
|
self.report_frame = ReportFrame(self.report_window)
|
|
self.report_frame.pack()
|
|
elif self.report_window.state() != Tkinter.NORMAL:
|
|
self.report_window.deiconify()
|
|
|
|
self.report_window.title(report.label)
|
|
self.report_frame.set_report(report)
|
|
|
|
|
|
def timer_update(self):
|
|
self.backend.update()
|
|
if self.backend.running:
|
|
action = 'Stop'
|
|
else:
|
|
action = 'Run'
|
|
self._run_stop_button.configure(text = action)
|
|
self._minimalbutton.configure(text = action)
|
|
self._parent.after(200, self.timer_update)
|
|
|
|
def start_tests(self, dont_care_event=None):
|
|
if self.backend.running:
|
|
return
|
|
paths = [path.strip() for path in self._entry.entry.get().split(',')]
|
|
self.backend.set_messages_callback(self.messages_callback)
|
|
self.backend.set_message_callback(self.message_callback)
|
|
self.backend.start_tests(args = paths)
|
|
|
|
self.show_report(None)
|
|
|
|
def update_status(self, reportstore):
|
|
self._statusbar.update_all(reportstore = reportstore, ids = [])
|
|
self._entry.update(reportstore = reportstore, ids = [])
|
|
|
|
def messages_callback(self, report_ids):
|
|
if not report_ids:
|
|
return
|
|
self.on_report_store_updated(self.backend.get_store())
|
|
|
|
def message_callback(self, report_id):
|
|
if report_id is None:
|
|
report = Null()
|
|
else:
|
|
report = self.backend.get_store().get(id = report_id)[0]
|
|
self.on_report_updated(report)
|
|
|
|
def set_paths(self, paths):
|
|
self._paths = paths
|
|
self._entry.entry.insert(Tkinter.END, ', '.join(paths))
|
|
|
|
def toggle_action(self):
|
|
if self.backend.running:
|
|
self.stop()
|
|
else:
|
|
self.start_tests()
|
|
|
|
def stop(self):
|
|
self.backend.shutdown()
|
|
self.backend.update()
|
|
self.messages_callback([None])
|
|
self.message_callback(None)
|
|
|
|
def shutdown(self):
|
|
self.should_stop = True
|
|
self.backend.shutdown()
|
|
py.std.sys.exit()
|
|
|