1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package net.grinder.scriptengine.jython;
24
25 import java.io.File;
26
27 import net.grinder.engine.common.EngineException;
28 import net.grinder.engine.common.ScriptLocation;
29 import net.grinder.scriptengine.ScriptEngineService;
30 import net.grinder.scriptengine.ScriptEngineService.ScriptEngine;
31 import net.grinder.scriptengine.ScriptEngineService.WorkerRunnable;
32 import net.grinder.scriptengine.ScriptExecutionException;
33
34 import org.python.core.PyClass;
35 import org.python.core.PyException;
36 import org.python.core.PyObject;
37 import org.python.core.PyString;
38 import org.python.core.PySystemState;
39 import org.python.util.PythonInterpreter;
40
41
42
43
44
45
46
47
48
49 final class JythonScriptEngine implements ScriptEngine {
50
51 private static final String PYTHON_HOME = "python.home";
52 private static final String PYTHON_CACHEDIR = "python.cachedir";
53 private static final String CACHEDIR_DEFAULT_NAME = "cachedir";
54
55 private static final String TEST_RUNNER_CALLABLE_NAME = "TestRunner";
56
57 private final PySystemState m_systemState;
58 private final PythonInterpreter m_interpreter;
59 private final PyClass m_dieQuietly;
60 private final String m_version;
61
62 private final PyObject m_testRunnerFactory;
63
64
65
66
67
68
69
70 public JythonScriptEngine(final ScriptLocation script)
71 throws EngineException {
72
73
74
75
76
77
78 if (System.getProperty(PYTHON_HOME) == null &&
79 System.getProperty(PYTHON_CACHEDIR) == null) {
80 final String classpath = System.getProperty("java.class.path");
81
82 final File grinderJar = findFileInPath(classpath, "grinder.jar");
83 final File grinderJarDirectory =
84 grinderJar != null ? grinderJar.getParentFile() : new File(".");
85
86 final File jythonJar = findFileInPath(classpath, "jython.jar");
87 final File jythonHome =
88 jythonJar != null ? jythonJar.getParentFile() : grinderJarDirectory;
89
90 if (grinderJarDirectory == null && jythonJar == null ||
91 grinderJarDirectory != null &&
92 grinderJarDirectory.equals(jythonHome)) {
93 final File cacheDir = new File(jythonHome, CACHEDIR_DEFAULT_NAME);
94 System.setProperty("python.cachedir", cacheDir.getAbsolutePath());
95 }
96 }
97
98 m_systemState = new PySystemState();
99 m_interpreter = new PythonInterpreter(null, m_systemState);
100
101 m_interpreter.exec("class ___DieQuietly___: pass");
102 m_dieQuietly = (PyClass) m_interpreter.get("___DieQuietly___");
103
104 String version;
105
106 try {
107 version = PySystemState.class.getField("version").get(null).toString();
108 }
109 catch (final Exception e) {
110 version = "Unknown";
111 }
112
113 m_version = version;
114
115
116
117 m_systemState.path.insert(0,
118 new PyString(script.getFile().getParent()));
119
120
121
122
123
124 m_systemState.path.insert(1,
125 new PyString(script.getDirectory().getFile().getPath()));
126
127 try {
128
129 m_interpreter.execfile(script.getFile().getPath());
130 }
131 catch (final PyException e) {
132 throw new JythonScriptExecutionException("initialising test script", e);
133 }
134
135
136 m_testRunnerFactory = m_interpreter.get(TEST_RUNNER_CALLABLE_NAME);
137
138 if (m_testRunnerFactory == null || !m_testRunnerFactory.isCallable()) {
139 throw new JythonScriptExecutionException(
140 "There is no callable (class or function) named '" +
141 TEST_RUNNER_CALLABLE_NAME + "' in " + script);
142 }
143 }
144
145
146
147
148
149
150
151 private static File findFileInPath(final String path, final String fileName) {
152
153 for (final String pathEntry : path.split(File.pathSeparator)) {
154 final File file = new File(pathEntry);
155
156 if (file.exists() && file.getName().equals(fileName)) {
157 return file;
158 }
159 }
160
161 return null;
162 }
163
164
165
166
167 @Override public WorkerRunnable createWorkerRunnable()
168 throws EngineException {
169
170 final PyObject pyTestRunner;
171
172 try {
173
174
175 pyTestRunner = m_testRunnerFactory.__call__();
176 }
177 catch (final PyException e) {
178 throw new JythonScriptExecutionException(
179 "creating per-thread TestRunner object", e);
180 }
181
182 if (!pyTestRunner.isCallable()) {
183 throw new JythonScriptExecutionException(
184 "The result of '" + TEST_RUNNER_CALLABLE_NAME +
185 "()' is not callable");
186 }
187
188 return new JythonWorkerRunnable(pyTestRunner);
189 }
190
191
192
193
194 @Override public WorkerRunnable createWorkerRunnable(final Object testRunner)
195 throws EngineException {
196
197 if (testRunner instanceof PyObject) {
198 final PyObject pyTestRunner = (PyObject) testRunner;
199
200 if (pyTestRunner.isCallable()) {
201 return new JythonWorkerRunnable(pyTestRunner);
202 }
203 }
204
205 throw new JythonScriptExecutionException(
206 "testRunner object is not callable");
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220
221 @Override
222 public void shutdown() throws EngineException {
223
224 final PyObject exitfunc = m_systemState.__findattr__("exitfunc");
225
226 if (exitfunc != null) {
227 try {
228 exitfunc.__call__();
229 }
230 catch (final PyException e) {
231 throw new JythonScriptExecutionException(
232 "calling script exit function", e);
233 }
234 }
235 }
236
237
238
239
240
241
242 @Override
243 public String getDescription() {
244 return "Jython " + m_version;
245 }
246
247
248
249
250 private final class JythonWorkerRunnable
251 implements ScriptEngineService.WorkerRunnable {
252
253 private final PyObject m_testRunner;
254
255 public JythonWorkerRunnable(final PyObject testRunner) {
256 m_testRunner = testRunner;
257 }
258
259 @Override
260 public void run() throws ScriptExecutionException {
261
262 try {
263 m_testRunner.__call__();
264 }
265 catch (final PyException e) {
266 throw new JythonScriptExecutionException("calling TestRunner", e);
267 }
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301 @Override
302 public void shutdown() throws ScriptExecutionException {
303
304 final PyObject del = m_testRunner.__findattr__("__del__");
305
306 if (del != null) {
307 try {
308 del.__call__();
309 }
310 catch (final PyException e) {
311 throw new JythonScriptExecutionException(
312 "deleting TestRunner instance", e);
313 }
314 finally {
315
316
317
318
319
320
321 m_testRunner.__setattr__("__class__", m_dieQuietly);
322 }
323 }
324 }
325 }
326 }