3
3
import time
4
4
import traceback
5
5
from multiprocessing import Process , Queue
6
- from threading import Thread
6
+ from threading import Thread , Lock
7
7
from Queue import Empty
8
8
9
9
from pyzen .ui import load_ui
10
10
11
11
_SLEEP_TIME = 1
12
12
13
- def _reloader_thread ():
14
- """When this function is run from the main thread, it will force other
15
- threads to exit when any modules currently loaded change.
16
-
17
- @param modification_callback: a function taking a single argument, the
18
- modified file, which is called every time a modification is detected
13
+ MAGIC_RETURN_CODE = 254
14
+
15
+ class ReloaderThread (Thread ):
16
+ def __init__ (self ):
17
+ super (ReloaderThread , self ).__init__ ()
18
+ self .daemon = False
19
+ self ._quit = False
20
+ self ._quit_lock = Lock ()
21
+ self .do_reload = False
22
+
23
+ def run (self ):
24
+ """When this is run, it will force other
25
+ threads to exit when any modules currently loaded change.
26
+ """
27
+ mtimes = {}
28
+ while True :
29
+ with self ._quit_lock :
30
+ if self ._quit :
31
+ return
32
+
33
+ for filename in filter (None , [getattr (module , '__file__' , None )
34
+ for module in sys .modules .values ()]):
35
+ while not os .path .isfile (filename ): # Probably in an egg or zip file
36
+ filename = os .path .dirname (filename )
37
+ if not filename :
38
+ break
39
+ if not filename : # Couldn't map to physical file, so just ignore
40
+ continue
41
+
42
+ if filename .endswith ('.pyc' ) or filename .endswith ('.pyo' ):
43
+ filename = filename [:- 1 ]
44
+
45
+ if not os .path .isfile (filename ):
46
+ # Compiled file for non-existant source
47
+ continue
48
+
49
+ mtime = os .stat (filename ).st_mtime
50
+ if filename not in mtimes :
51
+ mtimes [filename ] = mtime
52
+ continue
53
+ if mtime > mtimes [filename ]:
54
+ print >> sys .stderr , 'Detected modification of %s, restarting.' % filename
55
+ self .do_reload = True
56
+ sys .exit (MAGIC_RETURN_CODE )
57
+ time .sleep (_SLEEP_TIME )
58
+
59
+ def quit (self ):
60
+ with self ._quit_lock ():
61
+ self ._quit = True
62
+
63
+
64
+ class RunnerThread (Thread ):
65
+ """A thread to run the provided function and push the test results
66
+ back to a Queue.
19
67
"""
20
- mtimes = {}
21
- while True :
22
- for filename in filter (None , [getattr (module , '__file__' , None )
23
- for module in sys .modules .values ()]):
24
- while not os .path .isfile (filename ): # Probably in an egg or zip file
25
- filename = os .path .dirname (filename )
26
- if not filename :
27
- break
28
- if not filename : # Couldn't map to physical file, so just ignore
29
- continue
30
-
31
- if filename .endswith ('.pyc' ) or filename .endswith ('.pyo' ):
32
- filename = filename [:- 1 ]
33
-
34
- if not os .path .isfile (filename ):
35
- # Compiled file for non-existant source
36
- continue
37
-
38
- mtime = os .stat (filename ).st_mtime
39
- if filename not in mtimes :
40
- mtimes [filename ] = mtime
41
- continue
42
- if mtime > mtimes [filename ]:
43
- print >> sys .stderr , 'Detected modification of %s, restarting.' % filename
44
- sys .exit (3 )
45
- time .sleep (_SLEEP_TIME )
46
-
47
- def _runner_thread (q , func , args , kwargs ):
48
- try :
49
- start_time = time .clock ()
50
- result = func (* args , ** kwargs )
51
- end_time = time .clock ()
52
- q .put ({
53
- 'failures' : len (result .failures ),
54
- 'errors' : len (result .errors ),
55
- 'total' : result .testsRun ,
56
- 'time' : end_time - start_time ,
57
- })
58
- except Exception :
59
- traceback .print_exc ()
60
- q .put ({
61
- 'failures' : - 1 ,
62
- 'errors' : - 1 ,
63
- 'total' : - 1 ,
64
- 'time' : 0 ,
65
- })
68
+
69
+ def __init__ (self , q , func , args , kwargs ):
70
+ super (RunnerThread , self ).__init__ ()
71
+ self .q = q
72
+ self .func = func
73
+ self .args = args
74
+ self .kwargs = kwargs
75
+
76
+ def run (self ):
77
+ try :
78
+ start_time = time .clock ()
79
+ result = self .func (* self .args , ** self .kwargs )
80
+ end_time = time .clock ()
81
+ self .q .put ({
82
+ 'failures' : len (result .failures ),
83
+ 'errors' : len (result .errors ),
84
+ 'total' : result .testsRun ,
85
+ 'time' : end_time - start_time ,
86
+ })
87
+ except Exception :
88
+ traceback .print_exc ()
89
+ self .q .put ({
90
+ 'failures' : - 1 ,
91
+ 'errors' : - 1 ,
92
+ 'total' : - 1 ,
93
+ 'time' : 0 ,
94
+ })
95
+
66
96
67
97
def reloader (q , func , args , kwargs ):
68
- t = Thread ( target = _runner_thread , args = ( q , func , args , kwargs ) )
98
+ t = ReloaderThread ( )
69
99
t .start ()
70
100
try :
71
- _reloader_thread ()
101
+ RunnerThread ( q , func , args , kwargs ). run ()
72
102
except KeyboardInterrupt :
73
- pass
103
+ t .quit ()
104
+ finally :
105
+ t .join ()
106
+ if t .do_reload :
107
+ sys .exit (MAGIC_RETURN_CODE )
108
+
74
109
75
110
def main (ui_override , func , * args , ** kwargs ):
76
111
p = None
@@ -89,7 +124,7 @@ def main(ui_override, func, *args, **kwargs):
89
124
except Empty :
90
125
# Timed out, check if we need to restart
91
126
if not p .is_alive ():
92
- if p .exitcode == 3 :
127
+ if p .exitcode == MAGIC_RETURN_CODE :
93
128
break # This means we need to restart it
94
129
else :
95
130
return p .exitcode # Any other return code should be considered real
@@ -98,5 +133,3 @@ def main(ui_override, func, *args, **kwargs):
98
133
ui .shutdown ()
99
134
if p is not None :
100
135
p .terminate ()
101
-
102
-
0 commit comments