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