PageRenderTime 23ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/webapptitude/testkit.py

https://gitlab.com/samba/webapptitude
Python | 282 lines | 245 code | 26 blank | 11 comment | 19 complexity | ba583e2196c815e92c592a9bc561a48b MD5 | raw file
  1. import unittest
  2. import traceback
  3. import sys
  4. import os
  5. import pdb
  6. import cStringIO
  7. import base64
  8. # from google.appengine.api import memcache
  9. from google.appengine.ext import testbed
  10. from google.appengine.ext import ndb
  11. from google.appengine.api import apiproxy_stub_map
  12. import webob
  13. import webapp2
  14. import webtest
  15. import logging
  16. import contextlib
  17. from webtest.http import StopableWSGIServer
  18. from util import odict
  19. try:
  20. from appengine_config import config as site_config
  21. except ImportError:
  22. site_config = {}
  23. site_config["webapp2_extras.sessions"] = {
  24. "secret_key": "TEST_SESSION_SECRET"
  25. }
  26. logger = logging.getLogger(__name__)
  27. logger.setLevel(logging.DEBUG)
  28. def environ_base64_buffer(name, default=None):
  29. """
  30. Read an environment variable, decode as base64, then render as a buffer.
  31. In some testing environments, sensitive information may be available as
  32. environment variables, encoded as base64 for compatibility. This provides
  33. a simple way to read them as though they were files.
  34. """
  35. value = os.environ.get(name, default)
  36. if isinstance(value, basestring):
  37. value = cStringIO.StringIO(base64.b64decode(value))
  38. return value
  39. @contextlib.contextmanager
  40. def debug_with(*exceptions):
  41. try:
  42. yield
  43. except exceptions:
  44. info = sys.exc_info()
  45. traceback.print_exception(*info)
  46. pdb.post_mortem(info[2])
  47. finally:
  48. pass
  49. def debug_on(*exceptions):
  50. def _decorate(func):
  51. def wrapper(*args, **kwargs):
  52. try:
  53. return func(*args, **kwargs)
  54. except exceptions:
  55. info = sys.exc_info()
  56. traceback.print_exception(*info)
  57. pdb.post_mortem(info[2])
  58. wrapper.__name__ = func.__name__
  59. wrapper.__doc__ = func.__doc__
  60. wrapper.__module__ = func.__module__
  61. return wrapper
  62. if len(exceptions) == 0:
  63. exceptions = (AssertionError,)
  64. return _decorate
  65. class Testbed(testbed.Testbed):
  66. service_alias = { # stub identifiers for common named services
  67. 'ndb': 'datastore_v3',
  68. 'datastore': 'datastore_v3',
  69. 'identity': 'app_identity_service',
  70. 'capability': 'capability_service'
  71. }
  72. def prepare(self, **kwargs):
  73. selected_stubs = kwargs.pop('stubs', None)
  74. app_path = kwargs.pop('apppath', None)
  75. if isinstance(app_path, basestring):
  76. if app_path.endswith('app.yaml'):
  77. app_path = app_path[:-8]
  78. else:
  79. app_path = os.path.dirname(os.path.dirname(__file__))
  80. self.activate()
  81. self.setup_env(overwrite=True, **kwargs)
  82. if selected_stubs is None:
  83. self.init_all_stubs()
  84. elif isinstance(selected_stubs, list):
  85. for stub in selected_stubs:
  86. self._init_stub(self.service_alias.get(stub, stub))
  87. if kwargs.pop('taskqueue', True):
  88. # This will fail when the taskqueue stub is not initialized.
  89. # This can happen when (for example) the 'stubs' arg is False.
  90. # taskqueue = apiproxy_stub_map.apiproxy.GetStub('taskqueue')
  91. taskqueue = self.get_stub('taskqueue')
  92. if taskqueue is not None:
  93. taskqueue._root_path = app_path
  94. @property
  95. def taskqueue_stub(self):
  96. return self.get_stub(testbed.TASKQUEUE_SERVICE_NAME)
  97. def loginUser(self, email='test@example.com', id='123', is_admin=False):
  98. self.setup_env(
  99. user_email=str(email), user_id=str(id),
  100. user_is_admin=('1' if is_admin else '0'),
  101. overwrite=True
  102. )
  103. def loginAnonymous(self):
  104. self.loginUser(email="", id=0, is_admin=False)
  105. class TestCase(unittest.TestCase):
  106. logger = logger
  107. noisy = False
  108. stubs = None
  109. stub_config = None
  110. @classmethod
  111. def application(cls, instance):
  112. """Construct a test application from an existing WSGI application."""
  113. assert isinstance(instance, webapp2.WSGIApplication)
  114. return webtest.TestApp(instance)
  115. @classmethod
  116. def application_from(cls, *handlers):
  117. """Construct a suitable test application from a mapping of handlers."""
  118. return cls.application(
  119. webapp2.WSGIApplication(handlers,
  120. config=site_config,
  121. debug=True)
  122. )
  123. @classmethod
  124. def notice(cls, message):
  125. if cls.noisy:
  126. cls.logger.warn(message)
  127. # print >>sys.stderr, message
  128. @classmethod
  129. @contextlib.contextmanager
  130. def prepare_webservice(cls, instance):
  131. """
  132. Spawn a thread for an application instance; this can be useful when a
  133. test utility requires reaching a TCP service, rather than passing
  134. calls through the WSGI standard.
  135. This context function yields a string, the URL root for the service
  136. thread, e.g. "htt://<hostname>:<port>"
  137. Usage:
  138. app = webapp2.WSGIApplication(...)
  139. class TestCase(helper.TestCase):
  140. def testRequestSomething(self):
  141. result = None
  142. with self.webservice as http:
  143. res = requests.get(http + '/path')
  144. result = res.status_code
  145. self.assertEqual(result, 200)
  146. """
  147. if isinstance(instance, webtest.TestApp):
  148. instance = instance.app
  149. assert isinstance(instance, webapp2.WSGIApplication)
  150. server = StopableWSGIServer.create(instance)
  151. host = server.adj.host
  152. port = server.adj.port
  153. try:
  154. cls.notice("Service started on %r" % ([host, port]))
  155. yield 'http://%s:%s' % (host, port)
  156. finally:
  157. cls.notice("Shutting down service on %r" % ([host, port]))
  158. server.shutdown()
  159. @property
  160. def webservice(self):
  161. """Shortcut for constructing a service thread."""
  162. assert isinstance(self.testapp, webtest.TestApp)
  163. return self.prepare_webservice(self.testapp.app)
  164. @classmethod
  165. def setUpClass(cls):
  166. testbed_options = cls.stub_config or {}
  167. cls.testbed = Testbed()
  168. cls.testbed.prepare(stubs=cls.stubs, **testbed_options)
  169. app = getattr(cls, 'getHandlers', None)
  170. if callable(app):
  171. handlers = app()
  172. if isinstance(handlers, webapp2.WSGIApplication):
  173. cls.testapp = cls.application(handlers)
  174. else:
  175. cls.testapp = cls.application_from(*list(handlers))
  176. def tearDown(self):
  177. # After each test, we want the cache reset to mitigate side-effects.
  178. ndb.get_context().clear_cache()
  179. @classmethod
  180. def tearDownClass(cls):
  181. cls.testbed.deactivate()
  182. def iter_queue_tasks(self, *queue_names):
  183. """
  184. Execute the queue. This assumes a sequence is already enqueued.
  185. Because this test environment does not leverage a proper web service
  186. thread, we simulate the queue-runner here, until the queue is empty.
  187. """
  188. taskqueue = self.testbed.taskqueue_stub
  189. while True:
  190. tasks_performed = 0
  191. for q in queue_names:
  192. tasks = taskqueue.get_filtered_tasks(queue_names=q)
  193. tasks_performed = tasks_performed + len(tasks)
  194. logging.info('Queue runner found %d tasks' % (len(tasks)))
  195. for task in tasks:
  196. yield q, task
  197. if not tasks_performed:
  198. break
  199. def assertResponse(self, response, body=None, response_code=None,
  200. content_type=None):
  201. """Shortcut for assessing one or multiple attributes of a response."""
  202. self.assertIsInstance(response, webob.Response)
  203. if isinstance(response_code, int):
  204. self.assertEqual(response.status_int, response_code)
  205. if isinstance(response_code, (list, tuple)):
  206. self.assertIn(response.status_int, response_code)
  207. if isinstance(content_type, basestring):
  208. self.assertEqual(response.content_type, content_type)
  209. if isinstance(body, basestring):
  210. self.assertEqual(response.normal_body, body)
  211. # For compatibility only...
  212. ServiceRequestCase = type("ServiceRequestCase", (TestCase,), {})
  213. def ApplicationTestCase(application):
  214. """Construct a testcase class based on an application instance."""
  215. @classmethod
  216. def handlers(cls):
  217. return application
  218. return type("TestCase_Application", (TestCase,), {'getHandlers': handlers})