OSDN Git Service

fix unittest
[mypaint-anime/master.git] / tests / test_performance.py
1 #!/usr/bin/env python
2
3 import sys, os, tempfile, subprocess, gc, cProfile
4 from time import time, sleep
5
6 import gtk, glib
7 from pylab import math, linspace, loadtxt
8
9 os.chdir(os.path.dirname(sys.argv[0]))
10 sys.path.insert(0, '..')
11
12 import guicontrol
13
14 start_measurement = -1
15 stop_measurement = -2
16
17 all_tests = {}
18
19 def run_test(testfunction, profile=None):
20     """Run a single test
21     testfunction must be a generator (using yield)
22     """
23     tst = testfunction()
24
25     time_total = 0.0
26     for res in tst:
27         assert res == start_measurement, res
28         def run_function_under_test():
29             res = tst.next()
30             assert res == stop_measurement
31         t0 = time()
32         if profile:
33             profile.runcall(run_function_under_test)
34         else:
35             run_function_under_test()
36         time_total += time() - t0
37
38     if time_total:
39         print 'result =', time_total
40     else:
41         pass # test did not make time measurements, it will print its own result (eg. memory)
42
43 def nogui_test(f):
44     "decorator for test functions that require no gui"
45     all_tests[f.__name__] = f
46     return f
47 def gui_test(f):
48     "decorator for test functions that require no gui"
49     def f2():
50         gui = guicontrol.GUI()
51         for action in f(gui):
52             yield action
53     all_tests[f.__name__] = f2
54     return f
55
56
57 @gui_test
58 def startup(gui):
59     yield start_measurement
60     gui.wait_for_idle()
61     yield stop_measurement
62
63 @gui_test
64 def paint(gui):
65     """
66     Paint with a constant number of frames per recorded second.
67     Not entirely realistic, but gives good and stable measurements.
68     """
69     gui.wait_for_gui()
70     FPS = 30
71     dw = gui.app.drawWindow
72     gui_doc = gui.app.doc
73     tdw = gui_doc.tdw
74
75     b = gui.app.brushmanager.get_brush_by_name('redbrush')
76     assert b, 'brush not found'
77
78     dw.fullscreen_cb()
79     gui.wait_for_idle()
80     gui.app.brushmanager.select_brush(b)
81     gui.wait_for_duration(1.5) # fullscreen seems to take some time to get through...
82     gui.wait_for_idle()
83
84     events = loadtxt('painting30sec.dat.gz')
85     events = list(events)
86     yield start_measurement
87     t_old = 0.0
88     t_last_redraw = 0.0
89     for t, x, y, pressure in events:
90         if t > t_last_redraw + 1.0/FPS:
91             gui.wait_for_gui()
92             t_last_redraw = t
93         dtime = t - t_old
94         t_old = t
95         cr = tdw.get_model_coordinates_cairo_context()
96         x, y = cr.device_to_user(x, y)
97         gui_doc.model.stroke_to(dtime, x, y, pressure, 0.0, 0.0)
98     yield stop_measurement
99
100 @gui_test
101 def paint_zoomed_out_5x(gui):
102     gui.wait_for_idle()
103     gui_doc = gui.app.doc
104     for i in range(5):
105         gui_doc.zoom('ZoomOut')
106     for res in paint(gui):
107         yield res
108
109 @gui_test
110 def layerpaint_nozoom(gui):
111     gui.wait_for_idle()
112     gui.app.filehandler.open_file('bigimage.ora')
113     gui_doc = gui.app.doc
114     gui_doc.model.select_layer(len(gui_doc.model.layers)/2)
115     for res in paint(gui):
116         yield res
117
118 @gui_test
119 def layerpaint_zoomed_out_5x(gui):
120     gui.wait_for_idle()
121     gui_doc = gui.app.doc
122     gui.app.filehandler.open_file('bigimage.ora')
123     gui_doc.tdw.scroll(800, 1000)
124     gui_doc.model.select_layer(len(gui_doc.model.layers)/3)
125     for i in range(5):
126         gui_doc.zoom('ZoomOut')
127     for res in paint(gui):
128         yield res
129
130 @gui_test
131 def paint_rotated(gui):
132     gui.wait_for_idle()
133     gui.app.doc.tdw.rotate(46.0/360*2*math.pi)
134     for res in paint(gui):
135         yield res
136
137 @nogui_test
138 def load_ora():
139     from lib import document
140     d = document.Document()
141     yield start_measurement
142     d.load('bigimage.ora')
143     yield stop_measurement
144
145 @nogui_test
146 def save_ora():
147     from lib import document
148     d = document.Document()
149     d.load('bigimage.ora')
150     yield start_measurement
151     d.save('test_save.ora')
152     yield stop_measurement
153
154 @nogui_test
155 def save_ora_again():
156     from lib import document
157     d = document.Document()
158     d.load('bigimage.ora')
159     d.save('test_save.ora')
160     yield start_measurement
161     d.save('test_save.ora')
162     yield stop_measurement
163
164 @nogui_test
165 def save_png():
166     from lib import document
167     d = document.Document()
168     d.load('bigimage.ora')
169     yield start_measurement
170     d.save('test_save.png')
171     yield stop_measurement
172
173 @nogui_test
174 def save_png_layer():
175     from lib import document
176     d = document.Document()
177     d.load('biglayer.png')
178     yield start_measurement
179     d.layer.save_as_png('test_save.png')
180     yield stop_measurement
181
182
183 @nogui_test
184 def brushengine_paint_hires():
185     from lib import tiledsurface, brush
186     s = tiledsurface.Surface()
187     bi = brush.BrushInfo(open('brushes/watercolor.myb').read())
188     b = brush.Brush(bi)
189
190     events = loadtxt('painting30sec.dat.gz')
191     t_old = events[0][0]
192     s.begin_atomic()
193     yield start_measurement
194     for t, x, y, pressure in events:
195         dtime = t - t_old
196         t_old = t
197         b.stroke_to (s, x*5, y*5, pressure, 0.0, 0.0, dtime)
198     yield stop_measurement
199     s.end_atomic()
200     #s.save('test_paint_hires.png') # approx. 3000x3000
201
202 @gui_test
203 def scroll_nozoom(gui):
204     gui.wait_for_idle()
205     dw = gui.app.drawWindow
206     dw.fullscreen_cb()
207     gui.app.filehandler.open_file('bigimage.ora')
208     gui.wait_for_idle()
209     yield start_measurement
210     gui.scroll()
211     yield stop_measurement
212
213 @gui_test
214 def scroll_nozoom_onelayer(gui):
215     gui.wait_for_idle()
216     dw = gui.app.drawWindow
217     dw.fullscreen_cb()
218     gui.app.filehandler.open_file('biglayer.png')
219     gui.wait_for_idle()
220     yield start_measurement
221     gui.scroll()
222     yield stop_measurement
223
224 @gui_test
225 def scroll_zoomed_out_1x_onelayer(gui):
226     gui.wait_for_idle()
227     dw = gui.app.drawWindow
228     dw.fullscreen_cb()
229     gui.app.filehandler.open_file('biglayer.png')
230     for i in range(1):
231         gui.app.doc.zoom('ZoomOut')
232     gui.wait_for_idle()
233     yield start_measurement
234     gui.scroll()
235     yield stop_measurement
236
237 @gui_test
238 def scroll_zoomed_out_2x_onelayer(gui):
239     gui.wait_for_idle()
240     dw = gui.app.drawWindow
241     dw.fullscreen_cb()
242     gui.app.filehandler.open_file('biglayer.png')
243     for i in range(2):
244         gui.app.doc.zoom('ZoomOut')
245     gui.wait_for_idle()
246     yield start_measurement
247     gui.scroll()
248     yield stop_measurement
249
250 @gui_test
251 def scroll_zoomed_out_5x(gui):
252     gui.wait_for_idle()
253     dw = gui.app.drawWindow
254     dw.fullscreen_cb()
255     gui.app.filehandler.open_file('bigimage.ora')
256     for i in range(5):
257         gui.app.doc.zoom('ZoomOut')
258     gui.wait_for_idle()
259     yield start_measurement
260     gui.scroll()
261     yield stop_measurement
262
263 @gui_test
264 def memory_zoomed_out_5x(gui):
265     gui.wait_for_idle()
266     dw = gui.app.drawWindow
267     dw.fullscreen_cb()
268     gui.app.filehandler.open_file('bigimage.ora')
269     for i in range(5):
270         gui.app.doc.zoom('ZoomOut')
271     gui.wait_for_idle()
272     gui.scroll()
273     print 'result =', open('/proc/self/statm').read().split()[0]
274     if False:
275         yield None # just to make this function iterator
276
277 @gui_test
278 def memory_after_startup(gui):
279     gui.wait_for_idle()
280     sleep(1)
281     gui.wait_for_idle()
282     sleep(1)
283     gui.wait_for_idle()
284     print 'result =', open('/proc/self/statm').read().split()[0]
285     if False:
286         yield None # just to make this function iterator
287
288 if __name__ == '__main__':
289     if len(sys.argv) == 4 and sys.argv[1] == 'SINGLE_TEST_RUN':
290         func = all_tests[sys.argv[2]]
291         if sys.argv[3] == 'NONE':
292             run_test(func)
293         else:
294             profile = cProfile.Profile()
295             run_test(func, profile)
296             profile.dump_stats(sys.argv[3])
297         sys.exit(0)
298
299     from optparse import OptionParser
300     parser = OptionParser('usage: %prog [options] [test1 test2 test3 ...]')
301     parser.add_option('-a', '--all', action='store_true', default=False, 
302                       help='run all tests')
303     parser.add_option('-l', '--list', action='store_true', default=False,
304                     help='list all available tests')
305     parser.add_option('-c', '--count', metavar='N', type='int', default=3, 
306                       help='number of repetitions (default: 3)')
307     parser.add_option('-p', '--profile', metavar='PREFIX',
308                     help='dump cProfile info to PREFIX_TESTNAME_N.pstats')
309     parser.add_option('-s', '--show-profile', action='store_true', default=False,
310                     help='run cProfile, gprof2dot.py and show last result')
311     options, tests = parser.parse_args()
312
313     if options.list:
314         for name in sorted(all_tests.keys()):
315             print name
316         sys.exit(0)
317
318     if not tests:
319         if options.all:
320             tests = list(all_tests)
321         else:
322             parser.print_help()
323             sys.exit(1)
324
325     for t in tests:
326         if t not in all_tests:
327             print 'Unknown test:', t
328             sys.exit(1)
329
330     results = []
331     for t in tests:
332         result = []
333         for i in range(options.count):
334             print '---'
335             print 'running test "%s" (run %d of %d)' % (t, i+1, options.count)
336             print '---'
337             # spawn a new process for each test, to ensure proper cleanup
338             args = ['./test_performance.py', 'SINGLE_TEST_RUN', t, 'NONE']
339             if options.profile or options.show_profile:
340                 if options.show_profile:
341                     fname = 'tmp.pstats'
342                 else:
343                     fname = '%s_%s_%d.pstats' % (options.profile, t, i)
344                 args[3] = fname
345             child = subprocess.Popen(args, stdout=subprocess.PIPE)
346             output, junk = child.communicate()
347             if child.returncode != 0:
348                 print 'FAILED'
349                 break
350             else:
351                 print output,
352                 try:
353                     value = float(output.split('result = ')[-1].strip())
354                 except:
355                     print 'FAILED to find result in test output.'
356                     result = None
357                     break
358                 else:
359                     result.append(value)
360         # some time to press ctrl-c
361         sleep(1.0)
362         if result is None:
363             sleep(3.0)
364         results.append(result)
365     print
366     print '=== DETAILS ==='
367     print 'tests =', repr(tests)
368     print 'results =', repr(results)
369     print
370     print '=== SUMMARY ==='
371     fail=False
372     for t, result in zip(tests, results):
373         if not result:
374             print t, 'FAILED'
375             fail=True
376         else:
377             print '%s %.3f' % (t, min(result))
378     if fail:
379         sys.exit(1)
380
381     if options.show_profile:
382         os.system('gprof2dot.py -f pstats tmp.pstats | dot -Tpng -o tmp.png && feh tmp.png')