View Javadoc

1   // Copyright (C) 2009 - 2012 Philip Aston
2   // All rights reserved.
3   //
4   // This file is part of The Grinder software distribution. Refer to
5   // the file LICENSE which is part of The Grinder distribution for
6   // licensing details. The Grinder distribution is available on the
7   // Internet at http://grinder.sourceforge.net/
8   //
9   // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
13  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
17  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
20  // OF THE POSSIBILITY OF SUCH DAMAGE.
21  
22  package net.grinder.scriptengine.jython.instrumentation;
23  
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertNotNull;
26  import static org.junit.Assert.assertNull;
27  import static org.junit.Assert.assertSame;
28  import static org.junit.Assert.fail;
29  
30  import java.lang.reflect.Field;
31  
32  import net.grinder.common.StubTest;
33  import net.grinder.common.UncheckedGrinderException;
34  import net.grinder.common.UncheckedInterruptedException;
35  import net.grinder.script.NonInstrumentableTypeException;
36  import net.grinder.script.NotWrappableTypeException;
37  import net.grinder.script.Test.InstrumentationFilter;
38  import net.grinder.scriptengine.Instrumenter;
39  import net.grinder.scriptengine.Recorder;
40  import net.grinder.testutility.AssertUtilities;
41  import net.grinder.testutility.RandomStubFactory;
42  
43  import org.junit.Test;
44  import org.python.core.PyClass;
45  import org.python.core.PyException;
46  import org.python.core.PyInstance;
47  import org.python.core.PyInteger;
48  import org.python.core.PyObject;
49  import org.python.core.PyProxy;
50  import org.python.core.PySystemState;
51  import org.python.core.PyTuple;
52  import org.python.util.PythonInterpreter;
53  
54  
55  /**
56   * Instrumentation unit tests.
57   *
58   * @author Philip Aston
59   */
60  public abstract class AbstractJythonInstrumenterTestCase {
61  
62    {
63      PySystemState.initialize();
64    }
65  
66    protected final Instrumenter m_instrumenter;
67  
68    protected final PythonInterpreter m_interpreter =
69      new PythonInterpreter(null, new PySystemState());
70  
71    protected final PyObject m_zero = new PyInteger(0);
72    protected final PyObject m_one = new PyInteger(1);
73    protected final PyObject m_two = new PyInteger(2);
74    protected final PyObject m_three = new PyInteger(3);
75    protected final PyObject m_six = new PyInteger(6);
76    protected final net.grinder.common.Test m_test = new StubTest(1, "test");
77  
78    protected final RandomStubFactory<Recorder> m_recorderStubFactory =
79        RandomStubFactory.create(Recorder.class);
80    protected final Recorder m_recorder = m_recorderStubFactory.getStub();
81  
82    public AbstractJythonInstrumenterTestCase(Instrumenter instrumenter) {
83      super();
84        m_instrumenter = instrumenter;
85    }
86  
87    protected abstract void assertTestReference(PyObject proxy,
88                                                net.grinder.common.Test test);
89  
90    protected abstract void assertTargetReference(PyObject proxy,
91                                                  Object original,
92                                                  boolean unwrapTarget);
93  
94    protected void assertTargetReference(PyObject proxy, Object original) {
95      assertTargetReference(proxy, original, false);
96    }
97  
98    public static Integer[] getJythonVersion() throws Exception {
99      final PyTuple pyTuple =
100       (PyTuple) PySystemState.class.getField("version_info").get(null);
101 
102     return new Integer[] { getVersionPart(pyTuple, 0),
103                            getVersionPart(pyTuple, 1),
104                            getVersionPart(pyTuple, 2), };
105   }
106 
107   private static Integer getVersionPart(PyTuple versionTuple, int part) {
108     // PyTuple.get()/toArray() do not exist in Jython 2.1.
109     return (Integer)versionTuple.__finditem__(part).__tojava__(Integer.class);
110   }
111 
112   public static void assertVersion(String expected) throws Exception {
113     AssertUtilities.assertContainsPattern(
114       PySystemState.class.getField("version").get(null).toString(),
115       expected);
116   }
117 
118   protected final PythonInterpreter getInterpretter() {
119     return m_interpreter;
120   }
121 
122   protected final void assertNotWrappable(Object o) throws Exception {
123     try {
124       m_instrumenter.createInstrumentedProxy(null, null, o);
125       fail("Expected NotWrappableTypeException");
126     }
127     catch (NotWrappableTypeException e) {
128     }
129   }
130 
131   protected final void assertNotWrappableByThisInstrumenter(Object o)
132     throws Exception {
133     assertNull(m_instrumenter.createInstrumentedProxy(null, null, o));
134   }
135 
136   protected final Object createInstrumentedProxy(net.grinder.common.Test test,
137                                                  Recorder recorder,
138                                                  PyObject pyTarget)
139     throws NotWrappableTypeException {
140 
141     // In the real world, the Java conversion happens implicitly because
142     // wrap() and record() are implemented in Java.
143     final Object javaTarget = pyTarget.__tojava__(Object.class);
144 
145     return m_instrumenter.createInstrumentedProxy(test, recorder, javaTarget);
146   }
147 
148   protected final Class<?> getClassForInstance(PyInstance target)
149     throws IllegalArgumentException, IllegalAccessException {
150 
151     Field f;
152 
153     try {
154       // Jython 2.1
155       f = PyObject.class.getField("__class__");
156     }
157     catch (NoSuchFieldException e) {
158       // Jython 2.2a1+
159       try {
160         f = PyInstance.class.getField("instclass");
161       }
162       catch (NoSuchFieldException e2) {
163         throw new AssertionError("Incompatible Jython release in classpath");
164       }
165     }
166 
167     final PyClass pyClass = (PyClass)f.get(target);
168 
169     return (Class<?>) pyClass.__tojava__(Class.class);
170   }
171 
172   @Test public void testCreateProxyWithPyFunction() throws Exception {
173     m_interpreter.exec("def return1(): return 1");
174     final PyObject pyFunction = m_interpreter.get("return1");
175     final PyObject pyFunctionProxy = (PyObject)
176       createInstrumentedProxy(m_test, m_recorder, pyFunction);
177 
178     final PyObject result = pyFunctionProxy.invoke("__call__");
179     assertEquals(m_one, result);
180     m_recorderStubFactory.assertSuccess("start");
181     m_recorderStubFactory.assertSuccess("end", true);
182     m_recorderStubFactory.assertNoMoreCalls();
183     assertTestReference(pyFunctionProxy, m_test);
184     assertTargetReference(pyFunctionProxy, pyFunction);
185 
186     m_interpreter.exec("def multiply(x, y): return x * y");
187     final PyObject pyFunction2 = m_interpreter.get("multiply");
188     final PyObject pyFunctionProxy2 = (PyObject)
189       createInstrumentedProxy(m_test, m_recorder, pyFunction2);
190     final PyObject result2 =
191       pyFunctionProxy2.invoke("__call__", m_two, m_three);
192     assertEquals(m_six, result2);
193     m_recorderStubFactory.assertSuccess("start");
194     m_recorderStubFactory.assertSuccess("end", true);
195     m_recorderStubFactory.assertNoMoreCalls();
196     assertTargetReference(pyFunctionProxy2, pyFunction2);
197 
198     final PyObject result3 =
199       pyFunctionProxy2.invoke("__call__", new PyObject[] { m_two, m_three});
200     assertEquals(m_six, result3);
201     m_recorderStubFactory.assertSuccess("start");
202     m_recorderStubFactory.assertSuccess("end", true);
203     m_recorderStubFactory.assertNoMoreCalls();
204 
205     m_interpreter.exec("def square(x): return x * x");
206     final PyObject pyFunction11 = m_interpreter.get("square");
207     final PyObject pyFunctionProxy11 = (PyObject)
208       createInstrumentedProxy(m_test, m_recorder, pyFunction11);
209     final PyObject result11 = pyFunctionProxy11.invoke("__call__", m_two);
210     assertEquals(new PyInteger(4), result11);
211     m_recorderStubFactory.assertSuccess("start");
212     m_recorderStubFactory.assertSuccess("end", true);
213     m_recorderStubFactory.assertNoMoreCalls();
214     assertTargetReference(pyFunctionProxy11, pyFunction11);
215 
216     // From Jython.
217     m_interpreter.set("proxy", pyFunctionProxy);
218     m_interpreter.set("proxy2", pyFunctionProxy2);
219 
220     m_interpreter.exec("result5 = proxy()");
221     final PyObject result5 = m_interpreter.get("result5");
222     assertEquals(m_one, result5);
223     m_recorderStubFactory.assertSuccess("start");
224     m_recorderStubFactory.assertSuccess("end", true);
225     m_recorderStubFactory.assertNoMoreCalls();
226   }
227 
228   @Test public void testCreateProxyWithPyInstance() throws Exception {
229     // PyInstance.
230     m_interpreter.exec(
231       "class Foo:\n" +
232       " def two(self): return 2\n" +
233       " def identity(self, x): return x\n" +
234       " def sum(self, x, y): return x + y\n" +
235       " def sum3(self, x, y, z): return x + y + z\n" +
236       "x=Foo()");
237 
238     final PyObject pyInstance = m_interpreter.get("x");
239     final PyObject pyInstanceProxy = (PyObject)
240         createInstrumentedProxy(m_test, m_recorder, pyInstance);
241     final PyObject result1 = pyInstanceProxy.invoke("two");
242     assertEquals(m_two, result1);
243     m_recorderStubFactory.assertSuccess("start");
244     m_recorderStubFactory.assertSuccess("end", true);
245     m_recorderStubFactory.assertNoMoreCalls();
246     assertTestReference(pyInstanceProxy, m_test);
247     assertNull(pyInstanceProxy.__findattr__("__blah__"));
248     assertTargetReference(pyInstanceProxy, pyInstance);
249 
250     final PyObject result2 = pyInstanceProxy.invoke("identity", m_one);
251     assertSame(m_one, result2);
252     m_recorderStubFactory.assertSuccess("start");
253     m_recorderStubFactory.assertSuccess("end", true);
254     m_recorderStubFactory.assertNoMoreCalls();
255 
256     final PyObject result3 = pyInstanceProxy.invoke("sum", m_one, m_two);
257     assertEquals(m_three, result3);
258     m_recorderStubFactory.assertSuccess("start");
259     m_recorderStubFactory.assertSuccess("end", true);
260     m_recorderStubFactory.assertNoMoreCalls();
261 
262     final PyObject result4 = pyInstanceProxy.invoke("sum3", new PyObject[] {
263         m_one, m_two, m_three });
264     assertEquals(m_six, result4);
265     m_recorderStubFactory.assertSuccess("start");
266     m_recorderStubFactory.assertSuccess("end", true);
267     m_recorderStubFactory.assertNoMoreCalls();
268 
269     final PyObject result5 = pyInstanceProxy.invoke("sum", new PyObject[] {
270         m_one, m_two }, new String[] { "x", "y" });
271     assertEquals(m_three, result5);
272     m_recorderStubFactory.assertSuccess("start");
273     m_recorderStubFactory.assertSuccess("end", true);
274     m_recorderStubFactory.assertNoMoreCalls();
275 
276     // From Jython.
277     m_interpreter.set("proxy", pyInstanceProxy);
278 
279     m_interpreter.exec("result6 = proxy.sum(2, 4)");
280     final PyObject result6 = m_interpreter.get("result6");
281     assertEquals(m_six, result6);
282     m_recorderStubFactory.assertSuccess("start");
283     m_recorderStubFactory.assertSuccess("end", true);
284     m_recorderStubFactory.assertNoMoreCalls();
285   }
286 
287   @Test public void testCreateProxyWithUnboundPyMethod() throws Exception {
288     m_interpreter.exec(
289       "class Foo:\n" +
290       " def two(self): return 2\n" +
291       " def identity(self, x): return x\n" +
292       " def sum(self, x, y): return x + y\n" +
293       " def sum3(self, x, y, z): return x + y + z\n" +
294       "x=Foo()");
295     final PyObject pyInstance = m_interpreter.get("x");
296     m_interpreter.exec("y=Foo.two");
297     final PyObject pyMethod = m_interpreter.get("y");
298     final PyObject pyMethodProxy = (PyObject)
299       createInstrumentedProxy(m_test, m_recorder, pyMethod);
300     final PyObject result = pyMethodProxy.invoke("__call__", pyInstance);
301     assertEquals(m_two, result);
302     m_recorderStubFactory.assertSuccess("start");
303     m_recorderStubFactory.assertSuccess("end", true);
304     m_recorderStubFactory.assertNoMoreCalls();
305     assertTestReference(pyMethodProxy, m_test);
306     assertNull(pyMethodProxy.__findattr__("__blah__"));
307     assertTargetReference(pyMethodProxy, pyMethod);
308 
309     m_interpreter.exec("y=Foo.identity");
310     final PyObject pyMethod2 = m_interpreter.get("y");
311     final PyObject pyMethodProxy2 = (PyObject)
312       createInstrumentedProxy(m_test, m_recorder, pyMethod2);
313     final PyObject result2 =
314       pyMethodProxy2.invoke("__call__", pyInstance, m_one);
315     assertEquals(m_one, result2);
316     m_recorderStubFactory.assertSuccess("start");
317     m_recorderStubFactory.assertSuccess("end", true);
318     m_recorderStubFactory.assertNoMoreCalls();
319 
320     m_interpreter.exec("y=Foo.sum");
321     final PyObject pyMethod3 = m_interpreter.get("y");
322     final PyObject pyMethodProxy3 = (PyObject)
323       createInstrumentedProxy(m_test, m_recorder, pyMethod3);
324     final PyObject result3 =
325       pyMethodProxy3.invoke(
326         "__call__", new PyObject[] { pyInstance, m_one, m_two });
327     assertEquals(m_three, result3);
328     m_recorderStubFactory.assertSuccess("start");
329     m_recorderStubFactory.assertSuccess("end", true);
330     m_recorderStubFactory.assertNoMoreCalls();
331 
332     // From Jython.
333     m_interpreter.set("proxy", pyMethodProxy);
334 
335     m_interpreter.exec("result5 = proxy(x)");
336     final PyObject result5 = m_interpreter.get("result5");
337     assertEquals(m_two, result5);
338     m_recorderStubFactory.assertSuccess("start");
339     m_recorderStubFactory.assertSuccess("end", true);
340     m_recorderStubFactory.assertNoMoreCalls();
341   }
342 
343   @Test public void testCreateProxyWithBoundPyMethod() throws Exception {
344     m_interpreter.exec(
345       "class Foo:\n" +
346       " def two(self): return 2\n" +
347       "x=Foo()\n" +
348       "y=Foo()\n");
349 
350     m_interpreter.exec("z=x.two");
351     final PyObject pyMethod = m_interpreter.get("z");
352     final PyObject pyMethodProxy = (PyObject)
353       createInstrumentedProxy(m_test, m_recorder, pyMethod);
354     final PyObject result = pyMethodProxy.invoke("__call__");
355     assertEquals(m_two, result);
356     m_recorderStubFactory.assertSuccess("start");
357     m_recorderStubFactory.assertSuccess("end", true);
358 
359     // Other instance is not instrumented.
360     m_interpreter.exec("z=y.two");
361     final PyObject pyMethod2 = m_interpreter.get("z");
362     final PyObject result2 = pyMethod2.invoke("__call__");
363     assertEquals(m_two, result2);
364 
365     m_recorderStubFactory.assertNoMoreCalls();
366   }
367 
368   @Test public void testCreateProxyWithPyReflectedFunction() throws Exception {
369     m_interpreter.exec("from grinder.test import MyClass\nx=MyClass(6, 5, 4)");
370     final PyObject pyJava = m_interpreter.get("x");
371     m_interpreter.exec("y=MyClass.getA");
372     final PyObject pyJavaMethod = m_interpreter.get("y");
373     final PyObject pyJavaMethodProxy = (PyObject)
374         createInstrumentedProxy(m_test, m_recorder, pyJavaMethod);
375     final PyObject result = pyJavaMethodProxy.__call__(pyJava);
376     assertEquals(m_six, result);
377     m_recorderStubFactory.assertSuccess("start");
378     m_recorderStubFactory.assertSuccess("end", true);
379     m_recorderStubFactory.assertNoMoreCalls();
380     assertTestReference(pyJavaMethodProxy, m_test);
381     assertNull(pyJavaMethodProxy.__findattr__("__blah__"));
382     assertTargetReference(pyJavaMethodProxy, pyJavaMethod);
383 
384     // From Jython.
385     m_interpreter.set("proxy", pyJavaMethodProxy);
386 
387     m_interpreter.exec("result2 = proxy(x)");
388     final PyObject result2 = m_interpreter.get("result2");
389     assertEquals(m_six, result2);
390     m_recorderStubFactory.assertSuccess("start");
391     m_recorderStubFactory.assertSuccess("end", true);
392     m_recorderStubFactory.assertNoMoreCalls();
393   }
394 
395   private PyObject getPyInstance(PyProxy pyProxy) throws Exception {
396     // Dynamic invocation because return type has changed in 2.5.
397     return (PyObject) PyProxy.class.getMethod("_getPyInstance").invoke(pyProxy);
398   }
399 
400   @Test public void testCreateProxyWithPyProxy() throws Exception {
401     m_interpreter.exec("from java.util import Random");
402     m_interpreter.exec(
403       "class PyRandom(Random):\n" +
404       " def one(self): return 1\n" +
405       "x=PyRandom()");
406     final PyObject pyInstance = m_interpreter.get("x");
407 
408     // PyProxy's come paired with PyInstances - need to call
409     // __tojava__ to get the PyProxy.
410     final PyProxy pyProxy = (PyProxy) pyInstance.__tojava__(PyProxy.class);
411     final Object pyProxyProxy =
412       m_instrumenter.createInstrumentedProxy(m_test,
413                                              m_recorder,
414                                              pyProxy);
415 
416     final PyObject pyProxyInstance =
417       (pyProxyProxy instanceof PyProxy) ?
418           getPyInstance((PyProxy) pyProxyProxy) : (PyObject)pyProxyProxy;
419 
420     final PyObject result = pyProxyInstance.invoke("one");
421     assertEquals(m_one, result);
422     m_recorderStubFactory.assertSuccess("start");
423     m_recorderStubFactory.assertSuccess("end", true);
424     m_recorderStubFactory.assertNoMoreCalls();
425     assertTestReference(pyProxyInstance, m_test);
426     assertTargetReference(pyProxyInstance, pyInstance);
427 
428     // From Jython.
429     m_interpreter.set("proxy", pyProxyProxy);
430 
431     m_interpreter.exec("result2 = proxy.one()");
432     final PyObject result2 = m_interpreter.get("result2");
433     assertEquals(m_one, result2);
434     m_recorderStubFactory.assertSuccess("start");
435     m_recorderStubFactory.assertSuccess("end", true);
436     m_recorderStubFactory.assertNoMoreCalls();
437 
438     m_interpreter.exec("result3 = proxy.nextInt()");
439     final PyObject result3 = m_interpreter.get("result3");
440     assertNotNull(result3);
441     m_recorderStubFactory.assertSuccess("start");
442     m_recorderStubFactory.assertSuccess("end", true);
443     m_recorderStubFactory.assertNoMoreCalls();
444   }
445 
446   @Test public void testCreateProxyWithRecursiveCode() throws Exception {
447     m_interpreter.exec(
448       "class Recurse:\n" +
449       "  def __init__(self):\n" +
450       "    self.i = 3\n" +
451       "  def foo(self):\n" +
452       "    self.i = self.i - 1\n" +
453       "    if self.i == 0: return 0\n" +
454       "    return self.i + self.foo()\n" +
455       "r = Recurse()");
456 
457     final PyObject proxy = (PyObject)
458       createInstrumentedProxy(m_test, m_recorder, m_interpreter.get("r"));
459 
460     final PyObject result = proxy.invoke("foo");
461 
462     assertEquals(new PyInteger(3), result);
463     // The dispatcher will be called multiple times. The real dispatcher
464     // only records the outer invocation.
465     m_recorderStubFactory.assertSuccess("start");
466     m_recorderStubFactory.assertSuccess("start");
467     m_recorderStubFactory.assertSuccess("start");
468     m_recorderStubFactory.assertSuccess("end", true);
469     m_recorderStubFactory.assertSuccess("end", true);
470     m_recorderStubFactory.assertSuccess("end", true);
471     m_recorderStubFactory.assertNoMoreCalls();
472   }
473 
474   @Test public void testPyDispatcherErrorHandling() throws Exception {
475     m_interpreter.exec("def blah(): raise Exception('a problem')");
476     final PyObject pyFunction = m_interpreter.get("blah");
477     final PyObject pyFunctionProxy = (PyObject)
478       createInstrumentedProxy(m_test, m_recorder, pyFunction);
479     try {
480       pyFunctionProxy.invoke("__call__");
481       fail("Expected PyException");
482     }
483     catch (PyException e) {
484       AssertUtilities.assertContains(e.toString(), "a problem");
485     }
486 
487     m_recorderStubFactory.assertSuccess("start");
488     m_recorderStubFactory.assertSuccess("end", false);
489     m_recorderStubFactory.assertNoMoreCalls();
490 
491     final UncheckedGrinderException e = new UncheckedInterruptedException(null);
492     m_recorderStubFactory.setThrows("start", e);
493 
494     try {
495       pyFunctionProxy.invoke("__call__");
496       fail("Expected UncheckedGrinderException");
497     }
498     catch (UncheckedGrinderException e2) {
499       assertSame(e, e2);
500     }
501     catch (PyException e3) {
502       assertSame(e, e3.value.__tojava__(Exception.class));
503     }
504   }
505 
506   @Test public void testSelectiveInstrumentUnsupported() throws Exception {
507     m_interpreter.exec(
508       "class Foo:\n" +
509       " def two(self): return 2\n" +
510       " def identity(self, x): return x\n" +
511       " def sum(self, x, y): return x + y\n" +
512       " def sum3(self, x, y, z): return x + y + z\n" +
513       "x=Foo()");
514 
515     final PyObject pyInstance = m_interpreter.get("x");
516 
517     final InstrumentationFilter filter = new InstrumentationFilter() {
518         public boolean matches(Object item) {
519           return true;
520         }
521       };
522 
523     try {
524       m_instrumenter.instrument(m_test, m_recorder, pyInstance, filter);
525       fail("Expected NonInstrumentableTypeException");
526     }
527     catch (NonInstrumentableTypeException e) {
528     }
529   }
530 }