OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 # Copyright (c) 2012 The Native Client Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Checks that known instructions are disassebled identically to "objdump -d". | |
7 | |
8 The single-threaded generator produces byte sequences of all instructions it | |
9 knows about and emits them in portions, file by file. Process these files in | |
10 parallel. | |
11 """ | |
12 | |
13 import optparse | |
14 import os | |
15 import signal | |
16 import subprocess | |
17 import sys | |
18 import threading | |
19 import time | |
20 | |
21 | |
22 CV = None # condition variable for all wait/resume | |
23 CAPACITY = 8 | |
24 | |
25 | |
26 class TestGenerator(threading.Thread): | |
27 """Runs the generator, produces names of the files to run tests on.""" | |
28 | |
29 def __init__(self, gen_cmd): | |
30 threading.Thread.__init__(self) | |
31 self.generator_command = gen_cmd | |
32 self.generated_filenames = [] | |
33 self.is_eof = False | |
34 self.proc = None | |
35 | |
36 def run(self): | |
37 """Updates generated_filenames as soon as filenames arrive. | |
38 | |
39 Starts the generator, receives filenames from its stdout as soon as they | |
40 appear, notifies workers to take them. Once over capacity, stops | |
41 the generator to continue later. | |
42 """ | |
43 self.proc = subprocess.Popen(self.generator_command, stdout=subprocess.PIPE) | |
44 while True: | |
45 CV.acquire() | |
46 try: | |
47 while len(self.generated_filenames) >= CAPACITY: | |
48 self.proc.send_signal(signal.SIGSTOP) | |
49 CV.wait() | |
50 self.proc.send_signal(signal.SIGCONT) | |
51 except OSError, err: | |
52 # OS Error with errno == 3 (ESRCH) means "no such process". The process | |
53 # has exited, ignore the error. | |
54 if err.errno != 3: | |
55 print >> sys.stderr, ('error: Unknown OSError while trying to ' + | |
56 'stop the generator: {0}'.format(err)) | |
57 os._exit(4) | |
58 line = self.proc.stdout.readline() | |
59 if line == '': | |
60 self.is_eof = True | |
61 CV.notifyAll() | |
62 CV.release() | |
63 break | |
64 self.generated_filenames.append(line.rstrip()) | |
65 CV.notifyAll() | |
66 CV.release() | |
67 # Yield to give Workers an opportunity to run. | |
68 time.sleep(0.000001) | |
69 | |
70 def NextAvailable(self): | |
71 """Indicates whether a worker has a file to take. | |
72 | |
73 If the generator is finished and there is no file left in the queue, a | |
74 worker should consume an empty value and return silently. Must be run under | |
75 a lock. | |
76 """ | |
77 if self.generated_filenames or self.is_eof: | |
78 return True | |
79 return False | |
80 | |
81 def TakeNext(self): | |
82 """Pops the next filename from the queue.""" | |
83 | |
84 if self.generated_filenames: | |
85 ret = self.generated_filenames.pop(0) | |
86 return ret | |
87 if self.is_eof: | |
88 return None | |
89 | |
90 def Cleanup(self): | |
91 if self.proc: | |
92 self.proc.kill() | |
93 | |
94 | |
95 class Worker(threading.Thread): | |
96 """Takes filenames from the queue and processes them until empty is seen. | |
97 | |
98 Kills all testing once a single failure report is observed. | |
99 """ | |
100 | |
101 def __init__(self, gen, option_parser): | |
102 threading.Thread.__init__(self) | |
103 self.generator = gen | |
104 self.opt = option_parser | |
105 | |
106 def run(self): | |
107 while True: | |
108 CV.acquire() | |
109 while not self.generator.NextAvailable(): | |
110 CV.wait() | |
111 f = self.generator.TakeNext() | |
112 CV.notifyAll() | |
113 CV.release() | |
114 if not f: | |
115 return | |
116 self.ProcessFile(f) | |
117 | |
118 def ProcessFile(self, filename): | |
119 pid = os.fork() | |
120 if pid == 0: | |
121 os.execl(self.opt.tester, self.opt.tester, | |
122 'GAS="' + self.opt.gas_path + '"', | |
123 'OBJDUMP="' + self.opt.objdump_path + '"', | |
124 'DECODER="' + self.opt.decoder_path + '"', | |
125 'ASMFILE="' + filename + '"') | |
126 | |
127 else: | |
128 (child_pid, retcode) = os.wait() | |
129 if retcode != 0: | |
130 print >> sys.stderr, ('error: return code 0 expected from tester, ' + | |
131 'got {0} instead'.format(retcode)) | |
132 self.ExitFail() | |
133 print '{0}..done'.format(filename) | |
134 | |
135 def ExitFail(self): | |
136 self.generator.Cleanup() | |
137 os._exit(3) | |
138 | |
139 | |
140 def Main(): | |
141 parser = optparse.OptionParser() | |
142 parser.add_option( | |
143 '-a', '--gas', dest='gas_path', | |
144 default=None, | |
145 help='path to find the assembler binary') | |
146 parser.add_option( | |
147 '-o', '--objdump', dest='objdump_path', | |
148 default=None, | |
149 help='path to find the objdump binary') | |
150 parser.add_option( | |
151 '-d', '--decoder', dest='decoder_path', | |
152 default=None, | |
153 help='path to find the tested decoder') | |
154 parser.add_option( | |
155 '-n', '--nthreads', dest='nthreads', | |
156 default='0', | |
157 help='amount of threads to use for running decoders') | |
158 parser.add_option( | |
159 '-t', '--tester', dest='tester', | |
160 default=None, | |
161 help='script to test an individual file') | |
162 opt, generator_command = parser.parse_args() | |
163 if (not opt.gas_path or | |
164 not opt.objdump_path or | |
165 not opt.decoder_path or | |
166 not opt.tester or | |
167 int(opt.nthreads) == 0): | |
168 parser.error('invalid arguments') | |
169 | |
170 global CV | |
171 CV = threading.Condition() | |
172 gen = TestGenerator(generator_command) | |
173 | |
174 # Start all threads. | |
175 workers = [] | |
176 for i in xrange(0, int(opt.nthreads)): | |
177 w = Worker(gen, opt) | |
178 workers.append(w) | |
179 w.start() | |
180 gen.start() | |
181 | |
182 # Wait for all threads to finish. | |
183 gen.join() | |
184 for w in workers: | |
185 w.join() | |
186 return 0 | |
187 | |
188 | |
189 if __name__ == '__main__': | |
190 sys.exit(Main()) | |
OLD | NEW |