diff tests/test_pymonkey.py @ 117:ac8ca0ee7760

Moved all .cpp/.h files into 'src' dir and test suite into 'tests' dir.
author Atul Varma <varmaa@toolness.com>
date Mon, 17 Aug 2009 22:08:33 -0700
parents test_pymonkey.py@00c1351b3e82
children 856ca7a139e4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_pymonkey.py	Mon Aug 17 22:08:33 2009 -0700
@@ -0,0 +1,575 @@
+import sys
+import unittest
+import weakref
+import time
+import threading
+
+import pymonkey
+
+class PymonkeyTests(unittest.TestCase):
+    def _evaljs(self, code):
+        rt = pymonkey.Runtime()
+        cx = rt.new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        return cx.evaluate_script(obj, code, '<string>', 1)
+
+    def _evalJsWrappedPyFunc(self, func, code):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc = cx.new_function(func, func.__name__)
+        cx.define_property(obj, func.__name__, jsfunc)
+        return cx.evaluate_script(obj, code, '<string>', 1)
+
+    def assertRaises(self, exctype, func, *args):
+        was_raised = False
+        try:
+            func(*args)
+        except exctype, e:
+            self.last_exception = e
+            was_raised = True
+        self.assertTrue(was_raised)
+
+    def testThreadSafetyExceptionIsRaised(self):
+        stuff = {}
+        def make_runtime():
+            stuff['rt'] = pymonkey.Runtime()
+        thread = threading.Thread(target = make_runtime)
+        thread.start()
+        thread.join()
+        self.assertRaises(pymonkey.error,
+                          stuff['rt'].new_context)
+        self.assertEqual(self.last_exception.args[0],
+                         'Function called from wrong thread')
+
+    def testClearObjectPrivateWorks(self):
+        class Foo(object):
+            pass
+        pyobj = Foo()
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object(pyobj)
+        pyobj = weakref.ref(pyobj)
+        self.assertEqual(pyobj(), cx.get_object_private(obj))
+        cx.clear_object_private(obj)
+        self.assertEqual(cx.get_object_private(obj), None)
+        self.assertEqual(pyobj(), None)
+
+    def testGetObjectPrivateWorks(self):
+        class Foo(object):
+            pass
+        pyobj = Foo()
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object(pyobj)
+        pyobj = weakref.ref(pyobj)
+        self.assertEqual(pyobj(), cx.get_object_private(obj))
+        del obj
+        del cx
+        self.assertEqual(pyobj(), None)
+
+    def testOperationCallbackIsCalled(self):
+        def opcb(cx):
+            raise Exception("stop eet!")
+
+        cx = pymonkey.Runtime().new_context()
+        cx.set_operation_callback(opcb)
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+
+        def watchdog():
+            time.sleep(0.1)
+            cx.trigger_operation_callback()
+
+        thread = threading.Thread(target = watchdog)
+        thread.start()
+
+        self.assertRaises(
+            pymonkey.error,
+            cx.evaluate_script,
+            obj, 'while (1) {}', '<string>', 1
+            )
+
+    def testUndefinedStrIsUndefined(self):
+        self.assertEqual(str(pymonkey.undefined),
+                         "pymonkey.undefined")
+
+    def testJsWrappedPythonFuncHasPrivate(self):
+        def foo(cx, this, args):
+            pass
+
+        cx = pymonkey.Runtime().new_context()
+        jsfunc = cx.new_function(foo, foo.__name__)
+        self.assertEqual(cx.get_object_private(jsfunc), foo)
+
+    def testJsWrappedPythonFuncIsNotGCd(self):
+        def define(cx, obj):
+            def func(cx, this, args):
+                return u'func was called'
+            jsfunc = cx.new_function(func, func.__name__)
+            cx.define_property(obj, func.__name__, jsfunc)
+            return weakref.ref(func)
+        rt = pymonkey.Runtime()
+        cx = rt.new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        ref = define(cx, obj)
+        cx.gc()
+        self.assertNotEqual(ref(), None)
+        result = cx.evaluate_script(obj, 'func()', '<string>', 1)
+        self.assertEqual(result, u'func was called')
+
+        # Now ensure that the wrapped function is GC'd when it's
+        # no longer reachable from JS space.
+        cx.define_property(obj, 'func', 0)
+        cx.gc()
+        self.assertEqual(ref(), None)
+
+    def testCircularJsWrappedPythonFuncIsGCdIfPrivateCleared(self):
+        def define(cx, obj):
+            rt = cx.get_runtime()
+            def func(cx, this, args):
+                # Oh noes, a circular reference is born!
+                rt
+            jsfunc = cx.new_function(func, func.__name__)
+            cx.define_property(obj, func.__name__, jsfunc)
+            return (jsfunc, weakref.ref(func))
+        rt = pymonkey.Runtime()
+        cx = rt.new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc, ref = define(cx, obj)
+
+        # This will break the circular reference.
+        cx.clear_object_private(jsfunc)
+
+        del jsfunc
+        del rt
+        del cx
+        del obj
+        self.assertEqual(ref(), None)
+
+    def testJsWrappedPythonFuncIsGCdAtRuntimeDestruction(self):
+        def define(cx, obj):
+            def func(cx, this, args):
+                return u'func was called'
+            jsfunc = cx.new_function(func, func.__name__)
+            cx.define_property(obj, func.__name__, jsfunc)
+            return weakref.ref(func)
+        rt = pymonkey.Runtime()
+        cx = rt.new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        ref = define(cx, obj)
+        del rt
+        del cx
+        del obj
+        self.assertEqual(ref(), None)
+
+    def testJsWrappedPythonFuncThrowsExcIfPrivateCleared(self):
+        def func(cx, this, args):
+            return True
+
+        code = "func()"
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc = cx.new_function(func, func.__name__)
+        cx.define_property(obj, func.__name__, jsfunc)
+        cx.clear_object_private(jsfunc)
+        self.assertRaises(pymonkey.error,
+                          cx.evaluate_script,
+                          obj, code, '<string>', 1)
+        self.assertEqual(
+            self._tostring(cx, self.last_exception.args[0]),
+            "Error: Wrapped Python function no longer exists"
+            )
+
+    def testJsWrappedPythonFuncPassesContext(self):
+        contexts = []
+
+        def func(cx, this, args):
+            contexts.append(cx)
+            return True
+
+        code = "func()"
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc = cx.new_function(func, func.__name__)
+        cx.define_property(obj, func.__name__, jsfunc)
+        cx.evaluate_script(obj, code, '<string>', 1)
+        self.assertEqual(contexts[0], cx)
+
+    def testJsWrappedPythonFuncPassesThisArg(self):
+        thisObjs = []
+
+        def func(cx, this, args):
+            thisObjs.append(this)
+            return True
+
+        code = "func()"
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc = cx.new_function(func, func.__name__)
+        cx.define_property(obj, func.__name__, jsfunc)
+        cx.evaluate_script(obj, code, '<string>', 1)
+        self.assertEqual(thisObjs[0], obj)
+
+    def testJsWrappedPythonFuncPassesFuncArgs(self):
+        funcArgs = []
+
+        def func(cx, this, args):
+            funcArgs.append(args)
+            return True
+
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc = cx.new_function(func, func.__name__)
+        cx.define_property(obj, func.__name__, jsfunc)
+
+        cx.evaluate_script(obj, "func()", '<string>', 1)
+        self.assertEqual(len(funcArgs[0]), 0)
+        self.assertTrue(isinstance(funcArgs[0], tuple))
+
+        cx.evaluate_script(obj, "func(1, 'foo')", '<string>', 1)
+        self.assertEqual(len(funcArgs[1]), 2)
+        self.assertEqual(funcArgs[1][0], 1)
+        self.assertEqual(funcArgs[1][1], u'foo')
+
+    def testJsWrappedPythonFunctionReturnsUnicodeWithEmbeddedNULs(self):
+        def hai2u(cx, this, args):
+            return args[0] + u"o hai"
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u,
+                                                   'hai2u("blah\x00 ")'),
+                         u"blah\x00 o hai")
+
+    def testJsWrappedPythonFunctionReturnsString(self):
+        def hai2u(cx, this, args):
+            return "o hai"
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         "o hai")
+
+    def testJsWrappedPythonFunctionReturnsUnicode(self):
+        def hai2u(cx, this, args):
+            return u"o hai\u2026"
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         u"o hai\u2026")
+
+    def testJsWrappedPythonFunctionThrowsJsException(self):
+        def hai2u(cx, this, args):
+            raise pymonkey.error(u"blarg")
+        self.assertRaises(pymonkey.error,
+                          self._evalJsWrappedPyFunc,
+                          hai2u, 'hai2u()')
+        self.assertEqual(self.last_exception.args[0], u"blarg")
+
+    def testJsWrappedPythonFunctionThrowsPyException(self):
+        thecx = []
+        def hai2u(cx, this, args):
+            thecx.append(cx)
+            raise Exception("hello")
+        self.assertRaises(pymonkey.error,
+                          self._evalJsWrappedPyFunc,
+                          hai2u, 'hai2u()')
+        exc = thecx[0].get_object_private(self.last_exception.args[0])
+        self.assertEqual(exc.args[0], "hello")
+
+    def testJsWrappedPythonFunctionReturnsNone(self):
+        def hai2u(cx, this, args):
+            pass
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         None)
+
+    def testJsWrappedPythonFunctionReturnsTrue(self):
+        def hai2u(cx, this, args):
+            return True
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         True)
+
+    def testJsWrappedPythonFunctionReturnsFalse(self):
+        def hai2u(cx, this, args):
+            return False
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         False)
+
+    def testJsWrappedPythonFunctionReturnsSmallInt(self):
+        def hai2u(cx, this, args):
+            return 5
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         5)
+
+    def testJsWrappedPythonFunctionReturnsFloat(self):
+        def hai2u(cx, this, args):
+            return 5.1
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         5.1)
+
+    def testJsWrappedPythonFunctionReturnsNegativeInt(self):
+        def hai2u(cx, this, args):
+            return -5
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         -5)
+
+    def testJsWrappedPythonFunctionReturnsBigInt(self):
+        def hai2u(cx, this, args):
+            return 2147483647
+        self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
+                         2147483647)
+
+    def testDefinePropertyWorksWithUnicodePropertyNames(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        foo = cx.new_object()
+        cx.define_property(obj, u"foo\u2026", foo)
+        self.assertEqual(
+            cx.get_property(obj, u"foo\u2026"),
+            foo
+            )
+
+    def testDefinePropertyWorksWithObject(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        foo = cx.new_object()
+        cx.define_property(obj, "foo", foo)
+        self.assertEqual(
+            cx.evaluate_script(obj, 'foo', '<string>', 1),
+            foo
+            )
+
+    def testDefinePropertyWorksWithString(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        foo = cx.new_object()
+        cx.define_property(obj, "foo", u"hello")
+        self.assertEqual(
+            cx.evaluate_script(obj, 'foo', '<string>', 1),
+            u"hello"
+            )
+
+    def testObjectIsIdentityPreserving(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        cx.evaluate_script(obj, 'var foo = {bar: 1}', '<string>', 1)
+        self.assertTrue(isinstance(cx.get_property(obj, u"foo"),
+                                   pymonkey.Object))
+        self.assertTrue(cx.get_property(obj, u"foo") is
+                        cx.get_property(obj, "foo"))
+
+    def testObjectGetattrThrowsException(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        result = cx.evaluate_script(obj, '({get foo() { throw "blah"; }})',
+                                    '<string>', 1)
+        self.assertRaises(pymonkey.error,
+                          cx.get_property,
+                          result,
+                          u"foo")
+        self.assertEqual(self.last_exception.args[0], u"blah")
+
+    def testInfiniteRecursionRaisesError(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        self.assertRaises(
+            pymonkey.error,
+            cx.evaluate_script,
+            obj, '(function foo() { foo(); })();', '<string>', 1
+            )
+        self.assertEqual(
+            self._tostring(cx, self.last_exception.args[0]),
+            "InternalError: too much recursion"
+            )
+
+    def testObjectGetattrWorks(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        cx.evaluate_script(obj, 'var boop = 5', '<string>', 1)
+        cx.evaluate_script(obj, 'this["blarg\u2026"] = 5', '<string>', 1)
+        self.assertEqual(cx.get_property(obj, u"beans"),
+                         pymonkey.undefined)
+        self.assertEqual(cx.get_property(obj, u"blarg\u2026"), 5)
+        self.assertEqual(cx.get_property(obj, u"boop"), 5)
+
+    def testContextIsInstance(self):
+        cx = pymonkey.Runtime().new_context()
+        self.assertTrue(isinstance(cx, pymonkey.Context))
+
+    def testContextTypeCannotBeInstantiated(self):
+        self.assertRaises(TypeError, pymonkey.Context)
+
+    def testObjectIsInstance(self):
+        obj = pymonkey.Runtime().new_context().new_object()
+        self.assertTrue(isinstance(obj, pymonkey.Object))
+        self.assertFalse(isinstance(obj, pymonkey.Function))
+
+    def testObjectTypeCannotBeInstantiated(self):
+        self.assertRaises(TypeError, pymonkey.Object)
+
+    def testFunctionIsInstance(self):
+        def boop():
+            pass
+        obj = pymonkey.Runtime().new_context().new_function(boop, "boop")
+        self.assertTrue(isinstance(obj, pymonkey.Object))
+        self.assertTrue(isinstance(obj, pymonkey.Function))
+
+    def testFunctionTypeCannotBeInstantiated(self):
+        self.assertRaises(TypeError, pymonkey.Function)
+
+    def testObjectGetRuntimeWorks(self):
+        rt = pymonkey.Runtime()
+        obj = rt.new_context().new_object()
+        self.assertEqual(obj.get_runtime(), rt)
+
+    def testContextGetRuntimeWorks(self):
+        rt = pymonkey.Runtime()
+        cx = rt.new_context()
+        self.assertEqual(cx.get_runtime(), rt)
+
+    def testUndefinedCannotBeInstantiated(self):
+        self.assertRaises(TypeError, pymonkey.undefined)
+
+    def testEvaluateThrowsException(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        self.assertRaises(pymonkey.error,
+                          cx.evaluate_script,
+                          obj, 'hai2u()', '<string>', 1)
+        self.assertEqual(self._tostring(cx,
+                                        self.last_exception.args[0]),
+                         'ReferenceError: hai2u is not defined')
+
+    def testEvaluateReturnsUndefined(self):
+        retval = self._evaljs("")
+        self.assertTrue(retval is pymonkey.undefined)
+
+    def testEvaludateReturnsUnicodeWithEmbeddedNULs(self):
+        retval = self._evaljs("'\x00hi'")
+        self.assertEqual(retval, u'\x00hi')
+
+    def testEvaluateReturnsSMPUnicode(self):
+        # This is 'LINEAR B SYLLABLE B008 A', in the supplementary
+        # multilingual plane (SMP).
+        retval = self._evaljs("'\uD800\uDC00'")
+        self.assertEqual(retval, u'\U00010000')
+        self.assertEqual(retval.encode('utf-16'),
+                         '\xff\xfe\x00\xd8\x00\xdc')
+
+    def testEvaluateReturnsBMPUnicode(self):
+        retval = self._evaljs("'o hai\u2026'")
+        self.assertTrue(type(retval) == unicode)
+        self.assertEqual(retval, u'o hai\u2026')
+
+    def testEvaluateReturnsObject(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        obj = cx.evaluate_script(obj, '({boop: 1})', '<string>', 1)
+        self.assertTrue(isinstance(obj, pymonkey.Object))
+        self.assertEqual(cx.get_property(obj, u"boop"), 1)
+
+    def testEvaluateReturnsFunction(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        obj = cx.evaluate_script(obj, '(function boop() { return 1; })',
+                                 '<string>', 1)
+        self.assertTrue(isinstance(obj, pymonkey.Function))
+
+    def testJsExceptionStateIsClearedAfterExceptionIsCaught(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        self.assertRaises(pymonkey.error,
+                          cx.evaluate_script,
+                          obj, 'blah()', '<string>', 1)
+        self.assertEqual(cx.evaluate_script(obj, '5+3', '<string>', 1),
+                         8)
+
+    def testCallFunctionRaisesErrorOnBadFuncArgs(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        obj = cx.evaluate_script(
+            obj,
+            '(function boop(a, b) { return a+b+this.c; })',
+            '<string>', 1
+            )
+        self.assertRaises(
+            NotImplementedError,
+            cx.call_function,
+            obj, obj, (1, self)
+            )
+
+    def _tostring(self, cx, obj):
+        return cx.call_function(obj,
+                                cx.get_property(obj, u"toString"),
+                                ())
+
+    def testCallFunctionRaisesErrorFromJS(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        obj = cx.evaluate_script(
+            obj,
+            '(function boop(a, b) { blarg(); })',
+            '<string>', 1
+            )
+        self.assertRaises(pymonkey.error,
+                          cx.call_function,
+                          obj, obj, (1,))
+        self.assertEqual(self._tostring(cx,
+                                        self.last_exception.args[0]),
+                         'ReferenceError: blarg is not defined')
+
+    def testInitStandardClassesRaisesExcOnRuntimeMismatch(self):
+        cx2 = pymonkey.Runtime().new_context()
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        self.assertRaises(ValueError,
+                          cx2.init_standard_classes,
+                          obj)
+        self.assertEqual(self.last_exception.args[0],
+                         'JS runtime mismatch')
+
+    def testCallFunctionWorks(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        thisArg = cx.new_object()
+        cx.define_property(thisArg, "c", 3)
+        cx.init_standard_classes(obj)
+        obj = cx.evaluate_script(
+            obj,
+            '(function boop(a, b) { return a+b+this.c; })',
+            '<string>', 1
+            )
+        self.assertEqual(cx.call_function(thisArg, obj, (1,2)), 6)
+
+    def testEvaluateReturnsTrue(self):
+        self.assertTrue(self._evaljs('true') is True)
+
+    def testEvaluateReturnsFalse(self):
+        self.assertTrue(self._evaljs('false') is False)
+
+    def testEvaluateReturnsNone(self):
+        self.assertTrue(self._evaljs('null') is None)
+
+    def testEvaluateReturnsIntegers(self):
+        self.assertEqual(self._evaljs('1+3'), 4)
+
+    def testEvaluateReturnsNegativeIntegers(self):
+        self.assertEqual(self._evaljs('-5'), -5)
+
+    def testEvaluateReturnsBigIntegers(self):
+        self.assertEqual(self._evaljs('2147483647*2'),
+                         2147483647*2)
+
+    def testEvaluateReturnsFloats(self):
+        self.assertEqual(self._evaljs('1.1+3'), 4.1)
+
+if __name__ == '__main__':
+    unittest.main()