1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18  import cPickle 
 19  import os 
 20  import qm.executable 
 21  import qm.test.cmdline 
 22  from   qm.test.target import * 
 23   
 24   
 25   
 26   
 27   
 29      """A 'ProcessTarget' runs tests in child processes.""" 
 30   
 31      arguments = [ 
 32          qm.fields.IntegerField( 
 33              name="processes", 
 34              title="Number of Processes", 
 35              description="""The number of processes to devote to running tests. 
 36   
 37              A positive integer that indicates the number of processes to 
 38              use when running tests.  Larger numbers will allow more 
 39              tests to be run at once.  You can experiment with this 
 40              value to find the number that results in the fastest 
 41              execution.""", 
 42              default_value=1), 
 43          qm.fields.TextField( 
 44              name="database_path", 
 45              title="Database Path", 
 46              description="""The path to the test database. 
 47   
 48              A string giving the directory containing the test 
 49              database.  If this value is the empty string, QMTest uses 
 50              the path provided on the command line.""", 
 51              default_value=""), 
 52          qm.fields.TextField( 
 53              name="qmtest", 
 54              title="QMTest Path", 
 55              description="""The path to the QMTest executable. 
 56   
 57              A string giving the file name of the 'qmtest' executable 
 58              program.  This path is used to invoke QMTest.""", 
 59              default_value=""), 
 60          ] 
 61   
 63          """A 'QMTestExecutable' redirects commands to a child process.""" 
 64   
 66   
 67              self.command_pipe = os.pipe() 
 68              self.response_pipe = os.pipe() 
  69   
 70   
 72   
 73               
 74              os.close(self.command_pipe[1]) 
 75               
 76              os.close(self.response_pipe[0]) 
 77               
 78               
 79              os.dup2(self.command_pipe[0], sys.stdin.fileno()) 
 80              os.dup2(self.response_pipe[1], sys.stdout.fileno()) 
   81   
 82               
 83           
 84 -    def __init__(self, database, properties): 
  85          """Construct a new 'ProcessTarget'. 
 86   
 87          'database' -- The 'Database' containing the tests that will be 
 88          run. 
 89       
 90          'properties'  -- A dictionary mapping strings (property names) 
 91          to strings (property values).""" 
 92           
 93           
 94          Target.__init__(self, database, properties) 
  95   
 96   
 98          """Return true if the target is idle. 
 99   
100          returns -- True if the target is idle.  If the target is idle, 
101          additional tasks may be assigned to it.""" 
102   
103          return self.__idle_children 
 104   
105   
106 -    def Start(self, response_queue, engine=None): 
 107          """Start the target. 
108           
109          'response_queue' -- The 'Queue' in which the results of test 
110          executions are placed. 
111   
112          'engine' -- The 'ExecutionEngine' that is starting the target, 
113          or 'None' if this target is being started without an 
114          'ExecutionEngine'.""" 
115   
116          Target.Start(self, response_queue, engine) 
117   
118           
119          self.__children = [] 
120          self.__idle_children = [] 
121          self.__busy_children = [] 
122          self.__children_by_fd = {} 
123           
124           
125          database_path = self.database_path 
126          if not database_path: 
127              database_path = self.GetDatabase().GetPath() 
128           
129           
130          qmtest_path = self.qmtest 
131          if not qmtest_path: 
132               
133               
134              qmtest_path \ 
135                  = qm.test.cmdline.get_qmtest().GetExecutablePath() 
136               
137              if not qmtest_path: 
138                  qmtest_path = "/usr/local/bin/qmtest" 
139           
140          arg_list = (self._GetInterpreter() + 
141                      [ qmtest_path, '-D', database_path, "remote" ]) 
142   
143           
144          for x in xrange(self.processes): 
145               
146               
147              e = ProcessTarget.QMTestExecutable() 
148              child_pid = e.Spawn(arg_list) 
149               
150               
151              os.close(e.command_pipe[0]) 
152               
153              os.close(e.response_pipe[1]) 
154   
155               
156              child = (child_pid, 
157                       os.fdopen(e.response_pipe[0], "r"), 
158                       os.fdopen(e.command_pipe[1], "w", 0)) 
159              self.__children.append(child) 
160              self.__idle_children.append(child) 
161              self.__children_by_fd[e.response_pipe[0]] = child 
162              engine.AddInputHandler(e.response_pipe[0], self.__ReadResults) 
 163   
164   
166          """Stop the target. 
167   
168          postconditions -- The target may no longer be used.""" 
169   
170           
171          for child in self.__children: 
172              try: 
173                  cPickle.dump("Stop", child[2]) 
174                  child[2].close() 
175              except: 
176                  pass 
177           
178          while self.__busy_children: 
179              self.__ReadResults(self.__busy_children[0][1].fileno()) 
180           
181          while self.__children: 
182              child = self.__children.pop() 
183              os.waitpid(child[0], 0) 
184               
185          Target.Stop(self) 
 186   
187   
188 -    def RunTest(self, descriptor, context): 
 189          """Run the test given by 'test_id'. 
190   
191          'descriptor' -- The 'TestDescriptor' for the test. 
192   
193          'context' -- The 'Context' in which to run the test.""" 
194   
195           
196          child = self.__idle_children.pop(0) 
197          self.__busy_children.append(child) 
198           
199          try: 
200              cPickle.dump(("RunTest", descriptor.GetId(), context), 
201                           child[2]) 
202          except: 
203               
204               
205               
206              result = Result(Result.TEST, descriptor.GetId()) 
207              result.NoteException() 
208              self._RecordResult(result) 
209              self.__idle_children.append(child) 
 210               
211   
213          """Return the interpreter to use. 
214   
215          returns -- A list giving the path to an interpreter, and 
216          arguments to provide the interpreter.  This interpreter is 
217          used to run QMTest.  If '[]' is returned, then no intepreter 
218          is used.""" 
219   
220          return [] 
 221           
222   
224          """Read results from one of the children. 
225   
226          'fd' -- The descriptor from which the results should be read.""" 
227           
228          child = self.__children_by_fd[fd] 
229          try: 
230              results = cPickle.load(child[1]) 
231              idle = None 
232              for result in results: 
233                  self._RecordResult(result) 
234                  if not idle and result.GetKind() == Result.TEST: 
235                      self.__idle_children.append(child) 
236                      self.__busy_children.remove(child) 
237                      idle = 1 
238          except EOFError: 
239              self.__idle_children.append(child) 
240              if child in self.__busy_children: 
241                  self.__busy_children.remove(child) 
  242