############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os, unittest, warnings from Products.PythonScripts.PythonScript import PythonScript from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager from RestrictedPython.tests.verify import verify if __name__=='__main__': here = os.getcwd() else: here = os.path.dirname(__file__) if not here: here = os.getcwd() class WarningInterceptor: _old_stderr = None _our_stderr_stream = None def _trap_warning_output( self ): if self._old_stderr is not None: return import sys from StringIO import StringIO self._old_stderr = sys.stderr self._our_stderr_stream = sys.stderr = StringIO() def _free_warning_output( self ): if self._old_stderr is None: return import sys sys.stderr = self._old_stderr # Test Classes def readf(name): path = os.path.join(here, 'tscripts', '%s.ps' % name) return open(path, 'r').read() class VerifiedPythonScript(PythonScript): def _newfun(self, code): verify(code) return PythonScript._newfun(self, code) class PythonScriptTestBase(unittest.TestCase): def setUp(self): newSecurityManager(None, None) def tearDown(self): noSecurityManager() def _newPS(self, txt, bind=None): ps = VerifiedPythonScript('ps') ps.ZBindings_edit(bind or {}) ps.write(txt) ps._makeFunction() if ps.errors: raise SyntaxError, ps.errors[0] return ps def _filePS(self, fname, bind=None): ps = VerifiedPythonScript(fname) ps.ZBindings_edit(bind or {}) ps.write(readf(fname)) ps._makeFunction() if ps.errors: raise SyntaxError, ps.errors[0] return ps class TestPythonScriptNoAq(PythonScriptTestBase): def testEmpty(self): empty = self._newPS('')() self.failUnless(empty is None) def testIndented(self): # This failed to compile in Zope 2.4.0b2. res = self._newPS('if 1:\n return 2')() self.assertEqual(res, 2) def testReturn(self): res = self._newPS('return 1')() self.assertEqual(res, 1) def testReturnNone(self): res = self._newPS('return')() self.failUnless(res is None) def testParam1(self): res = self._newPS('##parameters=x\nreturn x')('txt') self.assertEqual(res, 'txt') def testParam2(self): eq = self.assertEqual one, two = self._newPS('##parameters=x,y\nreturn x,y')('one','two') eq(one, 'one') eq(two, 'two') def testParam26(self): import string params = string.letters[:26] sparams = ','.join(params) ps = self._newPS('##parameters=%s\nreturn %s' % (sparams, sparams)) res = ps(*params) self.assertEqual(res, tuple(params)) def testArithmetic(self): res = self._newPS('return 1 * 5 + 4 / 2 - 6')() self.assertEqual(res, 1) def testReduce(self): res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7])')() self.assertEqual(res, 16) res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7], 1)')() self.assertEqual(res, 17) def testImport(self): eq = self.assertEqual a, b, c = self._newPS('import string; return string.split("a b c")')() eq(a, 'a') eq(b, 'b') eq(c, 'c') def testWhileLoop(self): res = self._filePS('while_loop')() self.assertEqual(res, 1) def testForLoop(self): res = self._filePS('for_loop')() self.assertEqual(res, 10) def testMutateLiterals(self): eq = self.assertEqual l, d = self._filePS('mutate_literals')() eq(l, [2]) eq(d, {'b': 2}) def testTupleUnpackAssignment(self): eq = self.assertEqual d, x = self._filePS('tuple_unpack_assignment')() eq(d, {'a': 0, 'b': 1, 'c': 2}) eq(x, 3) def testDoubleNegation(self): res = self._newPS('return not not "this"')() self.assertEqual(res, 1) def testTryExcept(self): eq = self.assertEqual a, b = self._filePS('try_except')() eq(a, 1) eq(b, 1) def testBigBoolean(self): res = self._filePS('big_boolean')() self.failUnless(res) def testFibonacci(self): res = self._filePS('fibonacci')() self.assertEqual( res, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986]) def testSimplePrint(self): res = self._filePS('simple_print')() self.assertEqual(res, 'a 1 []\n') def testComplexPrint(self): res = self._filePS('complex_print')() self.assertEqual(res, 'double\ndouble\nx: 1\ny: 0 1 2\n\n') def testNSBind(self): f = self._filePS('ns_bind', bind={'name_ns': '_'}) bound = f.__render_with_namespace__({'yes': 1, 'no': self.fail}) self.assertEqual(bound, 1) def testBooleanMap(self): res = self._filePS('boolean_map')() self.failUnless(res) def testGetSize(self): f = self._filePS('complex_print') self.assertEqual(f.get_size(), len(f.read())) class TestPythonScriptErrors(PythonScriptTestBase): def assertPSRaises(self, error, path=None, body=None): assert not (path and body) and (path or body) if body is None: body = readf(path) if error is SyntaxError: self.assertRaises(SyntaxError, self._newPS, body) else: ps = self._newPS(body) self.assertRaises(error, ps) def testSubversiveExcept(self): self.assertPSRaises(SyntaxError, path='subversive_except') def testBadImports(self): self.assertPSRaises(ImportError, body="from string import *") self.assertPSRaises(ImportError, body="from datetime import datetime") #self.assertPSRaises(ImportError, body="import mmap") def testAttributeAssignment(self): # It's illegal to assign to attributes of anything that # doesn't has enabling security declared. # Classes (and their instances) defined by restricted code # are an exception -- they are fully readable and writable. cases = [("import string", "string"), ("def f(): pass", "f"), ] assigns = ["%s.splat = 'spam'", "setattr(%s, '_getattr_', lambda x, y: True)", "del %s.splat", ] for defn, name in cases: for asn in assigns: f = self._newPS(defn + "\n" + asn % name) self.assertRaises(TypeError, f) class TestPythonScriptGlobals(PythonScriptTestBase, WarningInterceptor): def setUp(self): PythonScriptTestBase.setUp(self) def tearDown(self): self._free_warning_output() PythonScriptTestBase.tearDown(self) def _exec(self, script, bound_names=None, args=None, kws=None): if args is None: args = () if kws is None: kws = {} bindings = {'name_container': 'container'} f = self._filePS(script, bindings) return f._exec(bound_names, args, kws) def testGlobalIsDeclaration(self): bindings = {'container': 7} results = self._exec('global_is_declaration', bindings) self.assertEqual(results, 8) def test__name__(self): f = self._filePS('class.__name__') self.assertEqual(f(), ("'foo'>", "'string'")) def test_filepath(self): # This test is meant to raise a deprecation warning. # It used to fail mysteriously instead. self._trap_warning_output() f = self._filePS('filepath') self.assertEqual(f(), [0]) self._free_warning_output() def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestPythonScriptNoAq)) suite.addTest(unittest.makeSuite(TestPythonScriptErrors)) suite.addTest(unittest.makeSuite(TestPythonScriptGlobals)) return suite def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main()