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()
 | |
| 
 |