From c844d02f1c7558e959891de75494cffd22dc323f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 12:13:55 +0100 Subject: [PATCH 01/43] Added the APPLICATION_ROOT configuration variable which is used by session backends. --- CHANGES | 1 + docs/config.rst | 10 +++++++++- flask/app.py | 1 + flask/sessions.py | 12 ++++++++++-- tests/flask_tests.py | 22 ++++++++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index dc288966..6ba980aa 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Relase date to be decided, codename to be chosen. conceptionally only instance depending and outside version control so it's the perfect place to put configuration files etc. For more information see :ref:`instance-folders`. +- Added the ``APPLICATION_ROOT`` configuration variable. Version 0.7.3 ------------- diff --git a/docs/config.rst b/docs/config.rst index 57bea926..eb9d0e06 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -77,6 +77,13 @@ The following configuration values are used internally by Flask: ``SERVER_NAME`` the name and port number of the server. Required for subdomain support (e.g.: ``'localhost:5000'``) +``APPLICATION_ROOT`` If the application does not occupy + a whole domain or subdomain this can + be set to the path where the application + is configured to live. This is for + session cookie as path value. If + domains are used, this should be + ``None``. ``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will reject incoming requests with a content length greater than this by @@ -134,7 +141,8 @@ The following configuration values are used internally by Flask: ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` .. versionadded:: 0.8 - ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS`` + ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, + ``APPLICATION_ROOT`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 35577984..6ad69975 100644 --- a/flask/app.py +++ b/flask/app.py @@ -236,6 +236,7 @@ class Flask(_PackageBoundObject): 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'SERVER_NAME': None, + 'APPLICATION_ROOT': None, 'MAX_CONTENT_LENGTH': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False diff --git a/flask/sessions.py b/flask/sessions.py index ee006cda..fda84a25 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -127,6 +127,13 @@ class SessionInterface(object): # chop of the port which is usually not supported by browsers return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] + def get_cookie_path(self, app): + """Returns the path for which the cookie should be valid. The + default implementation uses the value from the ``APPLICATION_ROOT`` + configuration variable or uses ``/`` if it's `None`. + """ + return app.config['APPLICATION_ROOT'] or '/' + def get_expiration_time(self, app, session): """A helper method that returns an expiration date for the session or `None` if the session is linked to the browser session. The @@ -169,9 +176,10 @@ class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): expires = self.get_expiration_time(app, session) domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) if session.modified and not session: - response.delete_cookie(app.session_cookie_name, + response.delete_cookie(app.session_cookie_name, path=path, domain=domain) else: - session.save_cookie(response, app.session_cookie_name, + session.save_cookie(response, app.session_cookie_name, path=path, expires=expires, httponly=True, domain=domain) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 220bf4cb..db8275d6 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -339,6 +339,28 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert 'domain=.example.com' in rv.headers['set-cookie'].lower() assert 'httponly' in rv.headers['set-cookie'].lower() + def test_session_using_application_root(self): + class PrefixPathMiddleware(object): + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + def __call__(self, environ, start_response): + environ['SCRIPT_NAME'] = self.prefix + return self.app(environ, start_response) + + app = flask.Flask(__name__) + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') + app.config.update( + SECRET_KEY='foo', + APPLICATION_ROOT='/bar' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/') + assert 'path=/bar' in rv.headers['set-cookie'].lower() + def test_missing_session(self): app = flask.Flask(__name__) def expect_exception(f, *args, **kwargs): From a5da2c98f3ed2f9fe254e3b61799b06fa230a175 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:18:39 +0100 Subject: [PATCH 02/43] Implemented flask.testing.TestClient.session_transaction for quick session modifications in test environments. --- CHANGES | 2 ++ docs/api.rst | 9 +++++++ flask/app.py | 2 ++ flask/testing.py | 58 +++++++++++++++++++++++++++++++++++++++++--- tests/flask_tests.py | 45 ++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 6ba980aa..a451b978 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,8 @@ Relase date to be decided, codename to be chosen. the perfect place to put configuration files etc. For more information see :ref:`instance-folders`. - Added the ``APPLICATION_ROOT`` configuration variable. +- Implemented :meth:`~flask.testing.TestClient.session_transaction` to + easily modify sessions from the test environment. Version 0.7.3 ------------- diff --git a/docs/api.rst b/docs/api.rst index 6b695bfa..f4fab86f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -218,6 +218,15 @@ implementation that Flask is using. :members: +Test Client +----------- + +.. currentmodule:: flask.testing + +.. autoclass:: TestClient + :members: + + Application Globals ------------------- diff --git a/flask/app.py b/flask/app.py index 6ad69975..20cbca52 100644 --- a/flask/app.py +++ b/flask/app.py @@ -706,6 +706,8 @@ class Flask(_PackageBoundObject): rv = c.get('/?vodka=42') assert request.args['vodka'] == '42' + See :class:`~flask.testing.TestClient` for more information. + .. versionchanged:: 0.4 added support for `with` block usage for the client. diff --git a/flask/testing.py b/flask/testing.py index 06a2c016..c1844c00 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,19 +10,69 @@ :license: BSD, see LICENSE for more details. """ +from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack class FlaskClient(Client): - """Works like a regular Werkzeug test client but has some - knowledge about how Flask works to defer the cleanup of the - request context stack to the end of a with body when used - in a with statement. + """Works like a regular Werkzeug test client but has some knowledge about + how Flask works to defer the cleanup of the request context stack to the + end of a with body when used in a with statement. For general information + about how to use this class refer to :class:`werkzeug.test.Client`. + + Basic usage is outlined in the :ref:`testing` chapter. """ preserve_context = context_preserved = False + @contextmanager + def session_transaction(self, *args, **kwargs): + """When used in combination with a with statement this opens a + session transaction. This can be used to modify the session that + the test client uses. Once the with block is left the session is + stored back. + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + app = self.application + environ_overrides = kwargs.pop('environ_overrides', {}) + if self.cookie_jar is not None: + self.cookie_jar.inject_wsgi(environ_overrides) + outer_reqctx = _request_ctx_stack.top + with app.test_request_context(*args, **kwargs) as c: + sess = app.open_session(c.request) + if sess is None: + raise RuntimeError('Session backend did not open a session. ' + 'Check the configuration') + + # Since we have to open a new request context for the session + # handling we want to make sure that we hide out own context + # from the caller. By pushing the original request context + # (or None) on top of this and popping it we get exactly that + # behavior. It's important to not use the push and pop + # methods of the actual request context object since that would + # mean that cleanup handlers are called + _request_ctx_stack.push(outer_reqctx) + try: + yield sess + finally: + _request_ctx_stack.pop() + + resp = app.response_class() + if not app.session_interface.is_null_session(sess): + app.save_session(sess, resp) + if self.cookie_jar is not None: + headers = resp.get_wsgi_headers(c.request.environ) + self.cookie_jar.extract_wsgi(c.request.environ, headers) + def open(self, *args, **kwargs): if self.context_preserved: _request_ctx_stack.pop() diff --git a/tests/flask_tests.py b/tests/flask_tests.py index db8275d6..3c125be4 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1028,6 +1028,50 @@ class BasicFunctionalityTestCase(unittest.TestCase): self.assertEqual(rv.data, 'success') +class TestToolsTestCase(unittest.TestCase): + + def test_session_transactions(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + @app.route('/') + def index(): + return unicode(flask.session['foo']) + + with app.test_client() as c: + with c.session_transaction() as sess: + self.assertEqual(len(sess), 0) + sess['foo'] = [42] + self.assertEqual(len(sess), 1) + rv = c.get('/') + self.assertEqual(rv.data, '[42]') + + def test_session_transactions_no_null_sessions(self): + app = flask.Flask(__name__) + app.testing = True + + with app.test_client() as c: + try: + with c.session_transaction() as sess: + pass + except RuntimeError, e: + self.assert_('Session backend did not open a session' in str(e)) + else: + self.fail('Expected runtime error') + + def test_session_transactions_keep_context(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + with app.test_client() as c: + rv = c.get('/') + req = flask.request._get_current_object() + with c.session_transaction(): + self.assert_(req is flask.request._get_current_object()) + + class InstanceTestCase(unittest.TestCase): def test_explicit_instance_paths(self): @@ -2209,6 +2253,7 @@ def suite(): suite.addTest(unittest.makeSuite(SubdomainTestCase)) suite.addTest(unittest.makeSuite(ViewTestCase)) suite.addTest(unittest.makeSuite(DeprecationsTestCase)) + suite.addTest(unittest.makeSuite(TestToolsTestCase)) suite.addTest(unittest.makeSuite(InstanceTestCase)) if flask.json_available: suite.addTest(unittest.makeSuite(JSONTestCase)) From 001a5128d87e2cb934d14244d901d47e01980873 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:20:40 +0100 Subject: [PATCH 03/43] Refactored tests to use a different subclass --- tests/flask_tests.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 3c125be4..743339f0 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -95,7 +95,11 @@ def emits_module_deprecation_warning(f): return update_wrapper(new_f, f) -class ContextTestCase(unittest.TestCase): +class FlaskTestCase(unittest.TestCase): + pass + + +class ContextTestCase(FlaskTestCase): def test_context_binding(self): app = flask.Flask(__name__) @@ -172,7 +176,7 @@ class ContextTestCase(unittest.TestCase): raise AssertionError('some kind of exception expected') -class BasicFunctionalityTestCase(unittest.TestCase): +class BasicFunctionalityTestCase(FlaskTestCase): def test_options_work(self): app = flask.Flask(__name__) @@ -1028,7 +1032,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): self.assertEqual(rv.data, 'success') -class TestToolsTestCase(unittest.TestCase): +class TestToolsTestCase(FlaskTestCase): def test_session_transactions(self): app = flask.Flask(__name__) @@ -1072,7 +1076,7 @@ class TestToolsTestCase(unittest.TestCase): self.assert_(req is flask.request._get_current_object()) -class InstanceTestCase(unittest.TestCase): +class InstanceTestCase(FlaskTestCase): def test_explicit_instance_paths(self): here = os.path.abspath(os.path.dirname(__file__)) @@ -1163,7 +1167,7 @@ class InstanceTestCase(unittest.TestCase): sys.modules['myapp'] = None -class JSONTestCase(unittest.TestCase): +class JSONTestCase(FlaskTestCase): def test_json_bad_requests(self): app = flask.Flask(__name__) @@ -1239,7 +1243,7 @@ class JSONTestCase(unittest.TestCase): test_modified_url_encoding = None -class TemplatingTestCase(unittest.TestCase): +class TemplatingTestCase(FlaskTestCase): def test_context_processing(self): app = flask.Flask(__name__) @@ -1361,7 +1365,7 @@ class TemplatingTestCase(unittest.TestCase): assert rv.data == 'Hello Custom World!' -class ModuleTestCase(unittest.TestCase): +class ModuleTestCase(FlaskTestCase): @emits_module_deprecation_warning def test_basic_module(self): @@ -1587,7 +1591,7 @@ class ModuleTestCase(unittest.TestCase): assert c.get('/foo/bar').data == 'bar' -class BlueprintTestCase(unittest.TestCase): +class BlueprintTestCase(FlaskTestCase): def test_blueprint_specific_error_handling(self): frontend = flask.Blueprint('frontend', __name__) @@ -1754,7 +1758,7 @@ class BlueprintTestCase(unittest.TestCase): self.assertEqual(c.get('/page/2').data, '2') -class SendfileTestCase(unittest.TestCase): +class SendfileTestCase(FlaskTestCase): def test_send_file_regular(self): app = flask.Flask(__name__) @@ -1854,7 +1858,7 @@ class SendfileTestCase(unittest.TestCase): assert options['filename'] == 'index.txt' -class LoggingTestCase(unittest.TestCase): +class LoggingTestCase(FlaskTestCase): def test_logger_cache(self): app = flask.Flask(__name__) @@ -1938,7 +1942,7 @@ class LoggingTestCase(unittest.TestCase): assert rv.data == 'Hello Server Error' -class ConfigTestCase(unittest.TestCase): +class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): assert app.secret_key == 'devkey' @@ -1998,7 +2002,7 @@ class ConfigTestCase(unittest.TestCase): assert not app.config.from_pyfile('missing.cfg', silent=True) -class SubdomainTestCase(unittest.TestCase): +class SubdomainTestCase(FlaskTestCase): def test_basic_support(self): app = flask.Flask(__name__) @@ -2061,7 +2065,7 @@ class SubdomainTestCase(unittest.TestCase): assert rv.data == 'Outside' -class TestSignals(unittest.TestCase): +class TestSignals(FlaskTestCase): def test_template_rendered(self): app = flask.Flask(__name__) @@ -2144,7 +2148,7 @@ class TestSignals(unittest.TestCase): flask.got_request_exception.disconnect(record, app) -class ViewTestCase(unittest.TestCase): +class ViewTestCase(FlaskTestCase): def common_test(self, app): c = app.test_client() @@ -2219,7 +2223,7 @@ class ViewTestCase(unittest.TestCase): self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) -class DeprecationsTestCase(unittest.TestCase): +class DeprecationsTestCase(FlaskTestCase): def test_init_jinja_globals(self): class MyFlask(flask.Flask): From f051939d8ba1c5ec585fd4db1f83237a66020c0a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:24:10 +0100 Subject: [PATCH 04/43] Test that we're not leaking a request context in the testsuite, fixed a leak --- tests/flask_tests.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 743339f0..62814882 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -96,7 +96,15 @@ def emits_module_deprecation_warning(f): class FlaskTestCase(unittest.TestCase): - pass + + def ensure_clean_request_context(self): + # make sure we're not leaking a request context since we are + # testing flask internally in debug mode in a few cases + self.assertEqual(flask._request_ctx_stack.top, None) + + def tearDown(self): + unittest.TestCase.tearDown(self) + self.ensure_clean_request_context() class ContextTestCase(FlaskTestCase): @@ -1881,23 +1889,23 @@ class LoggingTestCase(FlaskTestCase): @app.route('/exc') def exc(): 1/0 - c = app.test_client() - with catch_stderr() as err: - c.get('/') - out = err.getvalue() - assert 'WARNING in flask_tests [' in out - assert 'flask_tests.py' in out - assert 'the standard library is dead' in out - assert 'this is a debug statement' in out - - with catch_stderr() as err: - try: - c.get('/exc') - except ZeroDivisionError: - pass - else: - assert False, 'debug log ate the exception' + with app.test_client() as c: + with catch_stderr() as err: + c.get('/') + out = err.getvalue() + assert 'WARNING in flask_tests [' in out + assert 'flask_tests.py' in out + assert 'the standard library is dead' in out + assert 'this is a debug statement' in out + + with catch_stderr() as err: + try: + c.get('/exc') + except ZeroDivisionError: + pass + else: + assert False, 'debug log ate the exception' def test_exception_logging(self): out = StringIO() From 311ac0f533bf5a308200a51b861a9728b9223276 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:33:03 +0100 Subject: [PATCH 05/43] Ensure that nobody can nest test client invocations --- flask/testing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/testing.py b/flask/testing.py index c1844c00..dfdaff57 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -108,6 +108,8 @@ class FlaskClient(Client): self.context_preserved = _request_ctx_stack.top is not old def __enter__(self): + if self.preserve_context: + raise RuntimeError('Cannot nest client invocations') self.preserve_context = True return self From 1ea3d4ea5300d9f456622da0cc55394250fa9b4c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 16:13:43 +0100 Subject: [PATCH 06/43] Updated documentation regarding the session transactions --- docs/testing.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/testing.rst b/docs/testing.rst index ed5765ea..ed44e5a2 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -273,3 +273,35 @@ is no longer available (because you are trying to use it outside of the actual r However, keep in mind that any :meth:`~flask.Flask.after_request` functions are already called at this point so your database connection and everything involved is probably already closed down. + + +Accessing and Modifying Sessions +-------------------------------- + +.. versionadded:: 0.8 + +Sometimes it can be very helpful to access or modify the sessions from the +test client. Generally there are two ways for this. Ify ou just want to +ensure that a session has certain keys set to certain values you can just +keep the context around and access :data:`flask.session`:: + + with app.test_client() as c: + rv = c.get('/') + assert flask.session['foo'] == 42 + +This however does not make it possible to also modify the session or to +access the session before a request was fired. Starting with Flask 0.8 we +provide a so called “session transaction” which simulates the appropriate +calls to open a session in the context of the test client and to modify +it. At the end of the transaction the session is stored. This works +independently of the session backend used:: + + with app.test_client() as c: + with c.session_transaction() as sess: + sess['a_key'] = 'a value' + + # once this is reached the session was stored + +Note that in this case you have to use the ``sess`` object instead of the +:data:`flask.session` proxy. The object however itself will provide the +same interface. From e853a0f73959ad12cdabc2d74324cdc9329ae794 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:47:50 +0100 Subject: [PATCH 07/43] The test client and test_request_context are now both using the same logic internally for creating the environ. Also they use APPLICATION_ROOT now. --- CHANGES | 3 +++ docs/api.rst | 2 +- flask/app.py | 21 +++++++-------------- flask/testing.py | 26 ++++++++++++-------------- tests/flask_tests.py | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/CHANGES b/CHANGES index a451b978..6727e98f 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,9 @@ Relase date to be decided, codename to be chosen. - Added the ``APPLICATION_ROOT`` configuration variable. - Implemented :meth:`~flask.testing.TestClient.session_transaction` to easily modify sessions from the test environment. +- Refactored test client internally. The ``APPLICATION_ROOT`` configuration + variable as well as ``SERVER_NAME`` are now properly used by the test client + as defaults. Version 0.7.3 ------------- diff --git a/docs/api.rst b/docs/api.rst index f4fab86f..1dc25eb1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -223,7 +223,7 @@ Test Client .. currentmodule:: flask.testing -.. autoclass:: TestClient +.. autoclass:: FlaskClient :members: diff --git a/flask/app.py b/flask/app.py index 20cbca52..b4ae64a5 100644 --- a/flask/app.py +++ b/flask/app.py @@ -706,7 +706,7 @@ class Flask(_PackageBoundObject): rv = c.get('/?vodka=42') assert request.args['vodka'] == '42' - See :class:`~flask.testing.TestClient` for more information. + See :class:`~flask.testing.FlaskClient` for more information. .. versionchanged:: 0.4 added support for `with` block usage for the client. @@ -1481,19 +1481,12 @@ class Flask(_PackageBoundObject): :func:`werkzeug.test.EnvironBuilder` for more information, this function accepts the same arguments). """ - from werkzeug.test import create_environ - environ_overrides = kwargs.setdefault('environ_overrides', {}) - if self.config.get('SERVER_NAME'): - server_name = self.config.get('SERVER_NAME') - if ':' not in server_name: - http_host, http_port = server_name, '80' - else: - http_host, http_port = server_name.split(':', 1) - - environ_overrides.setdefault('SERVER_NAME', server_name) - environ_overrides.setdefault('HTTP_HOST', server_name) - environ_overrides.setdefault('SERVER_PORT', http_port) - return self.request_context(create_environ(*args, **kwargs)) + from flask.testing import make_test_environ_builder + builder = make_test_environ_builder(self, *args, **kwargs) + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() def wsgi_app(self, environ, start_response): """The actual WSGI application. This is not implemented in diff --git a/flask/testing.py b/flask/testing.py index dfdaff57..c4d18ca2 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -15,6 +15,17 @@ from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack +def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): + """Creates a new test builder with some application defaults thrown in.""" + http_host = app.config.get('SERVER_NAME') + app_root = app.config.get('APPLICATION_ROOT') + if base_url is None: + base_url = 'http://%s/' % (http_host or 'localhost') + if app_root: + base_url += app_root.lstrip('/') + return EnvironBuilder(path, base_url, *args, **kwargs) + + class FlaskClient(Client): """Works like a regular Werkzeug test client but has some knowledge about how Flask works to defer the cleanup of the request context stack to the @@ -83,21 +94,8 @@ class FlaskClient(Client): as_tuple = kwargs.pop('as_tuple', False) buffered = kwargs.pop('buffered', False) follow_redirects = kwargs.pop('follow_redirects', False) + builder = make_test_environ_builder(self.application, *args, **kwargs) - builder = EnvironBuilder(*args, **kwargs) - - if self.application.config.get('SERVER_NAME'): - server_name = self.application.config.get('SERVER_NAME') - if ':' not in server_name: - http_host, http_port = server_name, None - else: - http_host, http_port = server_name.split(':', 1) - if builder.base_url == 'http://localhost/': - # Default Generated Base URL - if http_port != None: - builder.host = http_host + ':' + http_port - else: - builder.host = http_host old = _request_ctx_stack.top try: return Client.open(self, builder, diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 62814882..aa8f2cf3 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1042,6 +1042,21 @@ class BasicFunctionalityTestCase(FlaskTestCase): class TestToolsTestCase(FlaskTestCase): + def test_environ_defaults_from_config(self): + app = flask.Flask(__name__) + app.testing = True + app.config['SERVER_NAME'] = 'example.com:1234' + app.config['APPLICATION_ROOT'] = '/foo' + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assertEqual(ctx.request.url, 'http://example.com:1234/foo/') + with app.test_client() as c: + rv = c.get('/') + self.assertEqual(rv.data, 'http://example.com:1234/foo/') + def test_session_transactions(self): app = flask.Flask(__name__) app.testing = True From 8dbd71ef8e16ce64cfcf7c526d584ea2771a28d1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:48:38 +0100 Subject: [PATCH 08/43] Added a testcase where SERVER_NAME and APPLICATION_ROOT are not set --- tests/flask_tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index aa8f2cf3..040ddfaa 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1057,6 +1057,19 @@ class TestToolsTestCase(FlaskTestCase): rv = c.get('/') self.assertEqual(rv.data, 'http://example.com:1234/foo/') + def test_environ_defaults(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assertEqual(ctx.request.url, 'http://localhost/') + with app.test_client() as c: + rv = c.get('/') + self.assertEqual(rv.data, 'http://localhost/') + def test_session_transactions(self): app = flask.Flask(__name__) app.testing = True From 485a6c332b1cd10495f6f3aafe2690e07f01b422 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:49:53 +0100 Subject: [PATCH 09/43] Moved testcase for test client context binding to the TestToolsTestCase --- tests/flask_tests.py | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 040ddfaa..1d9daf83 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -153,36 +153,6 @@ class ContextTestCase(FlaskTestCase): else: assert 0, 'expected runtime error' - def test_test_client_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - flask.g.value = 42 - return 'Hello World!' - - @app.route('/other') - def other(): - 1/0 - - with app.test_client() as c: - resp = c.get('/') - assert flask.g.value == 42 - assert resp.data == 'Hello World!' - assert resp.status_code == 200 - - resp = c.get('/other') - assert not hasattr(flask.g, 'value') - assert 'Internal Server Error' in resp.data - assert resp.status_code == 500 - flask.g.value = 23 - - try: - flask.g.value - except (AttributeError, RuntimeError): - pass - else: - raise AssertionError('some kind of exception expected') - class BasicFunctionalityTestCase(FlaskTestCase): @@ -1111,6 +1081,36 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction(): self.assert_(req is flask.request._get_current_object()) + def test_test_client_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + flask.g.value = 42 + return 'Hello World!' + + @app.route('/other') + def other(): + 1/0 + + with app.test_client() as c: + resp = c.get('/') + assert flask.g.value == 42 + assert resp.data == 'Hello World!' + assert resp.status_code == 200 + + resp = c.get('/other') + assert not hasattr(flask.g, 'value') + assert 'Internal Server Error' in resp.data + assert resp.status_code == 500 + flask.g.value = 23 + + try: + flask.g.value + except (AttributeError, RuntimeError): + pass + else: + raise AssertionError('some kind of exception expected') + class InstanceTestCase(FlaskTestCase): From d3ca55177a9f39de526143c59edc049d28f4424c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:56:43 +0100 Subject: [PATCH 10/43] Updated the docs and examples to non-failing teardown handlers --- docs/patterns/sqlite3.rst | 10 +++++++++- docs/reqcontext.rst | 10 +++++++++- docs/upgrading.rst | 3 ++- examples/flaskr/flaskr.py | 3 ++- examples/minitwit/minitwit.py | 3 ++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index bc471f66..0d02e465 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -24,7 +24,15 @@ So here is a simple example of how you can use SQLite 3 with Flask:: @app.teardown_request def teardown_request(exception): - g.db.close() + if hasattr(g, 'db'): + g.db.close() + +.. note:: + + Please keep in mind that the teardown request functions are always + executed, even if a before-request handler failed or was never + executed. Because of this we have to make sure here that the database + is there before we close it. Connect on Demand ----------------- diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 3b49e1d5..0249b88e 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -131,7 +131,9 @@ understand what is actually happening. The new behavior is quite simple: 4. At the end of the request the :meth:`~flask.Flask.teardown_request` functions are executed. This always happens, even in case of an - unhandled exception down the road. + unhandled exception down the road or if a before-request handler was + not executed yet or at all (for example in test environments sometimes + you might want to not execute before-request callbacks). Now what happens on errors? In production mode if an exception is not caught, the 500 internal server handler is called. In development mode @@ -183,6 +185,12 @@ It's easy to see the behavior from the command line: this runs after request >>> +Keep in mind that teardown callbacks are always executed, even if +before-request callbacks were not executed yet but an exception happened. +Certain parts of the test system might also temporarily create a request +context without calling the before-request handlers. Make sure to write +your teardown-request handlers in a way that they will never fail. + .. _notes-on-proxies: Notes On Proxies diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 13b5be71..b318292c 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -142,7 +142,8 @@ You are now encouraged to use this instead:: @app.teardown_request def after_request(exception): - g.db.close() + if hasattr(g, 'db'): + g.db.close() On the upside this change greatly improves the internal code flow and makes it easier to customize the dispatching and error handling. This diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 361c1aee..6f9b06fc 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -50,7 +50,8 @@ def before_request(): @app.teardown_request def teardown_request(exception): """Closes the database again at the end of the request.""" - g.db.close() + if hasattr(g, 'db'): + g.db.close() @app.route('/') diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index f7c700d3..fee023fd 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -85,7 +85,8 @@ def before_request(): @app.teardown_request def teardown_request(exception): """Closes the database again at the end of the request.""" - g.db.close() + if hasattr(g, 'db'): + g.db.close() @app.route('/') From a43f73c75c169de86dde372d6ecc42019773271f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuh=C3=A4user?= Date: Fri, 26 Aug 2011 10:13:54 +0300 Subject: [PATCH 11/43] s/Ify ou/If you/ typo --- docs/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index ed44e5a2..1e00fe80 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -281,7 +281,7 @@ Accessing and Modifying Sessions .. versionadded:: 0.8 Sometimes it can be very helpful to access or modify the sessions from the -test client. Generally there are two ways for this. Ify ou just want to +test client. Generally there are two ways for this. If you just want to ensure that a session has certain keys set to certain values you can just keep the context around and access :data:`flask.session`:: From 314f9201ab133ca7b78f219937371223e14ee32a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 21:16:06 +0100 Subject: [PATCH 12/43] Updated instance path documentation to explain the $PREFIX lookup --- docs/config.rst | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 65e5064a..df31aba0 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -299,25 +299,37 @@ With Flask 0.8 a new attribute was introduced: version control and be deployment specific. It's the perfect place to drop things that either change at runtime or configuration files. -To make it easier to put this folder into an ignore list for your version -control system it's called ``instance`` and placed directly next to your -package or module by default. This path can be overridden by specifying -the `instance_path` parameter to your application:: +You can either explicitly provide the path of the instance folder when +creating the Flask application or you can let Flask autodetect the +instance folder. For explicit configuration use the `instance_path` +parameter:: app = Flask(__name__, instance_path='/path/to/instance/folder') -Default locations:: +Please keep in mind that this path *must* be absolute when provided. + +If the `instance_path` parameter is not provided the following default +locations are used: + +- Uninstalled module:: - Module situation: /myapp.py /instance - Package situation: +- Uninstalled package:: + /myapp /__init__.py /instance -Please keep in mind that this path *must* be absolute when provided. +- Installed module or package:: + + $PREFIX/lib/python2.X/site-packages/myapp + $PREFIX/var/myapp-instance + + ``$PREFIX`` is the prefix of your Python installation. This can be + ``/usr`` or the path to your virtualenv. You can print the value of + ``sys.prefix`` to see what the prefix is set to. Since the config object provided loading of configuration files from relative filenames we made it possible to change the loading via filenames From 8340d3c9f5cb38f23dd16d7d28aa643a121c4236 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 21:49:50 +0100 Subject: [PATCH 13/43] Updated docstring on make_response --- flask/helpers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index a260b03f..d8f7ac63 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -145,6 +145,13 @@ def make_response(*args): response = make_response(render_template('not_found.html'), 404) + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + Internally this function does the following things: - if no arguments are passed, it creates a new response argument From ef0f626f0a916fdced42e05aa2f58dede6c660fd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 22:09:48 +0100 Subject: [PATCH 14/43] Added flask.views.View.decorators to automatically decorate class based views. --- CHANGES | 2 ++ flask/views.py | 35 +++++++++++++++++++++++++++++++++++ tests/flask_tests.py | 21 +++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/CHANGES b/CHANGES index 6727e98f..fa8c00bf 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,8 @@ Relase date to be decided, codename to be chosen. - Refactored test client internally. The ``APPLICATION_ROOT`` configuration variable as well as ``SERVER_NAME`` are now properly used by the test client as defaults. +- Added :attr:`flask.views.View.decorators` to support simpler decorating of + pluggable (class based) views. Version 0.7.3 ------------- diff --git a/flask/views.py b/flask/views.py index 9a185570..2fe34622 100644 --- a/flask/views.py +++ b/flask/views.py @@ -30,10 +30,38 @@ class View(object): return 'Hello %s!' % name app.add_url_rule('/hello/', view_func=MyView.as_view('myview')) + + When you want to decorate a pluggable view you will have to either do that + when the view function is created (by wrapping the return value of + :meth:`as_view`) or you can use the :attr:`decorators` attribute:: + + class SecretView(View): + methods = ['GET'] + decorators = [superuser_required] + + def dispatch_request(self): + ... + + The decorators stored in the decorators list are applied one after another + when the view function is created. Note that you can *not* use the class + based decorators since those would decorate the view class and not the + generated view function! """ + #: A for which methods this pluggable view can handle. methods = None + #: The canonical way to decorate class based views is to decorate the + #: return value of as_view(). However since this moves parts of the + #: logic from the class declaration to the place where it's hooked + #: into the routing system. + #: + #: You can place one or more decorators in this list and whenever the + #: view function is created the result is automatically decorated. + #: + #: .. versionadded:: 0.8 + decorators = [] + def dispatch_request(self): """Subclasses have to override this method to implement the actual view functionc ode. This method is called with all @@ -54,6 +82,13 @@ class View(object): def view(*args, **kwargs): self = view.view_class(*class_args, **class_kwargs) return self.dispatch_request(*args, **kwargs) + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + view = decorator(view) + # we attach the view class to the view function for two reasons: # first of all it allows us to easily figure out what class based # view this thing came from, secondly it's also used for instanciating diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 1d9daf83..dcc3e1a0 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -2258,6 +2258,27 @@ class ViewTestCase(FlaskTestCase): meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) + def test_view_decorators(self): + app = flask.Flask(__name__) + + def add_x_parachute(f): + def new_function(*args, **kwargs): + resp = flask.make_response(f(*args, **kwargs)) + resp.headers['X-Parachute'] = 'awesome' + return resp + return new_function + + class Index(flask.views.View): + decorators = [add_x_parachute] + def dispatch_request(self): + return 'Awesome' + + app.add_url_rule('/', view_func=Index.as_view('index')) + c = app.test_client() + rv = c.get('/') + self.assertEqual(rv.headers['X-Parachute'], 'awesome') + self.assertEqual(rv.data, 'Awesome') + class DeprecationsTestCase(FlaskTestCase): From 85ed1bf05834f484ce7dfdc37e11b89eec8cee65 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 08:32:36 +0100 Subject: [PATCH 15/43] Mentioned View.decorators in the views docs --- docs/views.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/views.rst b/docs/views.rst index fc22d1d7..10ddb57d 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -135,3 +135,24 @@ easily do that. Each HTTP method maps to a function with the same name That way you also don't have to provide the :attr:`~flask.views.View.methods` attribute. It's automatically set based on the methods defined in the class. + +Decorating Views +---------------- + +Since the view class itself is not the view function that is added to the +routing system it does not make much sense to decorate the class itself. +Instead you either have to decorate the return value of +:meth:`~flask.views.View.as_view` by hand:: + + view = rate_limited(UserAPI.as_view('users')) + app.add_url_rule('/users/', view_func=view) + +Starting with Flask 0.8 there is also an alternative way where you can +specify a list of decorators to apply in the class declaration:: + + class UserAPI(MethodView): + decorators = [rate_limited] + +Due to the implicit self from the caller's perspective you cannot use +regular view decorators on the individual methods of the view however, +keep this in mind. From 4cb6eea8f1e1edc707874f72cbca43a0f698ec7d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:21:26 +0100 Subject: [PATCH 16/43] Split up testsuite and moved it to flask.testsuite. This fixes #246 --- flask/testsuite/__init__.py | 121 + flask/testsuite/basic.py | 992 +++++++ flask/testsuite/blueprints.py | 423 +++ flask/testsuite/config.py | 177 ++ flask/testsuite/deprecations.py | 38 + flask/testsuite/examples.py | 38 + flask/testsuite/helpers.py | 295 +++ flask/testsuite/signals.py | 103 + {tests => flask/testsuite}/static/index.html | 0 .../testsuite}/templates/_macro.html | 0 .../templates/context_template.html | 0 .../templates/escaping_template.html | 0 {tests => flask/testsuite}/templates/mail.txt | 0 .../testsuite}/templates/nested/nested.txt | 0 .../testsuite}/templates/simple_template.html | 0 .../testsuite}/templates/template_filter.html | 0 flask/testsuite/templating.py | 141 + .../test_apps}/blueprintapp/__init__.py | 0 .../test_apps}/blueprintapp/apps/__init__.py | 0 .../blueprintapp/apps/admin/__init__.py | 0 .../apps/admin/static/css/test.css | 0 .../blueprintapp/apps/admin/static/test.txt | 0 .../apps/admin/templates/admin/index.html | 0 .../blueprintapp/apps/frontend/__init__.py | 0 .../frontend/templates/frontend/index.html | 0 .../testsuite/test_apps/config_module_app.py | 4 + .../test_apps/config_package_app/__init__.py | 4 + .../test_apps}/moduleapp/__init__.py | 0 .../test_apps}/moduleapp/apps/__init__.py | 0 .../moduleapp/apps/admin/__init__.py | 0 .../moduleapp/apps/admin/static/css/test.css | 0 .../moduleapp/apps/admin/static/test.txt | 0 .../moduleapp/apps/admin/templates/index.html | 0 .../moduleapp/apps/frontend/__init__.py | 0 .../apps/frontend/templates/index.html | 0 .../subdomaintestmodule/__init__.py | 0 .../subdomaintestmodule/static/hello.txt | 0 flask/testsuite/testing.py | 121 + flask/testsuite/views.py | 117 + {tests => scripts}/flaskext_test.py | 0 setup.py | 8 +- tests/flask_tests.py | 2329 ----------------- 42 files changed, 2575 insertions(+), 2336 deletions(-) create mode 100644 flask/testsuite/__init__.py create mode 100644 flask/testsuite/basic.py create mode 100644 flask/testsuite/blueprints.py create mode 100644 flask/testsuite/config.py create mode 100644 flask/testsuite/deprecations.py create mode 100644 flask/testsuite/examples.py create mode 100644 flask/testsuite/helpers.py create mode 100644 flask/testsuite/signals.py rename {tests => flask/testsuite}/static/index.html (100%) rename {tests => flask/testsuite}/templates/_macro.html (100%) rename {tests => flask/testsuite}/templates/context_template.html (100%) rename {tests => flask/testsuite}/templates/escaping_template.html (100%) rename {tests => flask/testsuite}/templates/mail.txt (100%) rename {tests => flask/testsuite}/templates/nested/nested.txt (100%) rename {tests => flask/testsuite}/templates/simple_template.html (100%) rename {tests => flask/testsuite}/templates/template_filter.html (100%) create mode 100644 flask/testsuite/templating.py rename {tests => flask/testsuite/test_apps}/blueprintapp/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/static/css/test.css (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/static/test.txt (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/templates/admin/index.html (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/frontend/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/frontend/templates/frontend/index.html (100%) create mode 100644 flask/testsuite/test_apps/config_module_app.py create mode 100644 flask/testsuite/test_apps/config_package_app/__init__.py rename {tests => flask/testsuite/test_apps}/moduleapp/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/static/css/test.css (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/static/test.txt (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/templates/index.html (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/frontend/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/frontend/templates/index.html (100%) rename {tests => flask/testsuite/test_apps}/subdomaintestmodule/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/subdomaintestmodule/static/hello.txt (100%) create mode 100644 flask/testsuite/testing.py create mode 100644 flask/testsuite/views.py rename {tests => scripts}/flaskext_test.py (100%) delete mode 100644 tests/flask_tests.py diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py new file mode 100644 index 00000000..8df7a7fd --- /dev/null +++ b/flask/testsuite/__init__.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite + ~~~~~~~~~~~~~~~ + + Tests Flask itself. The majority of Flask is already tested + as part of Werkzeug. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +import flask +import warnings +import unittest +from StringIO import StringIO +from functools import update_wrapper +from contextlib import contextmanager +from werkzeug.utils import import_string, find_modules + + +def add_to_path(path): + def _samefile(x, y): + try: + return os.path.samefile(x, y) + except (IOError, OSError): + return False + for entry in sys.path: + try: + if os.path.samefile(path, entry): + return + except (OSError, IOError): + pass + sys.path.append(path) + + +def setup_paths(): + add_to_path(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps'))) + + +def iter_suites(): + for module in find_modules(__name__): + mod = import_string(module) + if hasattr(mod, 'suite'): + yield mod.suite() + + +@contextmanager +def catch_warnings(): + """Catch warnings in a with block in a list""" + # make sure deprecation warnings are active in tests + warnings.simplefilter('default', category=DeprecationWarning) + + filters = warnings.filters + warnings.filters = filters[:] + old_showwarning = warnings.showwarning + log = [] + def showwarning(message, category, filename, lineno, file=None, line=None): + log.append(locals()) + try: + warnings.showwarning = showwarning + yield log + finally: + warnings.filters = filters + warnings.showwarning = old_showwarning + + +@contextmanager +def catch_stderr(): + """Catch stderr in a StringIO""" + old_stderr = sys.stderr + sys.stderr = rv = StringIO() + try: + yield rv + finally: + sys.stderr = old_stderr + + +def emits_module_deprecation_warning(f): + def new_f(self, *args, **kwargs): + with catch_warnings() as log: + f(self, *args, **kwargs) + self.assert_(log, 'expected deprecation warning') + for entry in log: + self.assert_('Modules are deprecated' in str(entry['message'])) + return update_wrapper(new_f, f) + + +class FlaskTestCase(unittest.TestCase): + + def ensure_clean_request_context(self): + # make sure we're not leaking a request context since we are + # testing flask internally in debug mode in a few cases + self.assert_equal(flask._request_ctx_stack.top, None) + + def setup(self): + pass + + def teardown(self): + pass + + def setUp(self): + self.setup() + + def tearDown(self): + unittest.TestCase.tearDown(self) + self.ensure_clean_request_context() + self.teardown() + + def assert_equal(self, x, y): + return self.assertEqual(x, y) + + +def suite(): + setup_paths() + suite = unittest.TestSuite() + for other_suite in iter_suites(): + suite.addTest(other_suite) + return suite diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py new file mode 100644 index 00000000..c55881e5 --- /dev/null +++ b/flask/testsuite/basic.py @@ -0,0 +1,992 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.basic + ~~~~~~~~~~~~~~~~~~~~~ + + The basic functionality. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import re +import flask +import unittest +from datetime import datetime +from threading import Thread +from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning +from werkzeug.exceptions import BadRequest, NotFound +from werkzeug.http import parse_date + + +class BasicFunctionalityTestCase(FlaskTestCase): + + def test_options_work(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + assert rv.data == '' + + def test_options_on_multiple_rules(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + @app.route('/', methods=['PUT']) + def index_put(): + return 'Aha!' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + def test_options_handling_disabled(self): + app = flask.Flask(__name__) + def index(): + return 'Hello World!' + index.provide_automatic_options = False + app.route('/')(index) + rv = app.test_client().open('/', method='OPTIONS') + assert rv.status_code == 405 + + app = flask.Flask(__name__) + def index2(): + return 'Hello World!' + index2.provide_automatic_options = True + app.route('/', methods=['OPTIONS'])(index2) + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['OPTIONS'] + + def test_request_dispatching(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.request.method + @app.route('/more', methods=['GET', 'POST']) + def more(): + return flask.request.method + + c = app.test_client() + assert c.get('/').data == 'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == 'POST' + assert c.get('/more').data == 'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + def test_url_mapping(self): + app = flask.Flask(__name__) + def index(): + return flask.request.method + def more(): + return flask.request.method + + app.add_url_rule('/', 'index', index) + app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) + + c = app.test_client() + assert c.get('/').data == 'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == 'POST' + assert c.get('/more').data == 'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + def test_werkzeug_routing(self): + from werkzeug.routing import Submount, Rule + app = flask.Flask(__name__) + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + def bar(): + return 'bar' + def index(): + return 'index' + app.view_functions['bar'] = bar + app.view_functions['index'] = index + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + def test_endpoint_decorator(self): + from werkzeug.routing import Submount, Rule + app = flask.Flask(__name__) + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + + @app.endpoint('bar') + def bar(): + return 'bar' + + @app.endpoint('index') + def index(): + return 'index' + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + def test_session(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + @app.route('/set', methods=['POST']) + def set(): + flask.session['value'] = flask.request.form['value'] + return 'value set' + @app.route('/get') + def get(): + return flask.session['value'] + + c = app.test_client() + assert c.post('/set', data={'value': '42'}).data == 'value set' + assert c.get('/get').data == '42' + + def test_session_using_server_name(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='example.com' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + def test_session_using_server_name_and_port(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='example.com:8080' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + def test_session_using_application_root(self): + class PrefixPathMiddleware(object): + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + def __call__(self, environ, start_response): + environ['SCRIPT_NAME'] = self.prefix + return self.app(environ, start_response) + + app = flask.Flask(__name__) + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') + app.config.update( + SECRET_KEY='foo', + APPLICATION_ROOT='/bar' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/') + assert 'path=/bar' in rv.headers['set-cookie'].lower() + + def test_missing_session(self): + app = flask.Flask(__name__) + def expect_exception(f, *args, **kwargs): + try: + f(*args, **kwargs) + except RuntimeError, e: + assert e.args and 'session is unavailable' in e.args[0] + else: + assert False, 'expected exception' + with app.test_request_context(): + assert flask.session.get('missing_key') is None + expect_exception(flask.session.__setitem__, 'foo', 42) + expect_exception(flask.session.pop, 'foo') + + def test_session_expiration(self): + permanent = True + app = flask.Flask(__name__) + app.secret_key = 'testkey' + @app.route('/') + def index(): + flask.session['test'] = 42 + flask.session.permanent = permanent + return '' + + @app.route('/test') + def test(): + return unicode(flask.session.permanent) + + client = app.test_client() + rv = client.get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) + expires = parse_date(match.group()) + expected = datetime.utcnow() + app.permanent_session_lifetime + assert expires.year == expected.year + assert expires.month == expected.month + assert expires.day == expected.day + + rv = client.get('/test') + assert rv.data == 'True' + + permanent = False + rv = app.test_client().get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) + assert match is None + + def test_flashes(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + with app.test_request_context(): + assert not flask.session.modified + flask.flash('Zap') + flask.session.modified = False + flask.flash('Zip') + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + + def test_extended_flashing(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + @app.route('/') + def index(): + flask.flash(u'Hello World') + flask.flash(u'Hello World', 'error') + flask.flash(flask.Markup(u'Testing'), 'warning') + return '' + + @app.route('/test') + def test(): + messages = flask.get_flashed_messages(with_categories=True) + assert len(messages) == 3 + assert messages[0] == ('message', u'Hello World') + assert messages[1] == ('error', u'Hello World') + assert messages[2] == ('warning', flask.Markup(u'Testing')) + return '' + messages = flask.get_flashed_messages() + assert len(messages) == 3 + assert messages[0] == u'Hello World' + assert messages[1] == u'Hello World' + assert messages[2] == flask.Markup(u'Testing') + + c = app.test_client() + c.get('/') + c.get('/test') + + def test_request_processing(self): + app = flask.Flask(__name__) + evts = [] + @app.before_request + def before_request(): + evts.append('before') + @app.after_request + def after_request(response): + response.data += '|after' + evts.append('after') + return response + @app.route('/') + def index(): + assert 'before' in evts + assert 'after' not in evts + return 'request' + assert 'after' not in evts + rv = app.test_client().get('/').data + assert 'after' in evts + assert rv == 'request|after' + + def test_teardown_request_handler(self): + called = [] + app = flask.Flask(__name__) + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + @app.route('/') + def root(): + return "Response" + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert 'Response' in rv.data + assert len(called) == 1 + + def test_teardown_request_handler_debug_mode(self): + called = [] + app = flask.Flask(__name__) + app.testing = True + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + @app.route('/') + def root(): + return "Response" + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert 'Response' in rv.data + assert len(called) == 1 + + def test_teardown_request_handler_error(self): + called = [] + app = flask.Flask(__name__) + @app.teardown_request + def teardown_request1(exc): + assert type(exc) == ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError + except: + pass + @app.teardown_request + def teardown_request2(exc): + assert type(exc) == ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError + except: + pass + @app.route('/') + def fails(): + 1/0 + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + assert len(called) == 2 + + def test_before_after_request_order(self): + called = [] + app = flask.Flask(__name__) + @app.before_request + def before1(): + called.append(1) + @app.before_request + def before2(): + called.append(2) + @app.after_request + def after1(response): + called.append(4) + return response + @app.after_request + def after2(response): + called.append(3) + return response + @app.teardown_request + def finish1(exc): + called.append(6) + @app.teardown_request + def finish2(exc): + called.append(5) + @app.route('/') + def index(): + return '42' + rv = app.test_client().get('/') + assert rv.data == '42' + assert called == [1, 2, 3, 4, 5, 6] + + def test_error_handling(self): + app = flask.Flask(__name__) + @app.errorhandler(404) + def not_found(e): + return 'not found', 404 + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + @app.route('/') + def index(): + flask.abort(404) + @app.route('/error') + def error(): + 1 // 0 + c = app.test_client() + rv = c.get('/') + assert rv.status_code == 404 + assert rv.data == 'not found' + rv = c.get('/error') + assert rv.status_code == 500 + assert 'internal server error' == rv.data + + def test_before_request_and_routing_errors(self): + app = flask.Flask(__name__) + @app.before_request + def attach_something(): + flask.g.something = 'value' + @app.errorhandler(404) + def return_something(error): + return flask.g.something, 404 + rv = app.test_client().get('/') + assert rv.status_code == 404 + assert rv.data == 'value' + + def test_user_error_handling(self): + class MyException(Exception): + pass + + app = flask.Flask(__name__) + @app.errorhandler(MyException) + def handle_my_exception(e): + assert isinstance(e, MyException) + return '42' + @app.route('/') + def index(): + raise MyException() + + c = app.test_client() + assert c.get('/').data == '42' + + def test_trapping_of_bad_request_key_errors(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/fail') + def fail(): + flask.request.form['missing_key'] + c = app.test_client() + assert c.get('/fail').status_code == 400 + + app.config['TRAP_BAD_REQUEST_ERRORS'] = True + c = app.test_client() + try: + c.get('/fail') + except KeyError, e: + assert isinstance(e, BadRequest) + else: + self.fail('Expected exception') + + def test_trapping_of_all_http_exceptions(self): + app = flask.Flask(__name__) + app.testing = True + app.config['TRAP_HTTP_EXCEPTIONS'] = True + @app.route('/fail') + def fail(): + flask.abort(404) + + c = app.test_client() + try: + c.get('/fail') + except NotFound, e: + pass + else: + self.fail('Expected exception') + + def test_enctype_debug_helper(self): + from flask.debughelpers import DebugFilesKeyError + app = flask.Flask(__name__) + app.debug = True + @app.route('/fail', methods=['POST']) + def index(): + return flask.request.files['foo'].filename + + # with statement is important because we leave an exception on the + # stack otherwise and we want to ensure that this is not the case + # to not negatively affect other tests. + with app.test_client() as c: + try: + c.post('/fail', data={'foo': 'index.txt'}) + except DebugFilesKeyError, e: + assert 'no file contents were transmitted' in str(e) + assert 'This was submitted: "index.txt"' in str(e) + else: + self.fail('Expected exception') + + def test_teardown_on_pop(self): + buffer = [] + app = flask.Flask(__name__) + @app.teardown_request + def end_of_request(exception): + buffer.append(exception) + + ctx = app.test_request_context() + ctx.push() + assert buffer == [] + ctx.pop() + assert buffer == [None] + + def test_response_creation(self): + app = flask.Flask(__name__) + @app.route('/unicode') + def from_unicode(): + return u'Hällo Wörld' + @app.route('/string') + def from_string(): + return u'Hällo Wörld'.encode('utf-8') + @app.route('/args') + def from_tuple(): + return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' + c = app.test_client() + assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') + assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') + rv = c.get('/args') + assert rv.data == 'Meh' + assert rv.headers['X-Foo'] == 'Testing' + assert rv.status_code == 400 + assert rv.mimetype == 'text/plain' + + def test_make_response(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.make_response() + assert rv.status_code == 200 + assert rv.data == '' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('Awesome') + assert rv.status_code == 200 + assert rv.data == 'Awesome' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('W00t', 404) + assert rv.status_code == 404 + assert rv.data == 'W00t' + assert rv.mimetype == 'text/html' + + def test_url_generation(self): + app = flask.Flask(__name__) + @app.route('/hello/', methods=['POST']) + def hello(): + pass + with app.test_request_context(): + assert flask.url_for('hello', name='test x') == '/hello/test%20x' + assert flask.url_for('hello', name='test x', _external=True) \ + == 'http://localhost/hello/test%20x' + + def test_custom_converters(self): + from werkzeug.routing import BaseConverter + class ListConverter(BaseConverter): + def to_python(self, value): + return value.split(',') + def to_url(self, value): + base_to_url = super(ListConverter, self).to_url + return ','.join(base_to_url(x) for x in value) + app = flask.Flask(__name__) + app.url_map.converters['list'] = ListConverter + @app.route('/') + def index(args): + return '|'.join(args) + c = app.test_client() + assert c.get('/1,2,3').data == '1|2|3' + + def test_static_files(self): + app = flask.Flask(__name__) + rv = app.test_client().get('/static/index.html') + assert rv.status_code == 200 + assert rv.data.strip() == '

Hello World!

' + with app.test_request_context(): + assert flask.url_for('static', filename='index.html') \ + == '/static/index.html' + + def test_none_response(self): + app = flask.Flask(__name__) + @app.route('/') + def test(): + return None + try: + app.test_client().get('/') + except ValueError, e: + assert str(e) == 'View function did not return a response' + pass + else: + assert "Expected ValueError" + + def test_request_locals(self): + self.assert_equal(repr(flask.g), '') + self.assertFalse(flask.g) + + def test_proper_test_request_context(self): + app = flask.Flask(__name__) + app.config.update( + SERVER_NAME='localhost.localdomain:5000' + ) + + @app.route('/') + def index(): + return None + + @app.route('/', subdomain='foo') + def sub(): + return None + + with app.test_request_context('/'): + assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' + + with app.test_request_context('/'): + assert flask.url_for('sub', _external=True) == 'http://foo.localhost.localdomain:5000/' + + try: + with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): + pass + except Exception, e: + assert isinstance(e, ValueError) + assert str(e) == "the server name provided " + \ + "('localhost.localdomain:5000') does not match the " + \ + "server name from the WSGI environment ('localhost')", str(e) + + try: + app.config.update(SERVER_NAME='localhost') + with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}): + pass + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost:80') + with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): + pass + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + def test_test_app_proper_environ(self): + app = flask.Flask(__name__) + app.config.update( + SERVER_NAME='localhost.localdomain:5000' + ) + @app.route('/') + def index(): + return 'Foo' + + @app.route('/', subdomain='foo') + def subdomain(): + return 'Foo SubDomain' + + try: + rv = app.test_client().get('/') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + rv = app.test_client().get('/', 'http://localhost.localdomain:5000') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + rv = app.test_client().get('/', 'https://localhost.localdomain:5000') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + rv = app.test_client().get('/', 'https://localhost.localdomain') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain:443') + rv = app.test_client().get('/', 'https://localhost.localdomain') + assert rv.data == 'Foo' + except ValueError, e: + assert str(e) == "the server name provided " + \ + "('localhost.localdomain:443') does not match the " + \ + "server name from the WSGI environment ('localhost.localdomain')", str(e) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + app.test_client().get('/', 'http://foo.localhost') + except ValueError, e: + assert str(e) == "the server name provided " + \ + "('localhost.localdomain') does not match the " + \ + "server name from the WSGI environment ('foo.localhost')", str(e) + + try: + rv = app.test_client().get('/', 'http://foo.localhost.localdomain') + assert rv.data == 'Foo SubDomain' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + def test_exception_propagation(self): + def apprunner(configkey): + app = flask.Flask(__name__) + @app.route('/') + def index(): + 1/0 + c = app.test_client() + if config_key is not None: + app.config[config_key] = True + try: + resp = c.get('/') + except Exception: + pass + else: + self.fail('expected exception') + else: + assert c.get('/').status_code == 500 + + # we have to run this test in an isolated thread because if the + # debug flag is set to true and an exception happens the context is + # not torn down. This causes other tests that run after this fail + # when they expect no exception on the stack. + for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None: + t = Thread(target=apprunner, args=(config_key,)) + t.start() + t.join() + + def test_max_content_length(self): + app = flask.Flask(__name__) + app.config['MAX_CONTENT_LENGTH'] = 64 + @app.before_request + def always_first(): + flask.request.form['myfile'] + assert False + @app.route('/accept', methods=['POST']) + def accept_file(): + flask.request.form['myfile'] + assert False + @app.errorhandler(413) + def catcher(error): + return '42' + + c = app.test_client() + rv = c.post('/accept', data={'myfile': 'foo' * 100}) + assert rv.data == '42' + + def test_url_processors(self): + app = flask.Flask(__name__) + + @app.url_defaults + def add_language_code(endpoint, values): + if flask.g.lang_code is not None and \ + app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values.setdefault('lang_code', flask.g.lang_code) + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code', None) + + @app.route('//') + def index(): + return flask.url_for('about') + + @app.route('//about') + def about(): + return flask.url_for('something_else') + + @app.route('/foo') + def something_else(): + return flask.url_for('about', lang_code='en') + + c = app.test_client() + + self.assert_equal(c.get('/de/').data, '/de/about') + self.assert_equal(c.get('/de/about').data, '/foo') + self.assert_equal(c.get('/foo').data, '/en/about') + + def test_debug_mode_complains_after_first_request(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/') + def index(): + return 'Awesome' + self.assert_(not app.got_first_request) + self.assert_equal(app.test_client().get('/').data, 'Awesome') + try: + @app.route('/foo') + def broken(): + return 'Meh' + except AssertionError, e: + self.assert_('A setup function was called' in str(e)) + else: + self.fail('Expected exception') + + app.debug = False + @app.route('/foo') + def working(): + return 'Meh' + self.assert_equal(app.test_client().get('/foo').data, 'Meh') + self.assert_(app.got_first_request) + + def test_before_first_request_functions(self): + got = [] + app = flask.Flask(__name__) + @app.before_first_request + def foo(): + got.append(42) + c = app.test_client() + c.get('/') + self.assert_equal(got, [42]) + c.get('/') + self.assert_equal(got, [42]) + self.assert_(app.got_first_request) + + def test_routing_redirect_debugging(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/foo/', methods=['GET', 'POST']) + def foo(): + return 'success' + with app.test_client() as c: + try: + c.post('/foo', data={}) + except AssertionError, e: + self.assert_('http://localhost/foo/' in str(e)) + self.assert_('Make sure to directly send your POST-request ' + 'to this URL' in str(e)) + else: + self.fail('Expected exception') + + rv = c.get('/foo', data={}, follow_redirects=True) + self.assert_equal(rv.data, 'success') + + app.debug = False + with app.test_client() as c: + rv = c.post('/foo', data={}, follow_redirects=True) + self.assert_equal(rv.data, 'success') + + +class ContextTestCase(FlaskTestCase): + + def test_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return 'Hello %s!' % flask.request.args['name'] + @app.route('/meh') + def meh(): + return flask.request.url + + with app.test_request_context('/?name=World'): + assert index() == 'Hello World!' + with app.test_request_context('/meh'): + assert meh() == 'http://localhost/meh' + assert flask._request_ctx_stack.top is None + + def test_context_test(self): + app = flask.Flask(__name__) + assert not flask.request + assert not flask.has_request_context() + ctx = app.test_request_context() + ctx.push() + try: + assert flask.request + assert flask.has_request_context() + finally: + ctx.pop() + + def test_manual_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return 'Hello %s!' % flask.request.args['name'] + + ctx = app.test_request_context('/?name=World') + ctx.push() + assert index() == 'Hello World!' + ctx.pop() + try: + index() + except RuntimeError: + pass + else: + assert 0, 'expected runtime error' + + +class SubdomainTestCase(FlaskTestCase): + + def test_basic_support(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + @app.route('/') + def normal_index(): + return 'normal index' + @app.route('/', subdomain='test') + def test_index(): + return 'test index' + + c = app.test_client() + rv = c.get('/', 'http://localhost/') + assert rv.data == 'normal index' + + rv = c.get('/', 'http://test.localhost/') + assert rv.data == 'test index' + + @emits_module_deprecation_warning + def test_module_static_path_subdomain(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'example.com' + from subdomaintestmodule import mod + app.register_module(mod) + c = app.test_client() + rv = c.get('/static/hello.txt', 'http://foo.example.com/') + assert rv.data.strip() == 'Hello Subdomain' + + def test_subdomain_matching(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + c = app.test_client() + rv = c.get('/', 'http://mitsuhiko.localhost/') + assert rv.data == 'index for mitsuhiko' + + @emits_module_deprecation_warning + def test_module_subdomain_support(self): + app = flask.Flask(__name__) + mod = flask.Module(__name__, 'test', subdomain='testing') + app.config['SERVER_NAME'] = 'localhost' + + @mod.route('/test') + def test(): + return 'Test' + + @mod.route('/outside', subdomain='xtesting') + def bar(): + return 'Outside' + + app.register_module(mod) + + c = app.test_client() + rv = c.get('/test', 'http://testing.localhost/') + assert rv.data == 'Test' + rv = c.get('/outside', 'http://xtesting.localhost/') + assert rv.data == 'Outside' + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase)) + suite.addTest(unittest.makeSuite(ContextTestCase)) + suite.addTest(unittest.makeSuite(SubdomainTestCase)) + return suite diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py new file mode 100644 index 00000000..93122ac5 --- /dev/null +++ b/flask/testsuite/blueprints.py @@ -0,0 +1,423 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.blueprints + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Blueprints (and currently modules) + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +import warnings +from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning +from werkzeug.exceptions import NotFound +from jinja2 import TemplateNotFound + + +# import moduleapp here because it uses deprecated features and we don't +# want to see the warnings +warnings.simplefilter('ignore', DeprecationWarning) +from moduleapp import app as moduleapp +warnings.simplefilter('default', DeprecationWarning) + + +class ModuleTestCase(FlaskTestCase): + + @emits_module_deprecation_warning + def test_basic_module(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin', url_prefix='/admin') + @admin.route('/') + def admin_index(): + return 'admin index' + @admin.route('/login') + def admin_login(): + return 'admin login' + @admin.route('/logout') + def admin_logout(): + return 'admin logout' + @app.route('/') + def index(): + return 'the index' + app.register_module(admin) + c = app.test_client() + assert c.get('/').data == 'the index' + assert c.get('/admin/').data == 'admin index' + assert c.get('/admin/login').data == 'admin login' + assert c.get('/admin/logout').data == 'admin logout' + + @emits_module_deprecation_warning + def test_default_endpoint_name(self): + app = flask.Flask(__name__) + mod = flask.Module(__name__, 'frontend') + def index(): + return 'Awesome' + mod.add_url_rule('/', view_func=index) + app.register_module(mod) + rv = app.test_client().get('/') + assert rv.data == 'Awesome' + with app.test_request_context(): + assert flask.url_for('frontend.index') == '/' + + @emits_module_deprecation_warning + def test_request_processing(self): + catched = [] + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin', url_prefix='/admin') + @admin.before_request + def before_admin_request(): + catched.append('before-admin') + @admin.after_request + def after_admin_request(response): + catched.append('after-admin') + return response + @admin.route('/') + def admin_index(): + return 'the admin' + @app.before_request + def before_request(): + catched.append('before-app') + @app.after_request + def after_request(response): + catched.append('after-app') + return response + @app.route('/') + def index(): + return 'the index' + app.register_module(admin) + c = app.test_client() + + assert c.get('/').data == 'the index' + assert catched == ['before-app', 'after-app'] + del catched[:] + + assert c.get('/admin/').data == 'the admin' + assert catched == ['before-app', 'before-admin', + 'after-admin', 'after-app'] + + @emits_module_deprecation_warning + def test_context_processors(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin', url_prefix='/admin') + @app.context_processor + def inject_all_regualr(): + return {'a': 1} + @admin.context_processor + def inject_admin(): + return {'b': 2} + @admin.app_context_processor + def inject_all_module(): + return {'c': 3} + @app.route('/') + def index(): + return flask.render_template_string('{{ a }}{{ b }}{{ c }}') + @admin.route('/') + def admin_index(): + return flask.render_template_string('{{ a }}{{ b }}{{ c }}') + app.register_module(admin) + c = app.test_client() + assert c.get('/').data == '13' + assert c.get('/admin/').data == '123' + + @emits_module_deprecation_warning + def test_late_binding(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin') + @admin.route('/') + def index(): + return '42' + app.register_module(admin, url_prefix='/admin') + assert app.test_client().get('/admin/').data == '42' + + @emits_module_deprecation_warning + def test_error_handling(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin') + @admin.app_errorhandler(404) + def not_found(e): + return 'not found', 404 + @admin.app_errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + @admin.route('/') + def index(): + flask.abort(404) + @admin.route('/error') + def error(): + 1 // 0 + app.register_module(admin) + c = app.test_client() + rv = c.get('/') + assert rv.status_code == 404 + assert rv.data == 'not found' + rv = c.get('/error') + assert rv.status_code == 500 + assert 'internal server error' == rv.data + + def test_templates_and_static(self): + app = moduleapp + app.testing = True + c = app.test_client() + + rv = c.get('/') + assert rv.data == 'Hello from the Frontend' + rv = c.get('/admin/') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/index2') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/static/test.txt') + assert rv.data.strip() == 'Admin File' + rv = c.get('/admin/static/css/test.css') + assert rv.data.strip() == '/* nested file */' + + with app.test_request_context(): + assert flask.url_for('admin.static', filename='test.txt') \ + == '/admin/static/test.txt' + + with app.test_request_context(): + try: + flask.render_template('missing.html') + except TemplateNotFound, e: + assert e.name == 'missing.html' + else: + assert 0, 'expected exception' + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template('nested/nested.txt') == 'I\'m nested' + + def test_safe_access(self): + app = moduleapp + + with app.test_request_context(): + f = app.view_functions['admin.static'] + + try: + f('/etc/passwd') + except NotFound: + pass + else: + assert 0, 'expected exception' + try: + f('../__init__.py') + except NotFound: + pass + else: + assert 0, 'expected exception' + + # testcase for a security issue that may exist on windows systems + import os + import ntpath + old_path = os.path + os.path = ntpath + try: + try: + f('..\\__init__.py') + except NotFound: + pass + else: + assert 0, 'expected exception' + finally: + os.path = old_path + + @emits_module_deprecation_warning + def test_endpoint_decorator(self): + from werkzeug.routing import Submount, Rule + from flask import Module + + app = flask.Flask(__name__) + app.testing = True + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + module = Module(__name__, __name__) + + @module.endpoint('bar') + def bar(): + return 'bar' + + @module.endpoint('index') + def index(): + return 'index' + + app.register_module(module) + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + +class BlueprintTestCase(FlaskTestCase): + + def test_blueprint_specific_error_handling(self): + frontend = flask.Blueprint('frontend', __name__) + backend = flask.Blueprint('backend', __name__) + sideend = flask.Blueprint('sideend', __name__) + + @frontend.errorhandler(403) + def frontend_forbidden(e): + return 'frontend says no', 403 + + @frontend.route('/frontend-no') + def frontend_no(): + flask.abort(403) + + @backend.errorhandler(403) + def backend_forbidden(e): + return 'backend says no', 403 + + @backend.route('/backend-no') + def backend_no(): + flask.abort(403) + + @sideend.route('/what-is-a-sideend') + def sideend_no(): + flask.abort(403) + + app = flask.Flask(__name__) + app.register_blueprint(frontend) + app.register_blueprint(backend) + app.register_blueprint(sideend) + + @app.errorhandler(403) + def app_forbidden(e): + return 'application itself says no', 403 + + c = app.test_client() + + assert c.get('/frontend-no').data == 'frontend says no' + assert c.get('/backend-no').data == 'backend says no' + assert c.get('/what-is-a-sideend').data == 'application itself says no' + + def test_blueprint_url_definitions(self): + bp = flask.Blueprint('test', __name__) + + @bp.route('/foo', defaults={'baz': 42}) + def foo(bar, baz): + return '%s/%d' % (bar, baz) + + @bp.route('/bar') + def bar(bar): + return unicode(bar) + + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) + app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) + + c = app.test_client() + self.assert_equal(c.get('/1/foo').data, u'23/42') + self.assert_equal(c.get('/2/foo').data, u'19/42') + self.assert_equal(c.get('/1/bar').data, u'23') + self.assert_equal(c.get('/2/bar').data, u'19') + + def test_blueprint_url_processors(self): + bp = flask.Blueprint('frontend', __name__, url_prefix='/') + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', flask.g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code') + + @bp.route('/') + def index(): + return flask.url_for('.about') + + @bp.route('/about') + def about(): + return flask.url_for('.index') + + app = flask.Flask(__name__) + app.register_blueprint(bp) + + c = app.test_client() + + self.assert_equal(c.get('/de/').data, '/de/about') + self.assert_equal(c.get('/de/about').data, '/de/') + + def test_templates_and_static(self): + from blueprintapp import app + c = app.test_client() + + rv = c.get('/') + assert rv.data == 'Hello from the Frontend' + rv = c.get('/admin/') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/index2') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/static/test.txt') + assert rv.data.strip() == 'Admin File' + rv = c.get('/admin/static/css/test.css') + assert rv.data.strip() == '/* nested file */' + + with app.test_request_context(): + assert flask.url_for('admin.static', filename='test.txt') \ + == '/admin/static/test.txt' + + with app.test_request_context(): + try: + flask.render_template('missing.html') + except TemplateNotFound, e: + assert e.name == 'missing.html' + else: + assert 0, 'expected exception' + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template('nested/nested.txt') == 'I\'m nested' + + def test_templates_list(self): + from blueprintapp import app + templates = sorted(app.jinja_env.list_templates()) + self.assert_equal(templates, ['admin/index.html', + 'frontend/index.html']) + + def test_dotted_names(self): + frontend = flask.Blueprint('myapp.frontend', __name__) + backend = flask.Blueprint('myapp.backend', __name__) + + @frontend.route('/fe') + def frontend_index(): + return flask.url_for('myapp.backend.backend_index') + + @frontend.route('/fe2') + def frontend_page2(): + return flask.url_for('.frontend_index') + + @backend.route('/be') + def backend_index(): + return flask.url_for('myapp.frontend.frontend_index') + + app = flask.Flask(__name__) + app.register_blueprint(frontend) + app.register_blueprint(backend) + + c = app.test_client() + self.assert_equal(c.get('/fe').data.strip(), '/be') + self.assert_equal(c.get('/fe2').data.strip(), '/fe') + self.assert_equal(c.get('/be').data.strip(), '/fe') + + def test_empty_url_defaults(self): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/', defaults={'page': 1}) + @bp.route('/page/') + def something(page): + return str(page) + + app = flask.Flask(__name__) + app.register_blueprint(bp) + + c = app.test_client() + self.assert_equal(c.get('/').data, '1') + self.assert_equal(c.get('/page/2').data, '2') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(BlueprintTestCase)) + suite.addTest(unittest.makeSuite(ModuleTestCase)) + return suite diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py new file mode 100644 index 00000000..c8bf4687 --- /dev/null +++ b/flask/testsuite/config.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.config + ~~~~~~~~~~~~~~~~~~~~~~ + + Configuration and instances. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +# config keys used for the ConfigTestCase +TEST_KEY = 'foo' +SECRET_KEY = 'devkey' + + +class ConfigTestCase(FlaskTestCase): + + def common_object_test(self, app): + assert app.secret_key == 'devkey' + assert app.config['TEST_KEY'] == 'foo' + assert 'ConfigTestCase' not in app.config + + def test_config_from_file(self): + app = flask.Flask(__name__) + app.config.from_pyfile(__file__.rsplit('.')[0] + '.py') + self.common_object_test(app) + + def test_config_from_object(self): + app = flask.Flask(__name__) + app.config.from_object(__name__) + self.common_object_test(app) + + def test_config_from_class(self): + class Base(object): + TEST_KEY = 'foo' + class Test(Base): + SECRET_KEY = 'devkey' + app = flask.Flask(__name__) + app.config.from_object(Test) + self.common_object_test(app) + + def test_config_from_envvar(self): + env = os.environ + try: + os.environ = {} + app = flask.Flask(__name__) + try: + app.config.from_envvar('FOO_SETTINGS') + except RuntimeError, e: + assert "'FOO_SETTINGS' is not set" in str(e) + else: + assert 0, 'expected exception' + assert not app.config.from_envvar('FOO_SETTINGS', silent=True) + + os.environ = {'FOO_SETTINGS': __file__.rsplit('.')[0] + '.py'} + assert app.config.from_envvar('FOO_SETTINGS') + self.common_object_test(app) + finally: + os.environ = env + + def test_config_missing(self): + app = flask.Flask(__name__) + try: + app.config.from_pyfile('missing.cfg') + except IOError, e: + msg = str(e) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") + else: + assert 0, 'expected config' + assert not app.config.from_pyfile('missing.cfg', silent=True) + + +class InstanceTestCase(FlaskTestCase): + + def test_explicit_instance_paths(self): + here = os.path.abspath(os.path.dirname(__file__)) + try: + flask.Flask(__name__, instance_path='instance') + except ValueError, e: + self.assert_('must be absolute' in str(e)) + else: + self.fail('Expected value error') + + app = flask.Flask(__name__, instance_path=here) + self.assert_equal(app.instance_path, here) + + def test_uninstalled_module_paths(self): + from config_module_app import app + here = os.path.abspath(os.path.dirname(__file__)) + self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance')) + + def test_uninstalled_package_paths(self): + from config_package_app import app + here = os.path.abspath(os.path.dirname(__file__)) + self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance')) + + def test_installed_module_paths(self): + import types + expected_prefix = os.path.abspath('foo') + mod = types.ModuleType('myapp') + mod.__file__ = os.path.join(expected_prefix, 'lib', 'python2.5', + 'site-packages', 'myapp.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + def test_installed_package_paths(self): + import types + expected_prefix = os.path.abspath('foo') + package_path = os.path.join(expected_prefix, 'lib', 'python2.5', + 'site-packages', 'myapp') + mod = types.ModuleType('myapp') + mod.__path__ = [package_path] + mod.__file__ = os.path.join(package_path, '__init__.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + def test_prefix_installed_paths(self): + import types + expected_prefix = os.path.abspath(sys.prefix) + package_path = os.path.join(expected_prefix, 'lib', 'python2.5', + 'site-packages', 'myapp') + mod = types.ModuleType('myapp') + mod.__path__ = [package_path] + mod.__file__ = os.path.join(package_path, '__init__.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + def test_egg_installed_paths(self): + import types + expected_prefix = os.path.abspath(sys.prefix) + package_path = os.path.join(expected_prefix, 'lib', 'python2.5', + 'site-packages', 'MyApp.egg', 'myapp') + mod = types.ModuleType('myapp') + mod.__path__ = [package_path] + mod.__file__ = os.path.join(package_path, '__init__.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ConfigTestCase)) + suite.addTest(unittest.makeSuite(InstanceTestCase)) + return suite diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py new file mode 100644 index 00000000..d691b1dd --- /dev/null +++ b/flask/testsuite/deprecations.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.deprecations + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests deprecation support. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase, catch_warnings + + +class DeprecationsTestCase(FlaskTestCase): + + def test_init_jinja_globals(self): + class MyFlask(flask.Flask): + def init_jinja_globals(self): + self.jinja_env.globals['foo'] = '42' + + with catch_warnings() as log: + app = MyFlask(__name__) + @app.route('/') + def foo(): + return app.jinja_env.globals['foo'] + + c = app.test_client() + assert c.get('/').data == '42' + assert len(log) == 1 + assert 'init_jinja_globals' in str(log[0]['message']) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(DeprecationsTestCase)) + return suite diff --git a/flask/testsuite/examples.py b/flask/testsuite/examples.py new file mode 100644 index 00000000..2d30958f --- /dev/null +++ b/flask/testsuite/examples.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.examples + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests the examples. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import unittest +from flask.testsuite import add_to_path + + +def setup_path(): + example_path = os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, 'examples') + add_to_path(os.path.join(example_path, 'flaskr')) + add_to_path(os.path.join(example_path, 'minitwit')) + + +def suite(): + setup_path() + suite = unittest.TestSuite() + try: + from minitwit_tests import MiniTwitTestCase + except ImportError: + pass + else: + suite.addTest(unittest.makeSuite(MiniTwitTestCase)) + try: + from flaskr_tests import FlaskrTestCase + except ImportError: + pass + else: + suite.addTest(unittest.makeSuite(FlaskrTestCase)) + return suite diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py new file mode 100644 index 00000000..faea9c8d --- /dev/null +++ b/flask/testsuite/helpers.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.helpers + ~~~~~~~~~~~~~~~~~~~~~~~ + + Various helpers. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import flask +import unittest +from logging import StreamHandler +from StringIO import StringIO +from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr +from werkzeug.http import parse_options_header + + +def has_encoding(name): + try: + import codecs + codecs.lookup(name) + return True + except LookupError: + return False + + +class JSONTestCase(FlaskTestCase): + + def test_json_bad_requests(self): + app = flask.Flask(__name__) + @app.route('/json', methods=['POST']) + def return_json(): + return unicode(flask.request.json) + c = app.test_client() + rv = c.post('/json', data='malformed', content_type='application/json') + self.assert_equal(rv.status_code, 400) + + def test_json_body_encoding(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.json + + c = app.test_client() + resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), + content_type='application/json; charset=iso-8859-15') + assert resp.data == u'Hällo Wörld'.encode('utf-8') + + def test_jsonify(self): + d = dict(a=23, b=42, c=[1, 2, 3]) + app = flask.Flask(__name__) + @app.route('/kw') + def return_kwargs(): + return flask.jsonify(**d) + @app.route('/dict') + def return_dict(): + return flask.jsonify(d) + c = app.test_client() + for url in '/kw', '/dict': + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == d + + def test_json_attr(self): + app = flask.Flask(__name__) + @app.route('/add', methods=['POST']) + def add(): + return unicode(flask.request.json['a'] + flask.request.json['b']) + c = app.test_client() + rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), + content_type='application/json') + assert rv.data == '3' + + def test_template_escaping(self): + app = flask.Flask(__name__) + render = flask.render_template_string + with app.test_request_context(): + rv = render('{{ ""|tojson|safe }}') + assert rv == '"<\\/script>"' + rv = render('{{ "<\0/script>"|tojson|safe }}') + assert rv == '"<\\u0000\\/script>"' + + def test_modified_url_encoding(self): + class ModifiedRequest(flask.Request): + url_charset = 'euc-kr' + app = flask.Flask(__name__) + app.request_class = ModifiedRequest + app.url_map.charset = 'euc-kr' + + @app.route('/') + def index(): + return flask.request.args['foo'] + + rv = app.test_client().get(u'/?foo=정상처리'.encode('euc-kr')) + assert rv.status_code == 200 + assert rv.data == u'정상처리'.encode('utf-8') + + if not has_encoding('euc-kr'): + test_modified_url_encoding = None + + +class SendfileTestCase(FlaskTestCase): + + def test_send_file_regular(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.send_file('static/index.html') + assert rv.direct_passthrough + assert rv.mimetype == 'text/html' + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + + def test_send_file_xsendfile(self): + app = flask.Flask(__name__) + app.use_x_sendfile = True + with app.test_request_context(): + rv = flask.send_file('static/index.html') + assert rv.direct_passthrough + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + assert rv.mimetype == 'text/html' + + def test_send_file_object(self): + app = flask.Flask(__name__) + with catch_warnings() as captured: + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f) + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + assert rv.mimetype == 'text/html' + # mimetypes + etag + assert len(captured) == 2 + + app.use_x_sendfile = True + with catch_warnings() as captured: + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f) + assert rv.mimetype == 'text/html' + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + # mimetypes + etag + assert len(captured) == 2 + + app.use_x_sendfile = False + with app.test_request_context(): + with catch_warnings() as captured: + f = StringIO('Test') + rv = flask.send_file(f) + assert rv.data == 'Test' + assert rv.mimetype == 'application/octet-stream' + # etags + assert len(captured) == 1 + with catch_warnings() as captured: + f = StringIO('Test') + rv = flask.send_file(f, mimetype='text/plain') + assert rv.data == 'Test' + assert rv.mimetype == 'text/plain' + # etags + assert len(captured) == 1 + + app.use_x_sendfile = True + with catch_warnings() as captured: + with app.test_request_context(): + f = StringIO('Test') + rv = flask.send_file(f) + assert 'x-sendfile' not in rv.headers + # etags + assert len(captured) == 1 + + def test_attachment(self): + app = flask.Flask(__name__) + with catch_warnings() as captured: + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f, as_attachment=True) + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + # mimetypes + etag + assert len(captured) == 2 + + with app.test_request_context(): + assert options['filename'] == 'index.html' + rv = flask.send_file('static/index.html', as_attachment=True) + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + assert options['filename'] == 'index.html' + + with app.test_request_context(): + rv = flask.send_file(StringIO('Test'), as_attachment=True, + attachment_filename='index.txt', + add_etags=False) + assert rv.mimetype == 'text/plain' + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + assert options['filename'] == 'index.txt' + + +class LoggingTestCase(FlaskTestCase): + + def test_logger_cache(self): + app = flask.Flask(__name__) + logger1 = app.logger + assert app.logger is logger1 + assert logger1.name == __name__ + app.logger_name = __name__ + '/test_logger_cache' + assert app.logger is not logger1 + + def test_debug_log(self): + app = flask.Flask(__name__) + app.debug = True + + @app.route('/') + def index(): + app.logger.warning('the standard library is dead') + app.logger.debug('this is a debug statement') + return '' + + @app.route('/exc') + def exc(): + 1/0 + + with app.test_client() as c: + with catch_stderr() as err: + c.get('/') + out = err.getvalue() + assert 'WARNING in helpers [' in out + assert os.path.basename(__file__.rsplit('.')[0] + '.py') in out + assert 'the standard library is dead' in out + assert 'this is a debug statement' in out + + with catch_stderr() as err: + try: + c.get('/exc') + except ZeroDivisionError: + pass + else: + assert False, 'debug log ate the exception' + + def test_exception_logging(self): + out = StringIO() + app = flask.Flask(__name__) + app.logger_name = 'flask_tests/test_exception_logging' + app.logger.addHandler(StreamHandler(out)) + + @app.route('/') + def index(): + 1/0 + + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + + err = out.getvalue() + assert 'Exception on / [GET]' in err + assert 'Traceback (most recent call last):' in err + assert '1/0' in err + assert 'ZeroDivisionError:' in err + + def test_processor_exceptions(self): + app = flask.Flask(__name__) + @app.before_request + def before_request(): + if trigger == 'before': + 1/0 + @app.after_request + def after_request(response): + if trigger == 'after': + 1/0 + return response + @app.route('/') + def index(): + return 'Foo' + @app.errorhandler(500) + def internal_server_error(e): + return 'Hello Server Error', 500 + for trigger in 'before', 'after': + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert rv.data == 'Hello Server Error' + + +def suite(): + suite = unittest.TestSuite() + if flask.json_available: + suite.addTest(unittest.makeSuite(JSONTestCase)) + suite.addTest(unittest.makeSuite(SendfileTestCase)) + suite.addTest(unittest.makeSuite(LoggingTestCase)) + return suite diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py new file mode 100644 index 00000000..e55807b3 --- /dev/null +++ b/flask/testsuite/signals.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.signals + ~~~~~~~~~~~~~~~~~~~~~~~ + + Signalling. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class SignalsTestCase(FlaskTestCase): + + def test_template_rendered(self): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return flask.render_template('simple_template.html', whiskey=42) + + recorded = [] + def record(sender, template, context): + recorded.append((template, context)) + + flask.template_rendered.connect(record, app) + try: + app.test_client().get('/') + assert len(recorded) == 1 + template, context = recorded[0] + assert template.name == 'simple_template.html' + assert context['whiskey'] == 42 + finally: + flask.template_rendered.disconnect(record, app) + + def test_request_signals(self): + app = flask.Flask(__name__) + calls = [] + + def before_request_signal(sender): + calls.append('before-signal') + + def after_request_signal(sender, response): + assert response.data == 'stuff' + calls.append('after-signal') + + @app.before_request + def before_request_handler(): + calls.append('before-handler') + + @app.after_request + def after_request_handler(response): + calls.append('after-handler') + response.data = 'stuff' + return response + + @app.route('/') + def index(): + calls.append('handler') + return 'ignored anyway' + + flask.request_started.connect(before_request_signal, app) + flask.request_finished.connect(after_request_signal, app) + + try: + rv = app.test_client().get('/') + assert rv.data == 'stuff' + + assert calls == ['before-signal', 'before-handler', + 'handler', 'after-handler', + 'after-signal'] + finally: + flask.request_started.disconnect(before_request_signal, app) + flask.request_finished.disconnect(after_request_signal, app) + + def test_request_exception_signal(self): + app = flask.Flask(__name__) + recorded = [] + + @app.route('/') + def index(): + 1/0 + + def record(sender, exception): + recorded.append(exception) + + flask.got_request_exception.connect(record, app) + try: + assert app.test_client().get('/').status_code == 500 + assert len(recorded) == 1 + assert isinstance(recorded[0], ZeroDivisionError) + finally: + flask.got_request_exception.disconnect(record, app) + + +def suite(): + suite = unittest.TestSuite() + if flask.signals_available: + suite.addTest(unittest.makeSuite(SignalsTestCase)) + return suite diff --git a/tests/static/index.html b/flask/testsuite/static/index.html similarity index 100% rename from tests/static/index.html rename to flask/testsuite/static/index.html diff --git a/tests/templates/_macro.html b/flask/testsuite/templates/_macro.html similarity index 100% rename from tests/templates/_macro.html rename to flask/testsuite/templates/_macro.html diff --git a/tests/templates/context_template.html b/flask/testsuite/templates/context_template.html similarity index 100% rename from tests/templates/context_template.html rename to flask/testsuite/templates/context_template.html diff --git a/tests/templates/escaping_template.html b/flask/testsuite/templates/escaping_template.html similarity index 100% rename from tests/templates/escaping_template.html rename to flask/testsuite/templates/escaping_template.html diff --git a/tests/templates/mail.txt b/flask/testsuite/templates/mail.txt similarity index 100% rename from tests/templates/mail.txt rename to flask/testsuite/templates/mail.txt diff --git a/tests/templates/nested/nested.txt b/flask/testsuite/templates/nested/nested.txt similarity index 100% rename from tests/templates/nested/nested.txt rename to flask/testsuite/templates/nested/nested.txt diff --git a/tests/templates/simple_template.html b/flask/testsuite/templates/simple_template.html similarity index 100% rename from tests/templates/simple_template.html rename to flask/testsuite/templates/simple_template.html diff --git a/tests/templates/template_filter.html b/flask/testsuite/templates/template_filter.html similarity index 100% rename from tests/templates/template_filter.html rename to flask/testsuite/templates/template_filter.html diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py new file mode 100644 index 00000000..e980ff92 --- /dev/null +++ b/flask/testsuite/templating.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.templating + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Template functionality + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class TemplatingTestCase(FlaskTestCase): + + def test_context_processing(self): + app = flask.Flask(__name__) + @app.context_processor + def context_processor(): + return {'injected_value': 42} + @app.route('/') + def index(): + return flask.render_template('context_template.html', value=23) + rv = app.test_client().get('/') + assert rv.data == '

23|42' + + def test_original_win(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template_string('{{ config }}', config=42) + rv = app.test_client().get('/') + assert rv.data == '42' + + def test_standard_context(self): + app = flask.Flask(__name__) + app.secret_key = 'development key' + @app.route('/') + def index(): + flask.g.foo = 23 + flask.session['test'] = 'aha' + return flask.render_template_string(''' + {{ request.args.foo }} + {{ g.foo }} + {{ config.DEBUG }} + {{ session.test }} + ''') + rv = app.test_client().get('/?foo=42') + assert rv.data.split() == ['42', '23', 'False', 'aha'] + + def test_escaping(self): + text = '

Hello World!' + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template('escaping_template.html', text=text, + html=flask.Markup(text)) + lines = app.test_client().get('/').data.splitlines() + assert lines == [ + '<p>Hello World!', + '

Hello World!', + '

Hello World!', + '

Hello World!', + '<p>Hello World!', + '

Hello World!' + ] + + def test_no_escaping(self): + app = flask.Flask(__name__) + with app.test_request_context(): + assert flask.render_template_string('{{ foo }}', + foo='') == '' + assert flask.render_template('mail.txt', foo='') \ + == ' Mail' + + def test_macros(self): + app = flask.Flask(__name__) + with app.test_request_context(): + macro = flask.get_template_attribute('_macro.html', 'hello') + assert macro('World') == 'Hello World!' + + def test_template_filter(self): + app = flask.Flask(__name__) + @app.template_filter() + def my_reverse(s): + return s[::-1] + assert 'my_reverse' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['my_reverse'] == my_reverse + assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' + + def test_template_filter_with_name(self): + app = flask.Flask(__name__) + @app.template_filter('strrev') + def my_reverse(s): + return s[::-1] + assert 'strrev' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['strrev'] == my_reverse + assert app.jinja_env.filters['strrev']('abcd') == 'dcba' + + def test_template_filter_with_template(self): + app = flask.Flask(__name__) + @app.template_filter() + def super_reverse(s): + return s[::-1] + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + assert rv.data == 'dcba' + + def test_template_filter_with_name_and_template(self): + app = flask.Flask(__name__) + @app.template_filter('super_reverse') + def my_reverse(s): + return s[::-1] + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + assert rv.data == 'dcba' + + def test_custom_template_loader(self): + class MyFlask(flask.Flask): + def create_global_jinja_loader(self): + from jinja2 import DictLoader + return DictLoader({'index.html': 'Hello Custom World!'}) + app = MyFlask(__name__) + @app.route('/') + def index(): + return flask.render_template('index.html') + c = app.test_client() + rv = c.get('/') + assert rv.data == 'Hello Custom World!' + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TemplatingTestCase)) + return suite diff --git a/tests/blueprintapp/__init__.py b/flask/testsuite/test_apps/blueprintapp/__init__.py similarity index 100% rename from tests/blueprintapp/__init__.py rename to flask/testsuite/test_apps/blueprintapp/__init__.py diff --git a/tests/blueprintapp/apps/__init__.py b/flask/testsuite/test_apps/blueprintapp/apps/__init__.py similarity index 100% rename from tests/blueprintapp/apps/__init__.py rename to flask/testsuite/test_apps/blueprintapp/apps/__init__.py diff --git a/tests/blueprintapp/apps/admin/__init__.py b/flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py similarity index 100% rename from tests/blueprintapp/apps/admin/__init__.py rename to flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py diff --git a/tests/blueprintapp/apps/admin/static/css/test.css b/flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css similarity index 100% rename from tests/blueprintapp/apps/admin/static/css/test.css rename to flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css diff --git a/tests/blueprintapp/apps/admin/static/test.txt b/flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt similarity index 100% rename from tests/blueprintapp/apps/admin/static/test.txt rename to flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt diff --git a/tests/blueprintapp/apps/admin/templates/admin/index.html b/flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html similarity index 100% rename from tests/blueprintapp/apps/admin/templates/admin/index.html rename to flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html diff --git a/tests/blueprintapp/apps/frontend/__init__.py b/flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py similarity index 100% rename from tests/blueprintapp/apps/frontend/__init__.py rename to flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py diff --git a/tests/blueprintapp/apps/frontend/templates/frontend/index.html b/flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html similarity index 100% rename from tests/blueprintapp/apps/frontend/templates/frontend/index.html rename to flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html diff --git a/flask/testsuite/test_apps/config_module_app.py b/flask/testsuite/test_apps/config_module_app.py new file mode 100644 index 00000000..380d46bf --- /dev/null +++ b/flask/testsuite/test_apps/config_module_app.py @@ -0,0 +1,4 @@ +import os +import flask +here = os.path.abspath(os.path.dirname(__file__)) +app = flask.Flask(__name__) diff --git a/flask/testsuite/test_apps/config_package_app/__init__.py b/flask/testsuite/test_apps/config_package_app/__init__.py new file mode 100644 index 00000000..380d46bf --- /dev/null +++ b/flask/testsuite/test_apps/config_package_app/__init__.py @@ -0,0 +1,4 @@ +import os +import flask +here = os.path.abspath(os.path.dirname(__file__)) +app = flask.Flask(__name__) diff --git a/tests/moduleapp/__init__.py b/flask/testsuite/test_apps/moduleapp/__init__.py similarity index 100% rename from tests/moduleapp/__init__.py rename to flask/testsuite/test_apps/moduleapp/__init__.py diff --git a/tests/moduleapp/apps/__init__.py b/flask/testsuite/test_apps/moduleapp/apps/__init__.py similarity index 100% rename from tests/moduleapp/apps/__init__.py rename to flask/testsuite/test_apps/moduleapp/apps/__init__.py diff --git a/tests/moduleapp/apps/admin/__init__.py b/flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py similarity index 100% rename from tests/moduleapp/apps/admin/__init__.py rename to flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py diff --git a/tests/moduleapp/apps/admin/static/css/test.css b/flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css similarity index 100% rename from tests/moduleapp/apps/admin/static/css/test.css rename to flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css diff --git a/tests/moduleapp/apps/admin/static/test.txt b/flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt similarity index 100% rename from tests/moduleapp/apps/admin/static/test.txt rename to flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt diff --git a/tests/moduleapp/apps/admin/templates/index.html b/flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html similarity index 100% rename from tests/moduleapp/apps/admin/templates/index.html rename to flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html diff --git a/tests/moduleapp/apps/frontend/__init__.py b/flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py similarity index 100% rename from tests/moduleapp/apps/frontend/__init__.py rename to flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py diff --git a/tests/moduleapp/apps/frontend/templates/index.html b/flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html similarity index 100% rename from tests/moduleapp/apps/frontend/templates/index.html rename to flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html diff --git a/tests/subdomaintestmodule/__init__.py b/flask/testsuite/test_apps/subdomaintestmodule/__init__.py similarity index 100% rename from tests/subdomaintestmodule/__init__.py rename to flask/testsuite/test_apps/subdomaintestmodule/__init__.py diff --git a/tests/subdomaintestmodule/static/hello.txt b/flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt similarity index 100% rename from tests/subdomaintestmodule/static/hello.txt rename to flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py new file mode 100644 index 00000000..4e16c257 --- /dev/null +++ b/flask/testsuite/testing.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.testing + ~~~~~~~~~~~~~~~~~~~~~~~ + + Test client and more. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class TestToolsTestCase(FlaskTestCase): + + def test_environ_defaults_from_config(self): + app = flask.Flask(__name__) + app.testing = True + app.config['SERVER_NAME'] = 'example.com:1234' + app.config['APPLICATION_ROOT'] = '/foo' + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assert_equal(ctx.request.url, 'http://example.com:1234/foo/') + with app.test_client() as c: + rv = c.get('/') + self.assert_equal(rv.data, 'http://example.com:1234/foo/') + + def test_environ_defaults(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assert_equal(ctx.request.url, 'http://localhost/') + with app.test_client() as c: + rv = c.get('/') + self.assert_equal(rv.data, 'http://localhost/') + + def test_session_transactions(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + @app.route('/') + def index(): + return unicode(flask.session['foo']) + + with app.test_client() as c: + with c.session_transaction() as sess: + self.assert_equal(len(sess), 0) + sess['foo'] = [42] + self.assert_equal(len(sess), 1) + rv = c.get('/') + self.assert_equal(rv.data, '[42]') + + def test_session_transactions_no_null_sessions(self): + app = flask.Flask(__name__) + app.testing = True + + with app.test_client() as c: + try: + with c.session_transaction() as sess: + pass + except RuntimeError, e: + self.assert_('Session backend did not open a session' in str(e)) + else: + self.fail('Expected runtime error') + + def test_session_transactions_keep_context(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + with app.test_client() as c: + rv = c.get('/') + req = flask.request._get_current_object() + with c.session_transaction(): + self.assert_(req is flask.request._get_current_object()) + + def test_test_client_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + flask.g.value = 42 + return 'Hello World!' + + @app.route('/other') + def other(): + 1/0 + + with app.test_client() as c: + resp = c.get('/') + assert flask.g.value == 42 + assert resp.data == 'Hello World!' + assert resp.status_code == 200 + + resp = c.get('/other') + assert not hasattr(flask.g, 'value') + assert 'Internal Server Error' in resp.data + assert resp.status_code == 500 + flask.g.value = 23 + + try: + flask.g.value + except (AttributeError, RuntimeError): + pass + else: + raise AssertionError('some kind of exception expected') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestToolsTestCase)) + return suite diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py new file mode 100644 index 00000000..a89c44ac --- /dev/null +++ b/flask/testsuite/views.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.views + ~~~~~~~~~~~~~~~~~~~~~ + + Pluggable views. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import flask.views +import unittest +from flask.testsuite import FlaskTestCase +from werkzeug.http import parse_set_header + + +class ViewTestCase(FlaskTestCase): + + def common_test(self, app): + c = app.test_client() + + self.assert_equal(c.get('/').data, 'GET') + self.assert_equal(c.post('/').data, 'POST') + self.assert_equal(c.put('/').status_code, 405) + meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) + self.assert_equal(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST']) + + def test_basic_view(self): + app = flask.Flask(__name__) + + class Index(flask.views.View): + methods = ['GET', 'POST'] + def dispatch_request(self): + return flask.request.method + + app.add_url_rule('/', view_func=Index.as_view('index')) + self.common_test(app) + + def test_method_based_view(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + return 'GET' + def post(self): + return 'POST' + + app.add_url_rule('/', view_func=Index.as_view('index')) + + self.common_test(app) + + def test_view_patching(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + 1/0 + def post(self): + 1/0 + + class Other(Index): + def get(self): + return 'GET' + def post(self): + return 'POST' + + view = Index.as_view('index') + view.view_class = Other + app.add_url_rule('/', view_func=view) + self.common_test(app) + + def test_view_inheritance(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + return 'GET' + def post(self): + return 'POST' + + class BetterIndex(Index): + def delete(self): + return 'DELETE' + + app.add_url_rule('/', view_func=BetterIndex.as_view('index')) + c = app.test_client() + + meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) + self.assert_equal(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) + + def test_view_decorators(self): + app = flask.Flask(__name__) + + def add_x_parachute(f): + def new_function(*args, **kwargs): + resp = flask.make_response(f(*args, **kwargs)) + resp.headers['X-Parachute'] = 'awesome' + return resp + return new_function + + class Index(flask.views.View): + decorators = [add_x_parachute] + def dispatch_request(self): + return 'Awesome' + + app.add_url_rule('/', view_func=Index.as_view('index')) + c = app.test_client() + rv = c.get('/') + self.assert_equal(rv.headers['X-Parachute'], 'awesome') + self.assert_equal(rv.data, 'Awesome') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ViewTestCase)) + return suite diff --git a/tests/flaskext_test.py b/scripts/flaskext_test.py similarity index 100% rename from tests/flaskext_test.py rename to scripts/flaskext_test.py diff --git a/setup.py b/setup.py index 1809e05e..a51c3887 100644 --- a/setup.py +++ b/setup.py @@ -77,12 +77,6 @@ class run_audit(Command): else: print ("No problems found in sourcecode.") -def run_tests(): - import os, sys - sys.path.append(os.path.join(os.path.dirname(__file__), 'tests')) - from flask_tests import suite - return suite() - setup( name='Flask', @@ -112,5 +106,5 @@ setup( 'Topic :: Software Development :: Libraries :: Python Modules' ], cmdclass={'audit': run_audit}, - test_suite='__main__.run_tests' + test_suite='flask.testsuite.suite' ) diff --git a/tests/flask_tests.py b/tests/flask_tests.py deleted file mode 100644 index dcc3e1a0..00000000 --- a/tests/flask_tests.py +++ /dev/null @@ -1,2329 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Flask Tests - ~~~~~~~~~~~ - - Tests Flask itself. The majority of Flask is already tested - as part of Werkzeug. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -from __future__ import with_statement -import os -import re -import sys -import flask -import flask.views -import unittest -import warnings -from threading import Thread -from logging import StreamHandler -from contextlib import contextmanager -from functools import update_wrapper -from datetime import datetime -from werkzeug import parse_date, parse_options_header -from werkzeug.exceptions import NotFound, BadRequest -from werkzeug.http import parse_set_header -from jinja2 import TemplateNotFound -from cStringIO import StringIO - -example_path = os.path.join(os.path.dirname(__file__), '..', 'examples') -sys.path.append(os.path.join(example_path, 'flaskr')) -sys.path.append(os.path.join(example_path, 'minitwit')) - - -def has_encoding(name): - try: - import codecs - codecs.lookup(name) - return True - except LookupError: - return False - - -# config keys used for the ConfigTestCase -TEST_KEY = 'foo' -SECRET_KEY = 'devkey' - - -# import moduleapp here because it uses deprecated features and we don't -# want to see the warnings -warnings.simplefilter('ignore', DeprecationWarning) -from moduleapp import app as moduleapp -warnings.simplefilter('default', DeprecationWarning) - - -@contextmanager -def catch_warnings(): - """Catch warnings in a with block in a list""" - # make sure deprecation warnings are active in tests - warnings.simplefilter('default', category=DeprecationWarning) - - filters = warnings.filters - warnings.filters = filters[:] - old_showwarning = warnings.showwarning - log = [] - def showwarning(message, category, filename, lineno, file=None, line=None): - log.append(locals()) - try: - warnings.showwarning = showwarning - yield log - finally: - warnings.filters = filters - warnings.showwarning = old_showwarning - - -@contextmanager -def catch_stderr(): - """Catch stderr in a StringIO""" - old_stderr = sys.stderr - sys.stderr = rv = StringIO() - try: - yield rv - finally: - sys.stderr = old_stderr - - -def emits_module_deprecation_warning(f): - def new_f(*args, **kwargs): - with catch_warnings() as log: - f(*args, **kwargs) - assert log, 'expected deprecation warning' - for entry in log: - assert 'Modules are deprecated' in str(entry['message']) - return update_wrapper(new_f, f) - - -class FlaskTestCase(unittest.TestCase): - - def ensure_clean_request_context(self): - # make sure we're not leaking a request context since we are - # testing flask internally in debug mode in a few cases - self.assertEqual(flask._request_ctx_stack.top, None) - - def tearDown(self): - unittest.TestCase.tearDown(self) - self.ensure_clean_request_context() - - -class ContextTestCase(FlaskTestCase): - - def test_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return 'Hello %s!' % flask.request.args['name'] - @app.route('/meh') - def meh(): - return flask.request.url - - with app.test_request_context('/?name=World'): - assert index() == 'Hello World!' - with app.test_request_context('/meh'): - assert meh() == 'http://localhost/meh' - assert flask._request_ctx_stack.top is None - - def test_context_test(self): - app = flask.Flask(__name__) - assert not flask.request - assert not flask.has_request_context() - ctx = app.test_request_context() - ctx.push() - try: - assert flask.request - assert flask.has_request_context() - finally: - ctx.pop() - - def test_manual_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return 'Hello %s!' % flask.request.args['name'] - - ctx = app.test_request_context('/?name=World') - ctx.push() - assert index() == 'Hello World!' - ctx.pop() - try: - index() - except RuntimeError: - pass - else: - assert 0, 'expected runtime error' - - -class BasicFunctionalityTestCase(FlaskTestCase): - - def test_options_work(self): - app = flask.Flask(__name__) - @app.route('/', methods=['GET', 'POST']) - def index(): - return 'Hello World' - rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - assert rv.data == '' - - def test_options_on_multiple_rules(self): - app = flask.Flask(__name__) - @app.route('/', methods=['GET', 'POST']) - def index(): - return 'Hello World' - @app.route('/', methods=['PUT']) - def index_put(): - return 'Aha!' - rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - def test_options_handling_disabled(self): - app = flask.Flask(__name__) - def index(): - return 'Hello World!' - index.provide_automatic_options = False - app.route('/')(index) - rv = app.test_client().open('/', method='OPTIONS') - assert rv.status_code == 405 - - app = flask.Flask(__name__) - def index2(): - return 'Hello World!' - index2.provide_automatic_options = True - app.route('/', methods=['OPTIONS'])(index2) - rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['OPTIONS'] - - def test_request_dispatching(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return flask.request.method - @app.route('/more', methods=['GET', 'POST']) - def more(): - return flask.request.method - - c = app.test_client() - assert c.get('/').data == 'GET' - rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] - rv = c.head('/') - assert rv.status_code == 200 - assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' - rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - - def test_url_mapping(self): - app = flask.Flask(__name__) - def index(): - return flask.request.method - def more(): - return flask.request.method - - app.add_url_rule('/', 'index', index) - app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) - - c = app.test_client() - assert c.get('/').data == 'GET' - rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] - rv = c.head('/') - assert rv.status_code == 200 - assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' - rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - - def test_werkzeug_routing(self): - from werkzeug.routing import Submount, Rule - app = flask.Flask(__name__) - app.url_map.add(Submount('/foo', [ - Rule('/bar', endpoint='bar'), - Rule('/', endpoint='index') - ])) - def bar(): - return 'bar' - def index(): - return 'index' - app.view_functions['bar'] = bar - app.view_functions['index'] = index - - c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' - - def test_endpoint_decorator(self): - from werkzeug.routing import Submount, Rule - app = flask.Flask(__name__) - app.url_map.add(Submount('/foo', [ - Rule('/bar', endpoint='bar'), - Rule('/', endpoint='index') - ])) - - @app.endpoint('bar') - def bar(): - return 'bar' - - @app.endpoint('index') - def index(): - return 'index' - - c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' - - def test_session(self): - app = flask.Flask(__name__) - app.secret_key = 'testkey' - @app.route('/set', methods=['POST']) - def set(): - flask.session['value'] = flask.request.form['value'] - return 'value set' - @app.route('/get') - def get(): - return flask.session['value'] - - c = app.test_client() - assert c.post('/set', data={'value': '42'}).data == 'value set' - assert c.get('/get').data == '42' - - def test_session_using_server_name(self): - app = flask.Flask(__name__) - app.config.update( - SECRET_KEY='foo', - SERVER_NAME='example.com' - ) - @app.route('/') - def index(): - flask.session['testing'] = 42 - return 'Hello World' - rv = app.test_client().get('/', 'http://example.com/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() - - def test_session_using_server_name_and_port(self): - app = flask.Flask(__name__) - app.config.update( - SECRET_KEY='foo', - SERVER_NAME='example.com:8080' - ) - @app.route('/') - def index(): - flask.session['testing'] = 42 - return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() - - def test_session_using_application_root(self): - class PrefixPathMiddleware(object): - def __init__(self, app, prefix): - self.app = app - self.prefix = prefix - def __call__(self, environ, start_response): - environ['SCRIPT_NAME'] = self.prefix - return self.app(environ, start_response) - - app = flask.Flask(__name__) - app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') - app.config.update( - SECRET_KEY='foo', - APPLICATION_ROOT='/bar' - ) - @app.route('/') - def index(): - flask.session['testing'] = 42 - return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'path=/bar' in rv.headers['set-cookie'].lower() - - def test_missing_session(self): - app = flask.Flask(__name__) - def expect_exception(f, *args, **kwargs): - try: - f(*args, **kwargs) - except RuntimeError, e: - assert e.args and 'session is unavailable' in e.args[0] - else: - assert False, 'expected exception' - with app.test_request_context(): - assert flask.session.get('missing_key') is None - expect_exception(flask.session.__setitem__, 'foo', 42) - expect_exception(flask.session.pop, 'foo') - - def test_session_expiration(self): - permanent = True - app = flask.Flask(__name__) - app.secret_key = 'testkey' - @app.route('/') - def index(): - flask.session['test'] = 42 - flask.session.permanent = permanent - return '' - - @app.route('/test') - def test(): - return unicode(flask.session.permanent) - - client = app.test_client() - rv = client.get('/') - assert 'set-cookie' in rv.headers - match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) - expires = parse_date(match.group()) - expected = datetime.utcnow() + app.permanent_session_lifetime - assert expires.year == expected.year - assert expires.month == expected.month - assert expires.day == expected.day - - rv = client.get('/test') - assert rv.data == 'True' - - permanent = False - rv = app.test_client().get('/') - assert 'set-cookie' in rv.headers - match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) - assert match is None - - def test_flashes(self): - app = flask.Flask(__name__) - app.secret_key = 'testkey' - - with app.test_request_context(): - assert not flask.session.modified - flask.flash('Zap') - flask.session.modified = False - flask.flash('Zip') - assert flask.session.modified - assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] - - def test_extended_flashing(self): - app = flask.Flask(__name__) - app.secret_key = 'testkey' - - @app.route('/') - def index(): - flask.flash(u'Hello World') - flask.flash(u'Hello World', 'error') - flask.flash(flask.Markup(u'Testing'), 'warning') - return '' - - @app.route('/test') - def test(): - messages = flask.get_flashed_messages(with_categories=True) - assert len(messages) == 3 - assert messages[0] == ('message', u'Hello World') - assert messages[1] == ('error', u'Hello World') - assert messages[2] == ('warning', flask.Markup(u'Testing')) - return '' - messages = flask.get_flashed_messages() - assert len(messages) == 3 - assert messages[0] == u'Hello World' - assert messages[1] == u'Hello World' - assert messages[2] == flask.Markup(u'Testing') - - c = app.test_client() - c.get('/') - c.get('/test') - - def test_request_processing(self): - app = flask.Flask(__name__) - evts = [] - @app.before_request - def before_request(): - evts.append('before') - @app.after_request - def after_request(response): - response.data += '|after' - evts.append('after') - return response - @app.route('/') - def index(): - assert 'before' in evts - assert 'after' not in evts - return 'request' - assert 'after' not in evts - rv = app.test_client().get('/').data - assert 'after' in evts - assert rv == 'request|after' - - def test_teardown_request_handler(self): - called = [] - app = flask.Flask(__name__) - @app.teardown_request - def teardown_request(exc): - called.append(True) - return "Ignored" - @app.route('/') - def root(): - return "Response" - rv = app.test_client().get('/') - assert rv.status_code == 200 - assert 'Response' in rv.data - assert len(called) == 1 - - def test_teardown_request_handler_debug_mode(self): - called = [] - app = flask.Flask(__name__) - app.testing = True - @app.teardown_request - def teardown_request(exc): - called.append(True) - return "Ignored" - @app.route('/') - def root(): - return "Response" - rv = app.test_client().get('/') - assert rv.status_code == 200 - assert 'Response' in rv.data - assert len(called) == 1 - - def test_teardown_request_handler_error(self): - called = [] - app = flask.Flask(__name__) - @app.teardown_request - def teardown_request1(exc): - assert type(exc) == ZeroDivisionError - called.append(True) - # This raises a new error and blows away sys.exc_info(), so we can - # test that all teardown_requests get passed the same original - # exception. - try: - raise TypeError - except: - pass - @app.teardown_request - def teardown_request2(exc): - assert type(exc) == ZeroDivisionError - called.append(True) - # This raises a new error and blows away sys.exc_info(), so we can - # test that all teardown_requests get passed the same original - # exception. - try: - raise TypeError - except: - pass - @app.route('/') - def fails(): - 1/0 - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert 'Internal Server Error' in rv.data - assert len(called) == 2 - - def test_before_after_request_order(self): - called = [] - app = flask.Flask(__name__) - @app.before_request - def before1(): - called.append(1) - @app.before_request - def before2(): - called.append(2) - @app.after_request - def after1(response): - called.append(4) - return response - @app.after_request - def after2(response): - called.append(3) - return response - @app.teardown_request - def finish1(exc): - called.append(6) - @app.teardown_request - def finish2(exc): - called.append(5) - @app.route('/') - def index(): - return '42' - rv = app.test_client().get('/') - assert rv.data == '42' - assert called == [1, 2, 3, 4, 5, 6] - - def test_error_handling(self): - app = flask.Flask(__name__) - @app.errorhandler(404) - def not_found(e): - return 'not found', 404 - @app.errorhandler(500) - def internal_server_error(e): - return 'internal server error', 500 - @app.route('/') - def index(): - flask.abort(404) - @app.route('/error') - def error(): - 1 // 0 - c = app.test_client() - rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' - rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data - - def test_before_request_and_routing_errors(self): - app = flask.Flask(__name__) - @app.before_request - def attach_something(): - flask.g.something = 'value' - @app.errorhandler(404) - def return_something(error): - return flask.g.something, 404 - rv = app.test_client().get('/') - assert rv.status_code == 404 - assert rv.data == 'value' - - def test_user_error_handling(self): - class MyException(Exception): - pass - - app = flask.Flask(__name__) - @app.errorhandler(MyException) - def handle_my_exception(e): - assert isinstance(e, MyException) - return '42' - @app.route('/') - def index(): - raise MyException() - - c = app.test_client() - assert c.get('/').data == '42' - - def test_trapping_of_bad_request_key_errors(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/fail') - def fail(): - flask.request.form['missing_key'] - c = app.test_client() - assert c.get('/fail').status_code == 400 - - app.config['TRAP_BAD_REQUEST_ERRORS'] = True - c = app.test_client() - try: - c.get('/fail') - except KeyError, e: - assert isinstance(e, BadRequest) - else: - self.fail('Expected exception') - - def test_trapping_of_all_http_exceptions(self): - app = flask.Flask(__name__) - app.testing = True - app.config['TRAP_HTTP_EXCEPTIONS'] = True - @app.route('/fail') - def fail(): - flask.abort(404) - - c = app.test_client() - try: - c.get('/fail') - except NotFound, e: - pass - else: - self.fail('Expected exception') - - def test_enctype_debug_helper(self): - from flask.debughelpers import DebugFilesKeyError - app = flask.Flask(__name__) - app.debug = True - @app.route('/fail', methods=['POST']) - def index(): - return flask.request.files['foo'].filename - - # with statement is important because we leave an exception on the - # stack otherwise and we want to ensure that this is not the case - # to not negatively affect other tests. - with app.test_client() as c: - try: - c.post('/fail', data={'foo': 'index.txt'}) - except DebugFilesKeyError, e: - assert 'no file contents were transmitted' in str(e) - assert 'This was submitted: "index.txt"' in str(e) - else: - self.fail('Expected exception') - - def test_teardown_on_pop(self): - buffer = [] - app = flask.Flask(__name__) - @app.teardown_request - def end_of_request(exception): - buffer.append(exception) - - ctx = app.test_request_context() - ctx.push() - assert buffer == [] - ctx.pop() - assert buffer == [None] - - def test_response_creation(self): - app = flask.Flask(__name__) - @app.route('/unicode') - def from_unicode(): - return u'Hällo Wörld' - @app.route('/string') - def from_string(): - return u'Hällo Wörld'.encode('utf-8') - @app.route('/args') - def from_tuple(): - return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' - c = app.test_client() - assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') - assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') - rv = c.get('/args') - assert rv.data == 'Meh' - assert rv.headers['X-Foo'] == 'Testing' - assert rv.status_code == 400 - assert rv.mimetype == 'text/plain' - - def test_make_response(self): - app = flask.Flask(__name__) - with app.test_request_context(): - rv = flask.make_response() - assert rv.status_code == 200 - assert rv.data == '' - assert rv.mimetype == 'text/html' - - rv = flask.make_response('Awesome') - assert rv.status_code == 200 - assert rv.data == 'Awesome' - assert rv.mimetype == 'text/html' - - rv = flask.make_response('W00t', 404) - assert rv.status_code == 404 - assert rv.data == 'W00t' - assert rv.mimetype == 'text/html' - - def test_url_generation(self): - app = flask.Flask(__name__) - @app.route('/hello/', methods=['POST']) - def hello(): - pass - with app.test_request_context(): - assert flask.url_for('hello', name='test x') == '/hello/test%20x' - assert flask.url_for('hello', name='test x', _external=True) \ - == 'http://localhost/hello/test%20x' - - def test_custom_converters(self): - from werkzeug.routing import BaseConverter - class ListConverter(BaseConverter): - def to_python(self, value): - return value.split(',') - def to_url(self, value): - base_to_url = super(ListConverter, self).to_url - return ','.join(base_to_url(x) for x in value) - app = flask.Flask(__name__) - app.url_map.converters['list'] = ListConverter - @app.route('/') - def index(args): - return '|'.join(args) - c = app.test_client() - assert c.get('/1,2,3').data == '1|2|3' - - def test_static_files(self): - app = flask.Flask(__name__) - rv = app.test_client().get('/static/index.html') - assert rv.status_code == 200 - assert rv.data.strip() == '

Hello World!

' - with app.test_request_context(): - assert flask.url_for('static', filename='index.html') \ - == '/static/index.html' - - def test_none_response(self): - app = flask.Flask(__name__) - @app.route('/') - def test(): - return None - try: - app.test_client().get('/') - except ValueError, e: - assert str(e) == 'View function did not return a response' - pass - else: - assert "Expected ValueError" - - def test_request_locals(self): - self.assertEqual(repr(flask.g), '') - self.assertFalse(flask.g) - - def test_proper_test_request_context(self): - app = flask.Flask(__name__) - app.config.update( - SERVER_NAME='localhost.localdomain:5000' - ) - - @app.route('/') - def index(): - return None - - @app.route('/', subdomain='foo') - def sub(): - return None - - with app.test_request_context('/'): - assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' - - with app.test_request_context('/'): - assert flask.url_for('sub', _external=True) == 'http://foo.localhost.localdomain:5000/' - - try: - with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): - pass - except Exception, e: - assert isinstance(e, ValueError) - assert str(e) == "the server name provided " + \ - "('localhost.localdomain:5000') does not match the " + \ - "server name from the WSGI environment ('localhost')", str(e) - - try: - app.config.update(SERVER_NAME='localhost') - with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}): - pass - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - app.config.update(SERVER_NAME='localhost:80') - with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): - pass - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - def test_test_app_proper_environ(self): - app = flask.Flask(__name__) - app.config.update( - SERVER_NAME='localhost.localdomain:5000' - ) - @app.route('/') - def index(): - return 'Foo' - - @app.route('/', subdomain='foo') - def subdomain(): - return 'Foo SubDomain' - - try: - rv = app.test_client().get('/') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - rv = app.test_client().get('/', 'http://localhost.localdomain:5000') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - rv = app.test_client().get('/', 'https://localhost.localdomain:5000') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - app.config.update(SERVER_NAME='localhost.localdomain') - rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - app.config.update(SERVER_NAME='localhost.localdomain:443') - rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' - except ValueError, e: - assert str(e) == "the server name provided " + \ - "('localhost.localdomain:443') does not match the " + \ - "server name from the WSGI environment ('localhost.localdomain')", str(e) - - try: - app.config.update(SERVER_NAME='localhost.localdomain') - app.test_client().get('/', 'http://foo.localhost') - except ValueError, e: - assert str(e) == "the server name provided " + \ - "('localhost.localdomain') does not match the " + \ - "server name from the WSGI environment ('foo.localhost')", str(e) - - try: - rv = app.test_client().get('/', 'http://foo.localhost.localdomain') - assert rv.data == 'Foo SubDomain' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - def test_exception_propagation(self): - def apprunner(configkey): - app = flask.Flask(__name__) - @app.route('/') - def index(): - 1/0 - c = app.test_client() - if config_key is not None: - app.config[config_key] = True - try: - resp = c.get('/') - except Exception: - pass - else: - self.fail('expected exception') - else: - assert c.get('/').status_code == 500 - - # we have to run this test in an isolated thread because if the - # debug flag is set to true and an exception happens the context is - # not torn down. This causes other tests that run after this fail - # when they expect no exception on the stack. - for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None: - t = Thread(target=apprunner, args=(config_key,)) - t.start() - t.join() - - def test_max_content_length(self): - app = flask.Flask(__name__) - app.config['MAX_CONTENT_LENGTH'] = 64 - @app.before_request - def always_first(): - flask.request.form['myfile'] - assert False - @app.route('/accept', methods=['POST']) - def accept_file(): - flask.request.form['myfile'] - assert False - @app.errorhandler(413) - def catcher(error): - return '42' - - c = app.test_client() - rv = c.post('/accept', data={'myfile': 'foo' * 100}) - assert rv.data == '42' - - def test_url_processors(self): - app = flask.Flask(__name__) - - @app.url_defaults - def add_language_code(endpoint, values): - if flask.g.lang_code is not None and \ - app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): - values.setdefault('lang_code', flask.g.lang_code) - - @app.url_value_preprocessor - def pull_lang_code(endpoint, values): - flask.g.lang_code = values.pop('lang_code', None) - - @app.route('//') - def index(): - return flask.url_for('about') - - @app.route('//about') - def about(): - return flask.url_for('something_else') - - @app.route('/foo') - def something_else(): - return flask.url_for('about', lang_code='en') - - c = app.test_client() - - self.assertEqual(c.get('/de/').data, '/de/about') - self.assertEqual(c.get('/de/about').data, '/foo') - self.assertEqual(c.get('/foo').data, '/en/about') - - def test_debug_mode_complains_after_first_request(self): - app = flask.Flask(__name__) - app.debug = True - @app.route('/') - def index(): - return 'Awesome' - self.assert_(not app.got_first_request) - self.assertEqual(app.test_client().get('/').data, 'Awesome') - try: - @app.route('/foo') - def broken(): - return 'Meh' - except AssertionError, e: - self.assert_('A setup function was called' in str(e)) - else: - self.fail('Expected exception') - - app.debug = False - @app.route('/foo') - def working(): - return 'Meh' - self.assertEqual(app.test_client().get('/foo').data, 'Meh') - self.assert_(app.got_first_request) - - def test_before_first_request_functions(self): - got = [] - app = flask.Flask(__name__) - @app.before_first_request - def foo(): - got.append(42) - c = app.test_client() - c.get('/') - self.assertEqual(got, [42]) - c.get('/') - self.assertEqual(got, [42]) - self.assert_(app.got_first_request) - - def test_routing_redirect_debugging(self): - app = flask.Flask(__name__) - app.debug = True - @app.route('/foo/', methods=['GET', 'POST']) - def foo(): - return 'success' - with app.test_client() as c: - try: - c.post('/foo', data={}) - except AssertionError, e: - self.assert_('http://localhost/foo/' in str(e)) - self.assert_('Make sure to directly send your POST-request ' - 'to this URL' in str(e)) - else: - self.fail('Expected exception') - - rv = c.get('/foo', data={}, follow_redirects=True) - self.assertEqual(rv.data, 'success') - - app.debug = False - with app.test_client() as c: - rv = c.post('/foo', data={}, follow_redirects=True) - self.assertEqual(rv.data, 'success') - - -class TestToolsTestCase(FlaskTestCase): - - def test_environ_defaults_from_config(self): - app = flask.Flask(__name__) - app.testing = True - app.config['SERVER_NAME'] = 'example.com:1234' - app.config['APPLICATION_ROOT'] = '/foo' - @app.route('/') - def index(): - return flask.request.url - - ctx = app.test_request_context() - self.assertEqual(ctx.request.url, 'http://example.com:1234/foo/') - with app.test_client() as c: - rv = c.get('/') - self.assertEqual(rv.data, 'http://example.com:1234/foo/') - - def test_environ_defaults(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/') - def index(): - return flask.request.url - - ctx = app.test_request_context() - self.assertEqual(ctx.request.url, 'http://localhost/') - with app.test_client() as c: - rv = c.get('/') - self.assertEqual(rv.data, 'http://localhost/') - - def test_session_transactions(self): - app = flask.Flask(__name__) - app.testing = True - app.secret_key = 'testing' - - @app.route('/') - def index(): - return unicode(flask.session['foo']) - - with app.test_client() as c: - with c.session_transaction() as sess: - self.assertEqual(len(sess), 0) - sess['foo'] = [42] - self.assertEqual(len(sess), 1) - rv = c.get('/') - self.assertEqual(rv.data, '[42]') - - def test_session_transactions_no_null_sessions(self): - app = flask.Flask(__name__) - app.testing = True - - with app.test_client() as c: - try: - with c.session_transaction() as sess: - pass - except RuntimeError, e: - self.assert_('Session backend did not open a session' in str(e)) - else: - self.fail('Expected runtime error') - - def test_session_transactions_keep_context(self): - app = flask.Flask(__name__) - app.testing = True - app.secret_key = 'testing' - - with app.test_client() as c: - rv = c.get('/') - req = flask.request._get_current_object() - with c.session_transaction(): - self.assert_(req is flask.request._get_current_object()) - - def test_test_client_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - flask.g.value = 42 - return 'Hello World!' - - @app.route('/other') - def other(): - 1/0 - - with app.test_client() as c: - resp = c.get('/') - assert flask.g.value == 42 - assert resp.data == 'Hello World!' - assert resp.status_code == 200 - - resp = c.get('/other') - assert not hasattr(flask.g, 'value') - assert 'Internal Server Error' in resp.data - assert resp.status_code == 500 - flask.g.value = 23 - - try: - flask.g.value - except (AttributeError, RuntimeError): - pass - else: - raise AssertionError('some kind of exception expected') - - -class InstanceTestCase(FlaskTestCase): - - def test_explicit_instance_paths(self): - here = os.path.abspath(os.path.dirname(__file__)) - try: - flask.Flask(__name__, instance_path='instance') - except ValueError, e: - self.assert_('must be absolute' in str(e)) - else: - self.fail('Expected value error') - - app = flask.Flask(__name__, instance_path=here) - self.assertEqual(app.instance_path, here) - - def test_uninstalled_module_paths(self): - here = os.path.abspath(os.path.dirname(__file__)) - app = flask.Flask(__name__) - self.assertEqual(app.instance_path, os.path.join(here, 'instance')) - - def test_uninstalled_package_paths(self): - from blueprintapp import app - here = os.path.abspath(os.path.dirname(__file__)) - self.assertEqual(app.instance_path, os.path.join(here, 'instance')) - - def test_installed_module_paths(self): - import types - expected_prefix = os.path.abspath('foo') - mod = types.ModuleType('myapp') - mod.__file__ = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'myapp.py') - sys.modules['myapp'] = mod - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - def test_installed_package_paths(self): - import types - expected_prefix = os.path.abspath('foo') - package_path = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'myapp') - mod = types.ModuleType('myapp') - mod.__path__ = [package_path] - mod.__file__ = os.path.join(package_path, '__init__.py') - sys.modules['myapp'] = mod - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - def test_prefix_installed_paths(self): - import types - expected_prefix = os.path.abspath(sys.prefix) - package_path = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'myapp') - mod = types.ModuleType('myapp') - mod.__path__ = [package_path] - mod.__file__ = os.path.join(package_path, '__init__.py') - sys.modules['myapp'] = mod - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - def test_egg_installed_paths(self): - import types - expected_prefix = os.path.abspath(sys.prefix) - package_path = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'MyApp.egg', 'myapp') - mod = types.ModuleType('myapp') - mod.__path__ = [package_path] - mod.__file__ = os.path.join(package_path, '__init__.py') - sys.modules['myapp'] = mod - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - -class JSONTestCase(FlaskTestCase): - - def test_json_bad_requests(self): - app = flask.Flask(__name__) - @app.route('/json', methods=['POST']) - def return_json(): - return unicode(flask.request.json) - c = app.test_client() - rv = c.post('/json', data='malformed', content_type='application/json') - self.assertEqual(rv.status_code, 400) - - def test_json_body_encoding(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/') - def index(): - return flask.request.json - - c = app.test_client() - resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), - content_type='application/json; charset=iso-8859-15') - assert resp.data == u'Hällo Wörld'.encode('utf-8') - - def test_jsonify(self): - d = dict(a=23, b=42, c=[1, 2, 3]) - app = flask.Flask(__name__) - @app.route('/kw') - def return_kwargs(): - return flask.jsonify(**d) - @app.route('/dict') - def return_dict(): - return flask.jsonify(d) - c = app.test_client() - for url in '/kw', '/dict': - rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data) == d - - def test_json_attr(self): - app = flask.Flask(__name__) - @app.route('/add', methods=['POST']) - def add(): - return unicode(flask.request.json['a'] + flask.request.json['b']) - c = app.test_client() - rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), - content_type='application/json') - assert rv.data == '3' - - def test_template_escaping(self): - app = flask.Flask(__name__) - render = flask.render_template_string - with app.test_request_context(): - rv = render('{{ ""|tojson|safe }}') - assert rv == '"<\\/script>"' - rv = render('{{ "<\0/script>"|tojson|safe }}') - assert rv == '"<\\u0000\\/script>"' - - def test_modified_url_encoding(self): - class ModifiedRequest(flask.Request): - url_charset = 'euc-kr' - app = flask.Flask(__name__) - app.request_class = ModifiedRequest - app.url_map.charset = 'euc-kr' - - @app.route('/') - def index(): - return flask.request.args['foo'] - - rv = app.test_client().get(u'/?foo=정상처리'.encode('euc-kr')) - assert rv.status_code == 200 - assert rv.data == u'정상처리'.encode('utf-8') - - if not has_encoding('euc-kr'): - test_modified_url_encoding = None - - -class TemplatingTestCase(FlaskTestCase): - - def test_context_processing(self): - app = flask.Flask(__name__) - @app.context_processor - def context_processor(): - return {'injected_value': 42} - @app.route('/') - def index(): - return flask.render_template('context_template.html', value=23) - rv = app.test_client().get('/') - assert rv.data == '

23|42' - - def test_original_win(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return flask.render_template_string('{{ config }}', config=42) - rv = app.test_client().get('/') - assert rv.data == '42' - - def test_standard_context(self): - app = flask.Flask(__name__) - app.secret_key = 'development key' - @app.route('/') - def index(): - flask.g.foo = 23 - flask.session['test'] = 'aha' - return flask.render_template_string(''' - {{ request.args.foo }} - {{ g.foo }} - {{ config.DEBUG }} - {{ session.test }} - ''') - rv = app.test_client().get('/?foo=42') - assert rv.data.split() == ['42', '23', 'False', 'aha'] - - def test_escaping(self): - text = '

Hello World!' - app = flask.Flask(__name__) - @app.route('/') - def index(): - return flask.render_template('escaping_template.html', text=text, - html=flask.Markup(text)) - lines = app.test_client().get('/').data.splitlines() - assert lines == [ - '<p>Hello World!', - '

Hello World!', - '

Hello World!', - '

Hello World!', - '<p>Hello World!', - '

Hello World!' - ] - - def test_no_escaping(self): - app = flask.Flask(__name__) - with app.test_request_context(): - assert flask.render_template_string('{{ foo }}', - foo='') == '' - assert flask.render_template('mail.txt', foo='') \ - == ' Mail' - - def test_macros(self): - app = flask.Flask(__name__) - with app.test_request_context(): - macro = flask.get_template_attribute('_macro.html', 'hello') - assert macro('World') == 'Hello World!' - - def test_template_filter(self): - app = flask.Flask(__name__) - @app.template_filter() - def my_reverse(s): - return s[::-1] - assert 'my_reverse' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['my_reverse'] == my_reverse - assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' - - def test_template_filter_with_name(self): - app = flask.Flask(__name__) - @app.template_filter('strrev') - def my_reverse(s): - return s[::-1] - assert 'strrev' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['strrev'] == my_reverse - assert app.jinja_env.filters['strrev']('abcd') == 'dcba' - - def test_template_filter_with_template(self): - app = flask.Flask(__name__) - @app.template_filter() - def super_reverse(s): - return s[::-1] - @app.route('/') - def index(): - return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') - assert rv.data == 'dcba' - - def test_template_filter_with_name_and_template(self): - app = flask.Flask(__name__) - @app.template_filter('super_reverse') - def my_reverse(s): - return s[::-1] - @app.route('/') - def index(): - return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') - assert rv.data == 'dcba' - - def test_custom_template_loader(self): - class MyFlask(flask.Flask): - def create_global_jinja_loader(self): - from jinja2 import DictLoader - return DictLoader({'index.html': 'Hello Custom World!'}) - app = MyFlask(__name__) - @app.route('/') - def index(): - return flask.render_template('index.html') - c = app.test_client() - rv = c.get('/') - assert rv.data == 'Hello Custom World!' - - -class ModuleTestCase(FlaskTestCase): - - @emits_module_deprecation_warning - def test_basic_module(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin', url_prefix='/admin') - @admin.route('/') - def admin_index(): - return 'admin index' - @admin.route('/login') - def admin_login(): - return 'admin login' - @admin.route('/logout') - def admin_logout(): - return 'admin logout' - @app.route('/') - def index(): - return 'the index' - app.register_module(admin) - c = app.test_client() - assert c.get('/').data == 'the index' - assert c.get('/admin/').data == 'admin index' - assert c.get('/admin/login').data == 'admin login' - assert c.get('/admin/logout').data == 'admin logout' - - @emits_module_deprecation_warning - def test_default_endpoint_name(self): - app = flask.Flask(__name__) - mod = flask.Module(__name__, 'frontend') - def index(): - return 'Awesome' - mod.add_url_rule('/', view_func=index) - app.register_module(mod) - rv = app.test_client().get('/') - assert rv.data == 'Awesome' - with app.test_request_context(): - assert flask.url_for('frontend.index') == '/' - - @emits_module_deprecation_warning - def test_request_processing(self): - catched = [] - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin', url_prefix='/admin') - @admin.before_request - def before_admin_request(): - catched.append('before-admin') - @admin.after_request - def after_admin_request(response): - catched.append('after-admin') - return response - @admin.route('/') - def admin_index(): - return 'the admin' - @app.before_request - def before_request(): - catched.append('before-app') - @app.after_request - def after_request(response): - catched.append('after-app') - return response - @app.route('/') - def index(): - return 'the index' - app.register_module(admin) - c = app.test_client() - - assert c.get('/').data == 'the index' - assert catched == ['before-app', 'after-app'] - del catched[:] - - assert c.get('/admin/').data == 'the admin' - assert catched == ['before-app', 'before-admin', - 'after-admin', 'after-app'] - - @emits_module_deprecation_warning - def test_context_processors(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin', url_prefix='/admin') - @app.context_processor - def inject_all_regualr(): - return {'a': 1} - @admin.context_processor - def inject_admin(): - return {'b': 2} - @admin.app_context_processor - def inject_all_module(): - return {'c': 3} - @app.route('/') - def index(): - return flask.render_template_string('{{ a }}{{ b }}{{ c }}') - @admin.route('/') - def admin_index(): - return flask.render_template_string('{{ a }}{{ b }}{{ c }}') - app.register_module(admin) - c = app.test_client() - assert c.get('/').data == '13' - assert c.get('/admin/').data == '123' - - @emits_module_deprecation_warning - def test_late_binding(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin') - @admin.route('/') - def index(): - return '42' - app.register_module(admin, url_prefix='/admin') - assert app.test_client().get('/admin/').data == '42' - - @emits_module_deprecation_warning - def test_error_handling(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin') - @admin.app_errorhandler(404) - def not_found(e): - return 'not found', 404 - @admin.app_errorhandler(500) - def internal_server_error(e): - return 'internal server error', 500 - @admin.route('/') - def index(): - flask.abort(404) - @admin.route('/error') - def error(): - 1 // 0 - app.register_module(admin) - c = app.test_client() - rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' - rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data - - def test_templates_and_static(self): - app = moduleapp - app.testing = True - c = app.test_client() - - rv = c.get('/') - assert rv.data == 'Hello from the Frontend' - rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' - rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' - - with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' - - with app.test_request_context(): - try: - flask.render_template('missing.html') - except TemplateNotFound, e: - assert e.name == 'missing.html' - else: - assert 0, 'expected exception' - - with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' - - def test_safe_access(self): - app = moduleapp - - with app.test_request_context(): - f = app.view_functions['admin.static'] - - try: - f('/etc/passwd') - except NotFound: - pass - else: - assert 0, 'expected exception' - try: - f('../__init__.py') - except NotFound: - pass - else: - assert 0, 'expected exception' - - # testcase for a security issue that may exist on windows systems - import os - import ntpath - old_path = os.path - os.path = ntpath - try: - try: - f('..\\__init__.py') - except NotFound: - pass - else: - assert 0, 'expected exception' - finally: - os.path = old_path - - @emits_module_deprecation_warning - def test_endpoint_decorator(self): - from werkzeug.routing import Submount, Rule - from flask import Module - - app = flask.Flask(__name__) - app.testing = True - app.url_map.add(Submount('/foo', [ - Rule('/bar', endpoint='bar'), - Rule('/', endpoint='index') - ])) - module = Module(__name__, __name__) - - @module.endpoint('bar') - def bar(): - return 'bar' - - @module.endpoint('index') - def index(): - return 'index' - - app.register_module(module) - - c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' - - -class BlueprintTestCase(FlaskTestCase): - - def test_blueprint_specific_error_handling(self): - frontend = flask.Blueprint('frontend', __name__) - backend = flask.Blueprint('backend', __name__) - sideend = flask.Blueprint('sideend', __name__) - - @frontend.errorhandler(403) - def frontend_forbidden(e): - return 'frontend says no', 403 - - @frontend.route('/frontend-no') - def frontend_no(): - flask.abort(403) - - @backend.errorhandler(403) - def backend_forbidden(e): - return 'backend says no', 403 - - @backend.route('/backend-no') - def backend_no(): - flask.abort(403) - - @sideend.route('/what-is-a-sideend') - def sideend_no(): - flask.abort(403) - - app = flask.Flask(__name__) - app.register_blueprint(frontend) - app.register_blueprint(backend) - app.register_blueprint(sideend) - - @app.errorhandler(403) - def app_forbidden(e): - return 'application itself says no', 403 - - c = app.test_client() - - assert c.get('/frontend-no').data == 'frontend says no' - assert c.get('/backend-no').data == 'backend says no' - assert c.get('/what-is-a-sideend').data == 'application itself says no' - - def test_blueprint_url_definitions(self): - bp = flask.Blueprint('test', __name__) - - @bp.route('/foo', defaults={'baz': 42}) - def foo(bar, baz): - return '%s/%d' % (bar, baz) - - @bp.route('/bar') - def bar(bar): - return unicode(bar) - - app = flask.Flask(__name__) - app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) - app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) - - c = app.test_client() - self.assertEqual(c.get('/1/foo').data, u'23/42') - self.assertEqual(c.get('/2/foo').data, u'19/42') - self.assertEqual(c.get('/1/bar').data, u'23') - self.assertEqual(c.get('/2/bar').data, u'19') - - def test_blueprint_url_processors(self): - bp = flask.Blueprint('frontend', __name__, url_prefix='/') - - @bp.url_defaults - def add_language_code(endpoint, values): - values.setdefault('lang_code', flask.g.lang_code) - - @bp.url_value_preprocessor - def pull_lang_code(endpoint, values): - flask.g.lang_code = values.pop('lang_code') - - @bp.route('/') - def index(): - return flask.url_for('.about') - - @bp.route('/about') - def about(): - return flask.url_for('.index') - - app = flask.Flask(__name__) - app.register_blueprint(bp) - - c = app.test_client() - - self.assertEqual(c.get('/de/').data, '/de/about') - self.assertEqual(c.get('/de/about').data, '/de/') - - def test_templates_and_static(self): - from blueprintapp import app - c = app.test_client() - - rv = c.get('/') - assert rv.data == 'Hello from the Frontend' - rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' - rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' - - with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' - - with app.test_request_context(): - try: - flask.render_template('missing.html') - except TemplateNotFound, e: - assert e.name == 'missing.html' - else: - assert 0, 'expected exception' - - with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' - - def test_templates_list(self): - from blueprintapp import app - templates = sorted(app.jinja_env.list_templates()) - self.assertEqual(templates, ['admin/index.html', - 'frontend/index.html']) - - def test_dotted_names(self): - frontend = flask.Blueprint('myapp.frontend', __name__) - backend = flask.Blueprint('myapp.backend', __name__) - - @frontend.route('/fe') - def frontend_index(): - return flask.url_for('myapp.backend.backend_index') - - @frontend.route('/fe2') - def frontend_page2(): - return flask.url_for('.frontend_index') - - @backend.route('/be') - def backend_index(): - return flask.url_for('myapp.frontend.frontend_index') - - app = flask.Flask(__name__) - app.register_blueprint(frontend) - app.register_blueprint(backend) - - c = app.test_client() - self.assertEqual(c.get('/fe').data.strip(), '/be') - self.assertEqual(c.get('/fe2').data.strip(), '/fe') - self.assertEqual(c.get('/be').data.strip(), '/fe') - - def test_empty_url_defaults(self): - bp = flask.Blueprint('bp', __name__) - - @bp.route('/', defaults={'page': 1}) - @bp.route('/page/') - def something(page): - return str(page) - - app = flask.Flask(__name__) - app.register_blueprint(bp) - - c = app.test_client() - self.assertEqual(c.get('/').data, '1') - self.assertEqual(c.get('/page/2').data, '2') - - -class SendfileTestCase(FlaskTestCase): - - def test_send_file_regular(self): - app = flask.Flask(__name__) - with app.test_request_context(): - rv = flask.send_file('static/index.html') - assert rv.direct_passthrough - assert rv.mimetype == 'text/html' - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - - def test_send_file_xsendfile(self): - app = flask.Flask(__name__) - app.use_x_sendfile = True - with app.test_request_context(): - rv = flask.send_file('static/index.html') - assert rv.direct_passthrough - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - assert rv.mimetype == 'text/html' - - def test_send_file_object(self): - app = flask.Flask(__name__) - with catch_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' - # mimetypes + etag - assert len(captured) == 2 - - app.use_x_sendfile = True - with catch_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - # mimetypes + etag - assert len(captured) == 2 - - app.use_x_sendfile = False - with app.test_request_context(): - with catch_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f) - assert rv.data == 'Test' - assert rv.mimetype == 'application/octet-stream' - # etags - assert len(captured) == 1 - with catch_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f, mimetype='text/plain') - assert rv.data == 'Test' - assert rv.mimetype == 'text/plain' - # etags - assert len(captured) == 1 - - app.use_x_sendfile = True - with catch_warnings() as captured: - with app.test_request_context(): - f = StringIO('Test') - rv = flask.send_file(f) - assert 'x-sendfile' not in rv.headers - # etags - assert len(captured) == 1 - - def test_attachment(self): - app = flask.Flask(__name__) - with catch_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f, as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - # mimetypes + etag - assert len(captured) == 2 - - with app.test_request_context(): - assert options['filename'] == 'index.html' - rv = flask.send_file('static/index.html', as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.html' - - with app.test_request_context(): - rv = flask.send_file(StringIO('Test'), as_attachment=True, - attachment_filename='index.txt', - add_etags=False) - assert rv.mimetype == 'text/plain' - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.txt' - - -class LoggingTestCase(FlaskTestCase): - - def test_logger_cache(self): - app = flask.Flask(__name__) - logger1 = app.logger - assert app.logger is logger1 - assert logger1.name == __name__ - app.logger_name = __name__ + '/test_logger_cache' - assert app.logger is not logger1 - - def test_debug_log(self): - app = flask.Flask(__name__) - app.debug = True - - @app.route('/') - def index(): - app.logger.warning('the standard library is dead') - app.logger.debug('this is a debug statement') - return '' - - @app.route('/exc') - def exc(): - 1/0 - - with app.test_client() as c: - with catch_stderr() as err: - c.get('/') - out = err.getvalue() - assert 'WARNING in flask_tests [' in out - assert 'flask_tests.py' in out - assert 'the standard library is dead' in out - assert 'this is a debug statement' in out - - with catch_stderr() as err: - try: - c.get('/exc') - except ZeroDivisionError: - pass - else: - assert False, 'debug log ate the exception' - - def test_exception_logging(self): - out = StringIO() - app = flask.Flask(__name__) - app.logger_name = 'flask_tests/test_exception_logging' - app.logger.addHandler(StreamHandler(out)) - - @app.route('/') - def index(): - 1/0 - - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert 'Internal Server Error' in rv.data - - err = out.getvalue() - assert 'Exception on / [GET]' in err - assert 'Traceback (most recent call last):' in err - assert '1/0' in err - assert 'ZeroDivisionError:' in err - - def test_processor_exceptions(self): - app = flask.Flask(__name__) - @app.before_request - def before_request(): - if trigger == 'before': - 1/0 - @app.after_request - def after_request(response): - if trigger == 'after': - 1/0 - return response - @app.route('/') - def index(): - return 'Foo' - @app.errorhandler(500) - def internal_server_error(e): - return 'Hello Server Error', 500 - for trigger in 'before', 'after': - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert rv.data == 'Hello Server Error' - - -class ConfigTestCase(FlaskTestCase): - - def common_object_test(self, app): - assert app.secret_key == 'devkey' - assert app.config['TEST_KEY'] == 'foo' - assert 'ConfigTestCase' not in app.config - - def test_config_from_file(self): - app = flask.Flask(__name__) - app.config.from_pyfile('flask_tests.py') - self.common_object_test(app) - - def test_config_from_object(self): - app = flask.Flask(__name__) - app.config.from_object(__name__) - self.common_object_test(app) - - def test_config_from_class(self): - class Base(object): - TEST_KEY = 'foo' - class Test(Base): - SECRET_KEY = 'devkey' - app = flask.Flask(__name__) - app.config.from_object(Test) - self.common_object_test(app) - - def test_config_from_envvar(self): - import os - env = os.environ - try: - os.environ = {} - app = flask.Flask(__name__) - try: - app.config.from_envvar('FOO_SETTINGS') - except RuntimeError, e: - assert "'FOO_SETTINGS' is not set" in str(e) - else: - assert 0, 'expected exception' - assert not app.config.from_envvar('FOO_SETTINGS', silent=True) - - os.environ = {'FOO_SETTINGS': 'flask_tests.py'} - assert app.config.from_envvar('FOO_SETTINGS') - self.common_object_test(app) - finally: - os.environ = env - - def test_config_missing(self): - app = flask.Flask(__name__) - try: - app.config.from_pyfile('missing.cfg') - except IOError, e: - msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.cfg'") - else: - assert 0, 'expected config' - assert not app.config.from_pyfile('missing.cfg', silent=True) - - -class SubdomainTestCase(FlaskTestCase): - - def test_basic_support(self): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' - @app.route('/') - def normal_index(): - return 'normal index' - @app.route('/', subdomain='test') - def test_index(): - return 'test index' - - c = app.test_client() - rv = c.get('/', 'http://localhost/') - assert rv.data == 'normal index' - - rv = c.get('/', 'http://test.localhost/') - assert rv.data == 'test index' - - @emits_module_deprecation_warning - def test_module_static_path_subdomain(self): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'example.com' - from subdomaintestmodule import mod - app.register_module(mod) - c = app.test_client() - rv = c.get('/static/hello.txt', 'http://foo.example.com/') - assert rv.data.strip() == 'Hello Subdomain' - - def test_subdomain_matching(self): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' - @app.route('/', subdomain='') - def index(user): - return 'index for %s' % user - - c = app.test_client() - rv = c.get('/', 'http://mitsuhiko.localhost/') - assert rv.data == 'index for mitsuhiko' - - @emits_module_deprecation_warning - def test_module_subdomain_support(self): - app = flask.Flask(__name__) - mod = flask.Module(__name__, 'test', subdomain='testing') - app.config['SERVER_NAME'] = 'localhost' - - @mod.route('/test') - def test(): - return 'Test' - - @mod.route('/outside', subdomain='xtesting') - def bar(): - return 'Outside' - - app.register_module(mod) - - c = app.test_client() - rv = c.get('/test', 'http://testing.localhost/') - assert rv.data == 'Test' - rv = c.get('/outside', 'http://xtesting.localhost/') - assert rv.data == 'Outside' - - -class TestSignals(FlaskTestCase): - - def test_template_rendered(self): - app = flask.Flask(__name__) - - @app.route('/') - def index(): - return flask.render_template('simple_template.html', whiskey=42) - - recorded = [] - def record(sender, template, context): - recorded.append((template, context)) - - flask.template_rendered.connect(record, app) - try: - app.test_client().get('/') - assert len(recorded) == 1 - template, context = recorded[0] - assert template.name == 'simple_template.html' - assert context['whiskey'] == 42 - finally: - flask.template_rendered.disconnect(record, app) - - def test_request_signals(self): - app = flask.Flask(__name__) - calls = [] - - def before_request_signal(sender): - calls.append('before-signal') - - def after_request_signal(sender, response): - assert response.data == 'stuff' - calls.append('after-signal') - - @app.before_request - def before_request_handler(): - calls.append('before-handler') - - @app.after_request - def after_request_handler(response): - calls.append('after-handler') - response.data = 'stuff' - return response - - @app.route('/') - def index(): - calls.append('handler') - return 'ignored anyway' - - flask.request_started.connect(before_request_signal, app) - flask.request_finished.connect(after_request_signal, app) - - try: - rv = app.test_client().get('/') - assert rv.data == 'stuff' - - assert calls == ['before-signal', 'before-handler', - 'handler', 'after-handler', - 'after-signal'] - finally: - flask.request_started.disconnect(before_request_signal, app) - flask.request_finished.disconnect(after_request_signal, app) - - def test_request_exception_signal(self): - app = flask.Flask(__name__) - recorded = [] - - @app.route('/') - def index(): - 1/0 - - def record(sender, exception): - recorded.append(exception) - - flask.got_request_exception.connect(record, app) - try: - assert app.test_client().get('/').status_code == 500 - assert len(recorded) == 1 - assert isinstance(recorded[0], ZeroDivisionError) - finally: - flask.got_request_exception.disconnect(record, app) - - -class ViewTestCase(FlaskTestCase): - - def common_test(self, app): - c = app.test_client() - - self.assertEqual(c.get('/').data, 'GET') - self.assertEqual(c.post('/').data, 'POST') - self.assertEqual(c.put('/').status_code, 405) - meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) - self.assertEqual(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST']) - - def test_basic_view(self): - app = flask.Flask(__name__) - - class Index(flask.views.View): - methods = ['GET', 'POST'] - def dispatch_request(self): - return flask.request.method - - app.add_url_rule('/', view_func=Index.as_view('index')) - self.common_test(app) - - def test_method_based_view(self): - app = flask.Flask(__name__) - - class Index(flask.views.MethodView): - def get(self): - return 'GET' - def post(self): - return 'POST' - - app.add_url_rule('/', view_func=Index.as_view('index')) - - self.common_test(app) - - def test_view_patching(self): - app = flask.Flask(__name__) - - class Index(flask.views.MethodView): - def get(self): - 1/0 - def post(self): - 1/0 - - class Other(Index): - def get(self): - return 'GET' - def post(self): - return 'POST' - - view = Index.as_view('index') - view.view_class = Other - app.add_url_rule('/', view_func=view) - self.common_test(app) - - def test_view_inheritance(self): - app = flask.Flask(__name__) - - class Index(flask.views.MethodView): - def get(self): - return 'GET' - def post(self): - return 'POST' - - class BetterIndex(Index): - def delete(self): - return 'DELETE' - - app.add_url_rule('/', view_func=BetterIndex.as_view('index')) - c = app.test_client() - - meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) - self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) - - def test_view_decorators(self): - app = flask.Flask(__name__) - - def add_x_parachute(f): - def new_function(*args, **kwargs): - resp = flask.make_response(f(*args, **kwargs)) - resp.headers['X-Parachute'] = 'awesome' - return resp - return new_function - - class Index(flask.views.View): - decorators = [add_x_parachute] - def dispatch_request(self): - return 'Awesome' - - app.add_url_rule('/', view_func=Index.as_view('index')) - c = app.test_client() - rv = c.get('/') - self.assertEqual(rv.headers['X-Parachute'], 'awesome') - self.assertEqual(rv.data, 'Awesome') - - -class DeprecationsTestCase(FlaskTestCase): - - def test_init_jinja_globals(self): - class MyFlask(flask.Flask): - def init_jinja_globals(self): - self.jinja_env.globals['foo'] = '42' - - with catch_warnings() as log: - app = MyFlask(__name__) - @app.route('/') - def foo(): - return app.jinja_env.globals['foo'] - - c = app.test_client() - assert c.get('/').data == '42' - assert len(log) == 1 - assert 'init_jinja_globals' in str(log[0]['message']) - - -def suite(): - from minitwit_tests import MiniTwitTestCase - from flaskr_tests import FlaskrTestCase - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(ContextTestCase)) - suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase)) - suite.addTest(unittest.makeSuite(TemplatingTestCase)) - suite.addTest(unittest.makeSuite(ModuleTestCase)) - suite.addTest(unittest.makeSuite(BlueprintTestCase)) - suite.addTest(unittest.makeSuite(SendfileTestCase)) - suite.addTest(unittest.makeSuite(LoggingTestCase)) - suite.addTest(unittest.makeSuite(ConfigTestCase)) - suite.addTest(unittest.makeSuite(SubdomainTestCase)) - suite.addTest(unittest.makeSuite(ViewTestCase)) - suite.addTest(unittest.makeSuite(DeprecationsTestCase)) - suite.addTest(unittest.makeSuite(TestToolsTestCase)) - suite.addTest(unittest.makeSuite(InstanceTestCase)) - if flask.json_available: - suite.addTest(unittest.makeSuite(JSONTestCase)) - if flask.signals_available: - suite.addTest(unittest.makeSuite(TestSignals)) - suite.addTest(unittest.makeSuite(MiniTwitTestCase)) - suite.addTest(unittest.makeSuite(FlaskrTestCase)) - return suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') From 12d74be7d7f62400663911d94783320bdfba86d4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:24:10 +0100 Subject: [PATCH 17/43] Added better test runner --- run-tests.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 run-tests.py diff --git a/run-tests.py b/run-tests.py new file mode 100644 index 00000000..96db7f9a --- /dev/null +++ b/run-tests.py @@ -0,0 +1,57 @@ +import sys +import unittest +from unittest.loader import TestLoader +from flask.testsuite import suite + +common_prefix = suite.__module__ + '.' + + +def find_all_tests(): + suites = [suite()] + while suites: + s = suites.pop() + try: + suites.extend(s) + except TypeError: + yield s + + +def find_all_tests_with_name(): + for testcase in find_all_tests(): + yield testcase, '%s.%s.%s' % ( + testcase.__class__.__module__, + testcase.__class__.__name__, + testcase._testMethodName + ) + + +class BetterLoader(TestLoader): + + def loadTestsFromName(self, name, module=None): + if name == 'suite': + return suite() + for testcase, testname in find_all_tests_with_name(): + if testname == name: + return testcase + if testname.startswith(common_prefix): + if testname[len(common_prefix):] == name: + return testcase + + all_results = [] + for testcase, testname in find_all_tests_with_name(): + if testname.endswith('.' + name): + all_results.append((testcase, testname)) + + if len(all_results) == 1: + return all_results[0][0] + elif not len(all_results): + error = 'could not find testcase "%s"' % name + else: + error = 'Too many matches: for "%s"\n%s' % \ + (name, '\n'.join(' - ' + n for c, n in all_results)) + + print >> sys.stderr, 'Error: %s' % error + sys.exit(1) + + +unittest.main(testLoader=BetterLoader(), defaultTest='suite') From 2b830af2efb4523fa2e2e9b9dfcc33212effe2e6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:24:42 +0100 Subject: [PATCH 18/43] Use the better test runner in the makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7b14422f..43f47275 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: clean-pyc test test: - python setup.py test + python run-tests.py audit: python setup.py audit From 3069e2d7f73f80ebc342029616a5cbc5381cfdf1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:38:43 +0100 Subject: [PATCH 19/43] Fight the generic asserts! --- flask/testsuite/basic.py | 212 ++++++++++++++++---------------- flask/testsuite/blueprints.py | 74 +++++------ flask/testsuite/config.py | 4 +- flask/testsuite/deprecations.py | 4 +- flask/testsuite/helpers.py | 78 ++++++------ flask/testsuite/signals.py | 18 +-- flask/testsuite/templating.py | 26 ++-- flask/testsuite/testing.py | 8 +- 8 files changed, 212 insertions(+), 212 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index c55881e5..8c2be901 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -26,8 +26,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(): return 'Hello World' rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - assert rv.data == '' + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) + self.assert_equal(rv.data, '') def test_options_on_multiple_rules(self): app = flask.Flask(__name__) @@ -38,7 +38,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index_put(): return 'Aha!' rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']) def test_options_handling_disabled(self): app = flask.Flask(__name__) @@ -47,7 +47,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): index.provide_automatic_options = False app.route('/')(index) rv = app.test_client().open('/', method='OPTIONS') - assert rv.status_code == 405 + self.assert_equal(rv.status_code, 405) app = flask.Flask(__name__) def index2(): @@ -55,7 +55,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): index2.provide_automatic_options = True app.route('/', methods=['OPTIONS'])(index2) rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['OPTIONS'] + self.assert_equal(sorted(rv.allow), ['OPTIONS']) def test_request_dispatching(self): app = flask.Flask(__name__) @@ -67,18 +67,18 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.request.method c = app.test_client() - assert c.get('/').data == 'GET' + self.assert_equal(c.get('/').data, 'GET') rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' + self.assert_equal(c.post('/more').data, 'POST') + self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) def test_url_mapping(self): app = flask.Flask(__name__) @@ -91,18 +91,18 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) c = app.test_client() - assert c.get('/').data == 'GET' + self.assert_equal(c.get('/').data, 'GET') rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' + self.assert_equal(c.post('/more').data, 'POST') + self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) def test_werkzeug_routing(self): from werkzeug.routing import Submount, Rule @@ -119,8 +119,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.view_functions['index'] = index c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' + self.assert_equal(c.get('/foo/').data, 'index') + self.assert_equal(c.get('/foo/bar').data, 'bar') def test_endpoint_decorator(self): from werkzeug.routing import Submount, Rule @@ -139,8 +139,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'index' c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' + self.assert_equal(c.get('/foo/').data, 'index') + self.assert_equal(c.get('/foo/bar').data, 'bar') def test_session(self): app = flask.Flask(__name__) @@ -154,8 +154,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.session['value'] c = app.test_client() - assert c.post('/set', data={'value': '42'}).data == 'value set' - assert c.get('/get').data == '42' + self.assert_equal(c.post('/set', data={'value': '42'}).data, 'value set') + self.assert_equal(c.get('/get').data, '42') def test_session_using_server_name(self): app = flask.Flask(__name__) @@ -241,12 +241,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime - assert expires.year == expected.year - assert expires.month == expected.month - assert expires.day == expected.day + self.assert_equal(expires.year, expected.year) + self.assert_equal(expires.month, expected.month) + self.assert_equal(expires.day, expected.day) rv = client.get('/test') - assert rv.data == 'True' + self.assert_equal(rv.data, 'True') permanent = False rv = app.test_client().get('/') @@ -264,7 +264,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session.modified = False flask.flash('Zip') assert flask.session.modified - assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): app = flask.Flask(__name__) @@ -280,16 +280,16 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/test') def test(): messages = flask.get_flashed_messages(with_categories=True) - assert len(messages) == 3 - assert messages[0] == ('message', u'Hello World') - assert messages[1] == ('error', u'Hello World') - assert messages[2] == ('warning', flask.Markup(u'Testing')) + self.assert_equal(len(messages), 3) + self.assert_equal(messages[0], ('message', u'Hello World')) + self.assert_equal(messages[1], ('error', u'Hello World')) + self.assert_equal(messages[2], ('warning', flask.Markup(u'Testing'))) return '' messages = flask.get_flashed_messages() - assert len(messages) == 3 - assert messages[0] == u'Hello World' - assert messages[1] == u'Hello World' - assert messages[2] == flask.Markup(u'Testing') + self.assert_equal(len(messages), 3) + self.assert_equal(messages[0], u'Hello World') + self.assert_equal(messages[1], u'Hello World') + self.assert_equal(messages[2], flask.Markup(u'Testing')) c = app.test_client() c.get('/') @@ -314,7 +314,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): assert 'after' not in evts rv = app.test_client().get('/').data assert 'after' in evts - assert rv == 'request|after' + self.assert_equal(rv, 'request|after') def test_teardown_request_handler(self): called = [] @@ -327,9 +327,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): def root(): return "Response" rv = app.test_client().get('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert 'Response' in rv.data - assert len(called) == 1 + self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): called = [] @@ -343,16 +343,16 @@ class BasicFunctionalityTestCase(FlaskTestCase): def root(): return "Response" rv = app.test_client().get('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert 'Response' in rv.data - assert len(called) == 1 + self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): called = [] app = flask.Flask(__name__) @app.teardown_request def teardown_request1(exc): - assert type(exc) == ZeroDivisionError + self.assert_equal(type(exc), ZeroDivisionError) called.append(True) # This raises a new error and blows away sys.exc_info(), so we can # test that all teardown_requests get passed the same original @@ -363,7 +363,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): pass @app.teardown_request def teardown_request2(exc): - assert type(exc) == ZeroDivisionError + self.assert_equal(type(exc), ZeroDivisionError) called.append(True) # This raises a new error and blows away sys.exc_info(), so we can # test that all teardown_requests get passed the same original @@ -376,9 +376,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): def fails(): 1/0 rv = app.test_client().get('/') - assert rv.status_code == 500 + self.assert_equal(rv.status_code, 500) assert 'Internal Server Error' in rv.data - assert len(called) == 2 + self.assert_equal(len(called), 2) def test_before_after_request_order(self): called = [] @@ -407,8 +407,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(): return '42' rv = app.test_client().get('/') - assert rv.data == '42' - assert called == [1, 2, 3, 4, 5, 6] + self.assert_equal(rv.data, '42') + self.assert_equal(called, [1, 2, 3, 4, 5, 6]) def test_error_handling(self): app = flask.Flask(__name__) @@ -426,11 +426,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1 // 0 c = app.test_client() rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'not found') rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data + self.assert_equal(rv.status_code, 500) + self.assert_equal('internal server error', rv.data) def test_before_request_and_routing_errors(self): app = flask.Flask(__name__) @@ -441,8 +441,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): def return_something(error): return flask.g.something, 404 rv = app.test_client().get('/') - assert rv.status_code == 404 - assert rv.data == 'value' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'value') def test_user_error_handling(self): class MyException(Exception): @@ -458,7 +458,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): raise MyException() c = app.test_client() - assert c.get('/').data == '42' + self.assert_equal(c.get('/').data, '42') def test_trapping_of_bad_request_key_errors(self): app = flask.Flask(__name__) @@ -467,7 +467,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def fail(): flask.request.form['missing_key'] c = app.test_client() - assert c.get('/fail').status_code == 400 + self.assert_equal(c.get('/fail').status_code, 400) app.config['TRAP_BAD_REQUEST_ERRORS'] = True c = app.test_client() @@ -523,9 +523,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): ctx = app.test_request_context() ctx.push() - assert buffer == [] + self.assert_equal(buffer, []) ctx.pop() - assert buffer == [None] + self.assert_equal(buffer, [None]) def test_response_creation(self): app = flask.Flask(__name__) @@ -539,31 +539,31 @@ class BasicFunctionalityTestCase(FlaskTestCase): def from_tuple(): return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' c = app.test_client() - assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') - assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') + self.assert_equal(c.get('/unicode').data, u'Hällo Wörld'.encode('utf-8')) + self.assert_equal(c.get('/string').data, u'Hällo Wörld'.encode('utf-8')) rv = c.get('/args') - assert rv.data == 'Meh' - assert rv.headers['X-Foo'] == 'Testing' - assert rv.status_code == 400 - assert rv.mimetype == 'text/plain' + self.assert_equal(rv.data, 'Meh') + self.assert_equal(rv.headers['X-Foo'], 'Testing') + self.assert_equal(rv.status_code, 400) + self.assert_equal(rv.mimetype, 'text/plain') def test_make_response(self): app = flask.Flask(__name__) with app.test_request_context(): rv = flask.make_response() - assert rv.status_code == 200 - assert rv.data == '' - assert rv.mimetype == 'text/html' + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, '') + self.assert_equal(rv.mimetype, 'text/html') rv = flask.make_response('Awesome') - assert rv.status_code == 200 - assert rv.data == 'Awesome' - assert rv.mimetype == 'text/html' + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, 'Awesome') + self.assert_equal(rv.mimetype, 'text/html') rv = flask.make_response('W00t', 404) - assert rv.status_code == 404 - assert rv.data == 'W00t' - assert rv.mimetype == 'text/html' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'W00t') + self.assert_equal(rv.mimetype, 'text/html') def test_url_generation(self): app = flask.Flask(__name__) @@ -571,7 +571,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def hello(): pass with app.test_request_context(): - assert flask.url_for('hello', name='test x') == '/hello/test%20x' + self.assert_equal(flask.url_for('hello', name='test x'), '/hello/test%20x') assert flask.url_for('hello', name='test x', _external=True) \ == 'http://localhost/hello/test%20x' @@ -589,13 +589,13 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(args): return '|'.join(args) c = app.test_client() - assert c.get('/1,2,3').data == '1|2|3' + self.assert_equal(c.get('/1,2,3').data, '1|2|3') def test_static_files(self): app = flask.Flask(__name__) rv = app.test_client().get('/static/index.html') - assert rv.status_code == 200 - assert rv.data.strip() == '

Hello World!

' + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data.strip(), '

Hello World!

') with app.test_request_context(): assert flask.url_for('static', filename='index.html') \ == '/static/index.html' @@ -608,7 +608,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: app.test_client().get('/') except ValueError, e: - assert str(e) == 'View function did not return a response' + self.assert_equal(str(e), 'View function did not return a response') pass else: assert "Expected ValueError" @@ -632,19 +632,19 @@ class BasicFunctionalityTestCase(FlaskTestCase): return None with app.test_request_context('/'): - assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' + self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/') with app.test_request_context('/'): - assert flask.url_for('sub', _external=True) == 'http://foo.localhost.localdomain:5000/' + self.assert_equal(flask.url_for('sub', _external=True), 'http://foo.localhost.localdomain:5000/') try: with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): pass except Exception, e: assert isinstance(e, ValueError) - assert str(e) == "the server name provided " + \ + self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:5000') does not match the " + \ - "server name from the WSGI environment ('localhost')", str(e) + "server name from the WSGI environment ('localhost')") try: app.config.update(SERVER_NAME='localhost') @@ -679,7 +679,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: rv = app.test_client().get('/') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -687,7 +687,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: rv = app.test_client().get('/', 'http://localhost.localdomain:5000') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -695,7 +695,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: rv = app.test_client().get('/', 'https://localhost.localdomain:5000') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -704,7 +704,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: app.config.update(SERVER_NAME='localhost.localdomain') rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -713,23 +713,23 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: app.config.update(SERVER_NAME='localhost.localdomain:443') rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: - assert str(e) == "the server name provided " + \ + self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:443') does not match the " + \ - "server name from the WSGI environment ('localhost.localdomain')", str(e) + "server name from the WSGI environment ('localhost.localdomain')") try: app.config.update(SERVER_NAME='localhost.localdomain') app.test_client().get('/', 'http://foo.localhost') except ValueError, e: - assert str(e) == "the server name provided " + \ + self.assert_equal(str(e), "the server name provided " + \ "('localhost.localdomain') does not match the " + \ - "server name from the WSGI environment ('foo.localhost')", str(e) + "server name from the WSGI environment ('foo.localhost')") try: rv = app.test_client().get('/', 'http://foo.localhost.localdomain') - assert rv.data == 'Foo SubDomain' + self.assert_equal(rv.data, 'Foo SubDomain') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -751,7 +751,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): else: self.fail('expected exception') else: - assert c.get('/').status_code == 500 + self.assert_equal(c.get('/').status_code, 500) # we have to run this test in an isolated thread because if the # debug flag is set to true and an exception happens the context is @@ -779,7 +779,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() rv = c.post('/accept', data={'myfile': 'foo' * 100}) - assert rv.data == '42' + self.assert_equal(rv.data, '42') def test_url_processors(self): app = flask.Flask(__name__) @@ -886,9 +886,9 @@ class ContextTestCase(FlaskTestCase): return flask.request.url with app.test_request_context('/?name=World'): - assert index() == 'Hello World!' + self.assert_equal(index(), 'Hello World!') with app.test_request_context('/meh'): - assert meh() == 'http://localhost/meh' + self.assert_equal(meh(), 'http://localhost/meh') assert flask._request_ctx_stack.top is None def test_context_test(self): @@ -911,7 +911,7 @@ class ContextTestCase(FlaskTestCase): ctx = app.test_request_context('/?name=World') ctx.push() - assert index() == 'Hello World!' + self.assert_equal(index(), 'Hello World!') ctx.pop() try: index() @@ -935,10 +935,10 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://localhost/') - assert rv.data == 'normal index' + self.assert_equal(rv.data, 'normal index') rv = c.get('/', 'http://test.localhost/') - assert rv.data == 'test index' + self.assert_equal(rv.data, 'test index') @emits_module_deprecation_warning def test_module_static_path_subdomain(self): @@ -948,7 +948,7 @@ class SubdomainTestCase(FlaskTestCase): app.register_module(mod) c = app.test_client() rv = c.get('/static/hello.txt', 'http://foo.example.com/') - assert rv.data.strip() == 'Hello Subdomain' + self.assert_equal(rv.data.strip(), 'Hello Subdomain') def test_subdomain_matching(self): app = flask.Flask(__name__) @@ -959,7 +959,7 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://mitsuhiko.localhost/') - assert rv.data == 'index for mitsuhiko' + self.assert_equal(rv.data, 'index for mitsuhiko') @emits_module_deprecation_warning def test_module_subdomain_support(self): @@ -979,9 +979,9 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/test', 'http://testing.localhost/') - assert rv.data == 'Test' + self.assert_equal(rv.data, 'Test') rv = c.get('/outside', 'http://xtesting.localhost/') - assert rv.data == 'Outside' + self.assert_equal(rv.data, 'Outside') def suite(): diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 93122ac5..fdd63fee 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -43,10 +43,10 @@ class ModuleTestCase(FlaskTestCase): return 'the index' app.register_module(admin) c = app.test_client() - assert c.get('/').data == 'the index' - assert c.get('/admin/').data == 'admin index' - assert c.get('/admin/login').data == 'admin login' - assert c.get('/admin/logout').data == 'admin logout' + self.assert_equal(c.get('/').data, 'the index') + self.assert_equal(c.get('/admin/').data, 'admin index') + self.assert_equal(c.get('/admin/login').data, 'admin login') + self.assert_equal(c.get('/admin/logout').data, 'admin logout') @emits_module_deprecation_warning def test_default_endpoint_name(self): @@ -57,9 +57,9 @@ class ModuleTestCase(FlaskTestCase): mod.add_url_rule('/', view_func=index) app.register_module(mod) rv = app.test_client().get('/') - assert rv.data == 'Awesome' + self.assert_equal(rv.data, 'Awesome') with app.test_request_context(): - assert flask.url_for('frontend.index') == '/' + self.assert_equal(flask.url_for('frontend.index'), '/') @emits_module_deprecation_warning def test_request_processing(self): @@ -89,13 +89,13 @@ class ModuleTestCase(FlaskTestCase): app.register_module(admin) c = app.test_client() - assert c.get('/').data == 'the index' - assert catched == ['before-app', 'after-app'] + self.assert_equal(c.get('/').data, 'the index') + self.assert_equal(catched, ['before-app', 'after-app']) del catched[:] - assert c.get('/admin/').data == 'the admin' - assert catched == ['before-app', 'before-admin', - 'after-admin', 'after-app'] + self.assert_equal(c.get('/admin/').data, 'the admin') + self.assert_equal(catched, ['before-app', 'before-admin', + 'after-admin', 'after-app']) @emits_module_deprecation_warning def test_context_processors(self): @@ -118,8 +118,8 @@ class ModuleTestCase(FlaskTestCase): return flask.render_template_string('{{ a }}{{ b }}{{ c }}') app.register_module(admin) c = app.test_client() - assert c.get('/').data == '13' - assert c.get('/admin/').data == '123' + self.assert_equal(c.get('/').data, '13') + self.assert_equal(c.get('/admin/').data, '123') @emits_module_deprecation_warning def test_late_binding(self): @@ -129,7 +129,7 @@ class ModuleTestCase(FlaskTestCase): def index(): return '42' app.register_module(admin, url_prefix='/admin') - assert app.test_client().get('/admin/').data == '42' + self.assert_equal(app.test_client().get('/admin/').data, '42') @emits_module_deprecation_warning def test_error_handling(self): @@ -150,11 +150,11 @@ class ModuleTestCase(FlaskTestCase): app.register_module(admin) c = app.test_client() rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'not found') rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data + self.assert_equal(rv.status_code, 500) + self.assert_equal('internal server error', rv.data) def test_templates_and_static(self): app = moduleapp @@ -162,15 +162,15 @@ class ModuleTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') - assert rv.data == 'Hello from the Frontend' + self.assert_equal(rv.data, 'Hello from the Frontend') rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' + self.assert_equal(rv.data.strip(), 'Admin File') rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' + self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): assert flask.url_for('admin.static', filename='test.txt') \ @@ -180,12 +180,12 @@ class ModuleTestCase(FlaskTestCase): try: flask.render_template('missing.html') except TemplateNotFound, e: - assert e.name == 'missing.html' + self.assert_equal(e.name, 'missing.html') else: assert 0, 'expected exception' with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' + self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') def test_safe_access(self): app = moduleapp @@ -245,8 +245,8 @@ class ModuleTestCase(FlaskTestCase): app.register_module(module) c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' + self.assert_equal(c.get('/foo/').data, 'index') + self.assert_equal(c.get('/foo/bar').data, 'bar') class BlueprintTestCase(FlaskTestCase): @@ -287,9 +287,9 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() - assert c.get('/frontend-no').data == 'frontend says no' - assert c.get('/backend-no').data == 'backend says no' - assert c.get('/what-is-a-sideend').data == 'application itself says no' + self.assert_equal(c.get('/frontend-no').data, 'frontend says no') + self.assert_equal(c.get('/backend-no').data, 'backend says no') + self.assert_equal(c.get('/what-is-a-sideend').data, 'application itself says no') def test_blueprint_url_definitions(self): bp = flask.Blueprint('test', __name__) @@ -344,15 +344,15 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') - assert rv.data == 'Hello from the Frontend' + self.assert_equal(rv.data, 'Hello from the Frontend') rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' + self.assert_equal(rv.data.strip(), 'Admin File') rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' + self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): assert flask.url_for('admin.static', filename='test.txt') \ @@ -362,12 +362,12 @@ class BlueprintTestCase(FlaskTestCase): try: flask.render_template('missing.html') except TemplateNotFound, e: - assert e.name == 'missing.html' + self.assert_equal(e.name, 'missing.html') else: assert 0, 'expected exception' with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' + self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') def test_templates_list(self): from blueprintapp import app diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index c8bf4687..2b689bc0 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -23,8 +23,8 @@ SECRET_KEY = 'devkey' class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): - assert app.secret_key == 'devkey' - assert app.config['TEST_KEY'] == 'foo' + self.assert_equal(app.secret_key, 'devkey') + self.assert_equal(app.config['TEST_KEY'], 'foo') assert 'ConfigTestCase' not in app.config def test_config_from_file(self): diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py index d691b1dd..531f7f82 100644 --- a/flask/testsuite/deprecations.py +++ b/flask/testsuite/deprecations.py @@ -27,8 +27,8 @@ class DeprecationsTestCase(FlaskTestCase): return app.jinja_env.globals['foo'] c = app.test_client() - assert c.get('/').data == '42' - assert len(log) == 1 + self.assert_equal(c.get('/').data, '42') + self.assert_equal(len(log), 1) assert 'init_jinja_globals' in str(log[0]['message']) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index faea9c8d..50494640 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -47,7 +47,7 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), content_type='application/json; charset=iso-8859-15') - assert resp.data == u'Hällo Wörld'.encode('utf-8') + self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8')) def test_jsonify(self): d = dict(a=23, b=42, c=[1, 2, 3]) @@ -61,8 +61,8 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() for url in '/kw', '/dict': rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data) == d + self.assert_equal(rv.mimetype, 'application/json') + self.assert_equal(flask.json.loads(rv.data), d) def test_json_attr(self): app = flask.Flask(__name__) @@ -72,16 +72,16 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), content_type='application/json') - assert rv.data == '3' + self.assert_equal(rv.data, '3') def test_template_escaping(self): app = flask.Flask(__name__) render = flask.render_template_string with app.test_request_context(): rv = render('{{ ""|tojson|safe }}') - assert rv == '"<\\/script>"' + self.assert_equal(rv, '"<\\/script>"') rv = render('{{ "<\0/script>"|tojson|safe }}') - assert rv == '"<\\u0000\\/script>"' + self.assert_equal(rv, '"<\\u0000\\/script>"') def test_modified_url_encoding(self): class ModifiedRequest(flask.Request): @@ -95,8 +95,8 @@ class JSONTestCase(FlaskTestCase): return flask.request.args['foo'] rv = app.test_client().get(u'/?foo=정상처리'.encode('euc-kr')) - assert rv.status_code == 200 - assert rv.data == u'정상처리'.encode('utf-8') + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, u'정상처리'.encode('utf-8')) if not has_encoding('euc-kr'): test_modified_url_encoding = None @@ -109,9 +109,9 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): rv = flask.send_file('static/index.html') assert rv.direct_passthrough - assert rv.mimetype == 'text/html' + self.assert_equal(rv.mimetype, 'text/html') with app.open_resource('static/index.html') as f: - assert rv.data == f.read() + self.assert_equal(rv.data, f.read()) def test_send_file_xsendfile(self): app = flask.Flask(__name__) @@ -120,9 +120,9 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file('static/index.html') assert rv.direct_passthrough assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - assert rv.mimetype == 'text/html' + self.assert_equal(rv.headers['x-sendfile'], + os.path.join(app.root_path, 'static/index.html')) + self.assert_equal(rv.mimetype, 'text/html') def test_send_file_object(self): app = flask.Flask(__name__) @@ -131,39 +131,39 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' + self.assert_equal(rv.data, f.read()) + self.assert_equal(rv.mimetype, 'text/html') # mimetypes + etag - assert len(captured) == 2 + self.assert_equal(len(captured), 2) app.use_x_sendfile = True with catch_warnings() as captured: with app.test_request_context(): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) - assert rv.mimetype == 'text/html' + self.assert_equal(rv.mimetype, 'text/html') assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') + self.assert_equal(rv.headers['x-sendfile'], + os.path.join(app.root_path, 'static/index.html')) # mimetypes + etag - assert len(captured) == 2 + self.assert_equal(len(captured), 2) app.use_x_sendfile = False with app.test_request_context(): with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f) - assert rv.data == 'Test' - assert rv.mimetype == 'application/octet-stream' + self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.mimetype, 'application/octet-stream') # etags - assert len(captured) == 1 + self.assert_equal(len(captured), 1) with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f, mimetype='text/plain') - assert rv.data == 'Test' - assert rv.mimetype == 'text/plain' + self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.mimetype, 'text/plain') # etags - assert len(captured) == 1 + self.assert_equal(len(captured), 1) app.use_x_sendfile = True with catch_warnings() as captured: @@ -172,7 +172,7 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file(f) assert 'x-sendfile' not in rv.headers # etags - assert len(captured) == 1 + self.assert_equal(len(captured), 1) def test_attachment(self): app = flask.Flask(__name__) @@ -181,25 +181,25 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f, as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' + self.assert_equal(value, 'attachment') # mimetypes + etag - assert len(captured) == 2 + self.assert_equal(len(captured), 2) with app.test_request_context(): - assert options['filename'] == 'index.html' + self.assert_equal(options['filename'], 'index.html') rv = flask.send_file('static/index.html', as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.html' + self.assert_equal(value, 'attachment') + self.assert_equal(options['filename'], 'index.html') with app.test_request_context(): rv = flask.send_file(StringIO('Test'), as_attachment=True, attachment_filename='index.txt', add_etags=False) - assert rv.mimetype == 'text/plain' + self.assert_equal(rv.mimetype, 'text/plain') value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.txt' + self.assert_equal(value, 'attachment') + self.assert_equal(options['filename'], 'index.txt') class LoggingTestCase(FlaskTestCase): @@ -208,7 +208,7 @@ class LoggingTestCase(FlaskTestCase): app = flask.Flask(__name__) logger1 = app.logger assert app.logger is logger1 - assert logger1.name == __name__ + self.assert_equal(logger1.name, __name__) app.logger_name = __name__ + '/test_logger_cache' assert app.logger is not logger1 @@ -254,7 +254,7 @@ class LoggingTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') - assert rv.status_code == 500 + self.assert_equal(rv.status_code, 500) assert 'Internal Server Error' in rv.data err = out.getvalue() @@ -282,8 +282,8 @@ class LoggingTestCase(FlaskTestCase): return 'Hello Server Error', 500 for trigger in 'before', 'after': rv = app.test_client().get('/') - assert rv.status_code == 500 - assert rv.data == 'Hello Server Error' + self.assert_equal(rv.status_code, 500) + self.assert_equal(rv.data, 'Hello Server Error') def suite(): diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index e55807b3..d9054a47 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -29,10 +29,10 @@ class SignalsTestCase(FlaskTestCase): flask.template_rendered.connect(record, app) try: app.test_client().get('/') - assert len(recorded) == 1 + self.assert_equal(len(recorded), 1) template, context = recorded[0] - assert template.name == 'simple_template.html' - assert context['whiskey'] == 42 + self.assert_equal(template.name, 'simple_template.html') + self.assert_equal(context['whiskey'], 42) finally: flask.template_rendered.disconnect(record, app) @@ -44,7 +44,7 @@ class SignalsTestCase(FlaskTestCase): calls.append('before-signal') def after_request_signal(sender, response): - assert response.data == 'stuff' + self.assert_equal(response.data, 'stuff') calls.append('after-signal') @app.before_request @@ -67,11 +67,11 @@ class SignalsTestCase(FlaskTestCase): try: rv = app.test_client().get('/') - assert rv.data == 'stuff' + self.assert_equal(rv.data, 'stuff') - assert calls == ['before-signal', 'before-handler', + self.assert_equal(calls, ['before-signal', 'before-handler', 'handler', 'after-handler', - 'after-signal'] + 'after-signal']) finally: flask.request_started.disconnect(before_request_signal, app) flask.request_finished.disconnect(after_request_signal, app) @@ -89,8 +89,8 @@ class SignalsTestCase(FlaskTestCase): flask.got_request_exception.connect(record, app) try: - assert app.test_client().get('/').status_code == 500 - assert len(recorded) == 1 + self.assert_equal(app.test_client().get('/').status_code, 500) + self.assert_equal(len(recorded), 1) assert isinstance(recorded[0], ZeroDivisionError) finally: flask.got_request_exception.disconnect(record, app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index e980ff92..20d7a16f 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -24,7 +24,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('context_template.html', value=23) rv = app.test_client().get('/') - assert rv.data == '

23|42' + self.assert_equal(rv.data, '

23|42') def test_original_win(self): app = flask.Flask(__name__) @@ -32,7 +32,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template_string('{{ config }}', config=42) rv = app.test_client().get('/') - assert rv.data == '42' + self.assert_equal(rv.data, '42') def test_standard_context(self): app = flask.Flask(__name__) @@ -48,7 +48,7 @@ class TemplatingTestCase(FlaskTestCase): {{ session.test }} ''') rv = app.test_client().get('/?foo=42') - assert rv.data.split() == ['42', '23', 'False', 'aha'] + self.assert_equal(rv.data.split(), ['42', '23', 'False', 'aha']) def test_escaping(self): text = '

Hello World!' @@ -58,14 +58,14 @@ class TemplatingTestCase(FlaskTestCase): return flask.render_template('escaping_template.html', text=text, html=flask.Markup(text)) lines = app.test_client().get('/').data.splitlines() - assert lines == [ + self.assert_equal(lines, [ '<p>Hello World!', '

Hello World!', '

Hello World!', '

Hello World!', '<p>Hello World!', '

Hello World!' - ] + ]) def test_no_escaping(self): app = flask.Flask(__name__) @@ -79,7 +79,7 @@ class TemplatingTestCase(FlaskTestCase): app = flask.Flask(__name__) with app.test_request_context(): macro = flask.get_template_attribute('_macro.html', 'hello') - assert macro('World') == 'Hello World!' + self.assert_equal(macro('World'), 'Hello World!') def test_template_filter(self): app = flask.Flask(__name__) @@ -87,8 +87,8 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] assert 'my_reverse' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['my_reverse'] == my_reverse - assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' + self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) + self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') def test_template_filter_with_name(self): app = flask.Flask(__name__) @@ -96,8 +96,8 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] assert 'strrev' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['strrev'] == my_reverse - assert app.jinja_env.filters['strrev']('abcd') == 'dcba' + self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) + self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') def test_template_filter_with_template(self): app = flask.Flask(__name__) @@ -108,7 +108,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - assert rv.data == 'dcba' + self.assert_equal(rv.data, 'dcba') def test_template_filter_with_name_and_template(self): app = flask.Flask(__name__) @@ -119,7 +119,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - assert rv.data == 'dcba' + self.assert_equal(rv.data, 'dcba') def test_custom_template_loader(self): class MyFlask(flask.Flask): @@ -132,7 +132,7 @@ class TemplatingTestCase(FlaskTestCase): return flask.render_template('index.html') c = app.test_client() rv = c.get('/') - assert rv.data == 'Hello Custom World!' + self.assert_equal(rv.data, 'Hello Custom World!') def suite(): diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 4e16c257..0cf1980d 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -97,14 +97,14 @@ class TestToolsTestCase(FlaskTestCase): with app.test_client() as c: resp = c.get('/') - assert flask.g.value == 42 - assert resp.data == 'Hello World!' - assert resp.status_code == 200 + self.assert_equal(flask.g.value, 42) + self.assert_equal(resp.data, 'Hello World!') + self.assert_equal(resp.status_code, 200) resp = c.get('/other') assert not hasattr(flask.g, 'value') assert 'Internal Server Error' in resp.data - assert resp.status_code == 500 + self.assert_equal(resp.status_code, 500) flask.g.value = 23 try: From fc2caa4b9c75a6ee8569495008efd1f37c27a8cb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:43:27 +0100 Subject: [PATCH 20/43] Changed assert to self.assert_ where it was still in place --- flask/testsuite/basic.py | 80 ++++++++++++++++----------------- flask/testsuite/blueprints.py | 18 ++++---- flask/testsuite/config.py | 20 ++++----- flask/testsuite/deprecations.py | 2 +- flask/testsuite/helpers.py | 34 +++++++------- flask/testsuite/signals.py | 2 +- flask/testsuite/templating.py | 12 ++--- flask/testsuite/testing.py | 4 +- 8 files changed, 86 insertions(+), 86 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 8c2be901..d09ec92e 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -73,7 +73,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - assert not rv.data # head truncates + self.assert_(not rv.data) # head truncates self.assert_equal(c.post('/more').data, 'POST') self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') @@ -97,7 +97,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - assert not rv.data # head truncates + self.assert_(not rv.data) # head truncates self.assert_equal(c.post('/more').data, 'POST') self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') @@ -168,8 +168,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() + self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower()) + self.assert_('httponly' in rv.headers['set-cookie'].lower()) def test_session_using_server_name_and_port(self): app = flask.Flask(__name__) @@ -182,8 +182,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() + self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower()) + self.assert_('httponly' in rv.headers['set-cookie'].lower()) def test_session_using_application_root(self): class PrefixPathMiddleware(object): @@ -205,7 +205,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'path=/bar' in rv.headers['set-cookie'].lower() + self.assert_('path=/bar' in rv.headers['set-cookie'].lower()) def test_missing_session(self): app = flask.Flask(__name__) @@ -213,11 +213,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: f(*args, **kwargs) except RuntimeError, e: - assert e.args and 'session is unavailable' in e.args[0] + self.assert_(e.args and 'session is unavailable' in e.args[0]) else: - assert False, 'expected exception' + self.assert_(False, 'expected exception') with app.test_request_context(): - assert flask.session.get('missing_key') is None + self.assert_(flask.session.get('missing_key') is None) expect_exception(flask.session.__setitem__, 'foo', 42) expect_exception(flask.session.pop, 'foo') @@ -237,7 +237,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): client = app.test_client() rv = client.get('/') - assert 'set-cookie' in rv.headers + self.assert_('set-cookie' in rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime @@ -250,20 +250,20 @@ class BasicFunctionalityTestCase(FlaskTestCase): permanent = False rv = app.test_client().get('/') - assert 'set-cookie' in rv.headers + self.assert_('set-cookie' in rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) - assert match is None + self.assert_(match is None) def test_flashes(self): app = flask.Flask(__name__) app.secret_key = 'testkey' with app.test_request_context(): - assert not flask.session.modified + self.assert_(not flask.session.modified) flask.flash('Zap') flask.session.modified = False flask.flash('Zip') - assert flask.session.modified + self.assert_(flask.session.modified) self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): @@ -308,12 +308,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): return response @app.route('/') def index(): - assert 'before' in evts - assert 'after' not in evts + self.assert_('before' in evts) + self.assert_('after' not in evts) return 'request' - assert 'after' not in evts + self.assert_('after' not in evts) rv = app.test_client().get('/').data - assert 'after' in evts + self.assert_('after' in evts) self.assert_equal(rv, 'request|after') def test_teardown_request_handler(self): @@ -328,7 +328,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - assert 'Response' in rv.data + self.assert_('Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): @@ -344,7 +344,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - assert 'Response' in rv.data + self.assert_('Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): @@ -377,7 +377,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - assert 'Internal Server Error' in rv.data + self.assert_('Internal Server Error' in rv.data) self.assert_equal(len(called), 2) def test_before_after_request_order(self): @@ -451,7 +451,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.errorhandler(MyException) def handle_my_exception(e): - assert isinstance(e, MyException) + self.assert_(isinstance(e, MyException)) return '42' @app.route('/') def index(): @@ -474,7 +474,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.get('/fail') except KeyError, e: - assert isinstance(e, BadRequest) + self.assert_(isinstance(e, BadRequest)) else: self.fail('Expected exception') @@ -509,8 +509,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.post('/fail', data={'foo': 'index.txt'}) except DebugFilesKeyError, e: - assert 'no file contents were transmitted' in str(e) - assert 'This was submitted: "index.txt"' in str(e) + self.assert_('no file contents were transmitted' in str(e)) + self.assert_('This was submitted: "index.txt"' in str(e)) else: self.fail('Expected exception') @@ -572,8 +572,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): pass with app.test_request_context(): self.assert_equal(flask.url_for('hello', name='test x'), '/hello/test%20x') - assert flask.url_for('hello', name='test x', _external=True) \ - == 'http://localhost/hello/test%20x' + self.assert_equal(flask.url_for('hello', name='test x', _external=True), + 'http://localhost/hello/test%20x') def test_custom_converters(self): from werkzeug.routing import BaseConverter @@ -597,8 +597,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(rv.status_code, 200) self.assert_equal(rv.data.strip(), '

Hello World!

') with app.test_request_context(): - assert flask.url_for('static', filename='index.html') \ - == '/static/index.html' + self.assert_equal(flask.url_for('static', filename='index.html'), + '/static/index.html') def test_none_response(self): app = flask.Flask(__name__) @@ -611,7 +611,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(str(e), 'View function did not return a response') pass else: - assert "Expected ValueError" + self.assert_("Expected ValueError") def test_request_locals(self): self.assert_equal(repr(flask.g), '') @@ -641,7 +641,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): pass except Exception, e: - assert isinstance(e, ValueError) + self.assert_(isinstance(e, ValueError)) self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:5000') does not match the " + \ "server name from the WSGI environment ('localhost')") @@ -768,11 +768,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.before_request def always_first(): flask.request.form['myfile'] - assert False + self.assert_(False) @app.route('/accept', methods=['POST']) def accept_file(): flask.request.form['myfile'] - assert False + self.assert_(False) @app.errorhandler(413) def catcher(error): return '42' @@ -889,17 +889,17 @@ class ContextTestCase(FlaskTestCase): self.assert_equal(index(), 'Hello World!') with app.test_request_context('/meh'): self.assert_equal(meh(), 'http://localhost/meh') - assert flask._request_ctx_stack.top is None + self.assert_(flask._request_ctx_stack.top is None) def test_context_test(self): app = flask.Flask(__name__) - assert not flask.request - assert not flask.has_request_context() + self.assert_(not flask.request) + self.assert_(not flask.has_request_context()) ctx = app.test_request_context() ctx.push() try: - assert flask.request - assert flask.has_request_context() + self.assert_(flask.request) + self.assert_(flask.has_request_context()) finally: ctx.pop() @@ -918,7 +918,7 @@ class ContextTestCase(FlaskTestCase): except RuntimeError: pass else: - assert 0, 'expected runtime error' + self.assert_(0, 'expected runtime error') class SubdomainTestCase(FlaskTestCase): diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index fdd63fee..88e2be36 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -173,8 +173,8 @@ class ModuleTestCase(FlaskTestCase): self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' + self.assert_equal(flask.url_for('admin.static', filename='test.txt'), + '/admin/static/test.txt') with app.test_request_context(): try: @@ -182,7 +182,7 @@ class ModuleTestCase(FlaskTestCase): except TemplateNotFound, e: self.assert_equal(e.name, 'missing.html') else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') @@ -198,13 +198,13 @@ class ModuleTestCase(FlaskTestCase): except NotFound: pass else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') try: f('../__init__.py') except NotFound: pass else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') # testcase for a security issue that may exist on windows systems import os @@ -217,7 +217,7 @@ class ModuleTestCase(FlaskTestCase): except NotFound: pass else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') finally: os.path = old_path @@ -355,8 +355,8 @@ class BlueprintTestCase(FlaskTestCase): self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' + self.assert_equal(flask.url_for('admin.static', filename='test.txt'), + '/admin/static/test.txt') with app.test_request_context(): try: @@ -364,7 +364,7 @@ class BlueprintTestCase(FlaskTestCase): except TemplateNotFound, e: self.assert_equal(e.name, 'missing.html') else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 2b689bc0..b9fa6b48 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -25,7 +25,7 @@ class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): self.assert_equal(app.secret_key, 'devkey') self.assert_equal(app.config['TEST_KEY'], 'foo') - assert 'ConfigTestCase' not in app.config + self.assert_('ConfigTestCase' not in app.config) def test_config_from_file(self): app = flask.Flask(__name__) @@ -54,13 +54,13 @@ class ConfigTestCase(FlaskTestCase): try: app.config.from_envvar('FOO_SETTINGS') except RuntimeError, e: - assert "'FOO_SETTINGS' is not set" in str(e) + self.assert_("'FOO_SETTINGS' is not set" in str(e)) else: - assert 0, 'expected exception' - assert not app.config.from_envvar('FOO_SETTINGS', silent=True) + self.assert_(0, 'expected exception') + self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) os.environ = {'FOO_SETTINGS': __file__.rsplit('.')[0] + '.py'} - assert app.config.from_envvar('FOO_SETTINGS') + self.assert_(app.config.from_envvar('FOO_SETTINGS')) self.common_object_test(app) finally: os.environ = env @@ -71,12 +71,12 @@ class ConfigTestCase(FlaskTestCase): app.config.from_pyfile('missing.cfg') except IOError, e: msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.cfg'") + self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):')) + self.assert_(msg.endswith("missing.cfg'")) else: - assert 0, 'expected config' - assert not app.config.from_pyfile('missing.cfg', silent=True) + self.assert_(0, 'expected config') + self.assert_(not app.config.from_pyfile('missing.cfg', silent=True)) class InstanceTestCase(FlaskTestCase): diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py index 531f7f82..062f40b0 100644 --- a/flask/testsuite/deprecations.py +++ b/flask/testsuite/deprecations.py @@ -29,7 +29,7 @@ class DeprecationsTestCase(FlaskTestCase): c = app.test_client() self.assert_equal(c.get('/').data, '42') self.assert_equal(len(log), 1) - assert 'init_jinja_globals' in str(log[0]['message']) + self.assert_('init_jinja_globals' in str(log[0]['message'])) def suite(): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 50494640..4bd05e3d 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -108,7 +108,7 @@ class SendfileTestCase(FlaskTestCase): app = flask.Flask(__name__) with app.test_request_context(): rv = flask.send_file('static/index.html') - assert rv.direct_passthrough + self.assert_(rv.direct_passthrough) self.assert_equal(rv.mimetype, 'text/html') with app.open_resource('static/index.html') as f: self.assert_equal(rv.data, f.read()) @@ -118,8 +118,8 @@ class SendfileTestCase(FlaskTestCase): app.use_x_sendfile = True with app.test_request_context(): rv = flask.send_file('static/index.html') - assert rv.direct_passthrough - assert 'x-sendfile' in rv.headers + self.assert_(rv.direct_passthrough) + self.assert_('x-sendfile' in rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) self.assert_equal(rv.mimetype, 'text/html') @@ -142,7 +142,7 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) self.assert_equal(rv.mimetype, 'text/html') - assert 'x-sendfile' in rv.headers + self.assert_('x-sendfile' in rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) # mimetypes + etag @@ -170,7 +170,7 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): f = StringIO('Test') rv = flask.send_file(f) - assert 'x-sendfile' not in rv.headers + self.assert_('x-sendfile' not in rv.headers) # etags self.assert_equal(len(captured), 1) @@ -207,10 +207,10 @@ class LoggingTestCase(FlaskTestCase): def test_logger_cache(self): app = flask.Flask(__name__) logger1 = app.logger - assert app.logger is logger1 + self.assert_(app.logger is logger1) self.assert_equal(logger1.name, __name__) app.logger_name = __name__ + '/test_logger_cache' - assert app.logger is not logger1 + self.assert_(app.logger is not logger1) def test_debug_log(self): app = flask.Flask(__name__) @@ -230,10 +230,10 @@ class LoggingTestCase(FlaskTestCase): with catch_stderr() as err: c.get('/') out = err.getvalue() - assert 'WARNING in helpers [' in out - assert os.path.basename(__file__.rsplit('.')[0] + '.py') in out - assert 'the standard library is dead' in out - assert 'this is a debug statement' in out + self.assert_('WARNING in helpers [' in out) + self.assert_(os.path.basename(__file__.rsplit('.')[0] + '.py') in out) + self.assert_('the standard library is dead' in out) + self.assert_('this is a debug statement' in out) with catch_stderr() as err: try: @@ -241,7 +241,7 @@ class LoggingTestCase(FlaskTestCase): except ZeroDivisionError: pass else: - assert False, 'debug log ate the exception' + self.assert_(False, 'debug log ate the exception') def test_exception_logging(self): out = StringIO() @@ -255,13 +255,13 @@ class LoggingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - assert 'Internal Server Error' in rv.data + self.assert_('Internal Server Error' in rv.data) err = out.getvalue() - assert 'Exception on / [GET]' in err - assert 'Traceback (most recent call last):' in err - assert '1/0' in err - assert 'ZeroDivisionError:' in err + self.assert_('Exception on / [GET]' in err) + self.assert_('Traceback (most recent call last):' in err) + self.assert_('1/0' in err) + self.assert_('ZeroDivisionError:' in err) def test_processor_exceptions(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index d9054a47..da1a68ca 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -91,7 +91,7 @@ class SignalsTestCase(FlaskTestCase): try: self.assert_equal(app.test_client().get('/').status_code, 500) self.assert_equal(len(recorded), 1) - assert isinstance(recorded[0], ZeroDivisionError) + self.assert_(isinstance(recorded[0], ZeroDivisionError)) finally: flask.got_request_exception.disconnect(record, app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 20d7a16f..eadbdcf6 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -70,10 +70,10 @@ class TemplatingTestCase(FlaskTestCase): def test_no_escaping(self): app = flask.Flask(__name__) with app.test_request_context(): - assert flask.render_template_string('{{ foo }}', - foo='') == '' - assert flask.render_template('mail.txt', foo='') \ - == ' Mail' + self.assert_equal(flask.render_template_string('{{ foo }}', + foo=''), '') + self.assert_equal(flask.render_template('mail.txt', foo=''), + ' Mail') def test_macros(self): app = flask.Flask(__name__) @@ -86,7 +86,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter() def my_reverse(s): return s[::-1] - assert 'my_reverse' in app.jinja_env.filters.keys() + self.assert_('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -95,7 +95,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter('strrev') def my_reverse(s): return s[::-1] - assert 'strrev' in app.jinja_env.filters.keys() + self.assert_('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 0cf1980d..f665e12c 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -102,8 +102,8 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(resp.status_code, 200) resp = c.get('/other') - assert not hasattr(flask.g, 'value') - assert 'Internal Server Error' in resp.data + self.assert_(not hasattr(flask.g, 'value')) + self.assert_('Internal Server Error' in resp.data) self.assert_equal(resp.status_code, 500) flask.g.value = 23 From d5cd4f8d59a4a8e5860c8813b3f5d3f3631e9183 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:48:09 +0100 Subject: [PATCH 21/43] Updated manifests --- MANIFEST.in | 5 ++++- setup.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3fef8b5b..f82ed054 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include Makefile CHANGES LICENSE AUTHORS +include Makefile CHANGES LICENSE AUTHORS run-tests.py recursive-include artwork * recursive-include tests * recursive-include examples * @@ -9,5 +9,8 @@ recursive-exclude tests *.pyc recursive-exclude tests *.pyo recursive-exclude examples *.pyc recursive-exclude examples *.pyo +recursive-include flask/testsuite/static * +recursive-include flask/testsuite/templates * +recursive-include flask/testsuite/test_apps * prune docs/_build prune docs/_themes/.git diff --git a/setup.py b/setup.py index a51c3887..db2eb9c7 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,10 @@ setup( description='A microframework based on Werkzeug, Jinja2 ' 'and good intentions', long_description=__doc__, - packages=['flask'], + packages=['flask', 'flask.testsuite'], + package_data={ + 'flask.testsuite': ['test_apps/*', 'static/*', 'templates/*'] + }, zip_safe=False, platforms='any', install_requires=[ From fbd6776e68a12aa7bf7d646ca03d568cedc616f3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:48:33 +0100 Subject: [PATCH 22/43] Fixed a bug in the testsuite that caused problems when dots where in directory names --- flask/testsuite/config.py | 4 ++-- flask/testsuite/helpers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index b9fa6b48..2ed726c8 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -29,7 +29,7 @@ class ConfigTestCase(FlaskTestCase): def test_config_from_file(self): app = flask.Flask(__name__) - app.config.from_pyfile(__file__.rsplit('.')[0] + '.py') + app.config.from_pyfile(__file__.rsplit('.', 1)[0] + '.py') self.common_object_test(app) def test_config_from_object(self): @@ -59,7 +59,7 @@ class ConfigTestCase(FlaskTestCase): self.assert_(0, 'expected exception') self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) - os.environ = {'FOO_SETTINGS': __file__.rsplit('.')[0] + '.py'} + os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'} self.assert_(app.config.from_envvar('FOO_SETTINGS')) self.common_object_test(app) finally: diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 4bd05e3d..56264f70 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -231,7 +231,7 @@ class LoggingTestCase(FlaskTestCase): c.get('/') out = err.getvalue() self.assert_('WARNING in helpers [' in out) - self.assert_(os.path.basename(__file__.rsplit('.')[0] + '.py') in out) + self.assert_(os.path.basename(__file__.rsplit('.', 1)[0] + '.py') in out) self.assert_('the standard library is dead' in out) self.assert_('this is a debug statement' in out) From 0851d956b39634d2cd1618e8c075149d85fb599f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:55:11 +0100 Subject: [PATCH 23/43] Updated README --- README | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/README b/README index 5c0eb4f1..7d5ada23 100644 --- a/README +++ b/README @@ -11,24 +11,41 @@ ~ Is it ready? - A preview release is out now, and I'm hoping for some - input about what you want from a microframework and - how it should look like. Consider the API to slightly - improve over time. + It's still not 1.0 but it's shaping up nicely and is + already widely used. Consider the API to slightly + improve over time but we don't plan on breaking it. ~ What do I need? - Jinja 2.4 and Werkzeug 0.6.1. `easy_install` will - install them for you if you do `easy_install Flask==dev`. + Jinja 2.4 and Werkzeug 0.6.1. `pip` or `easy_install` will + install them for you if you do `easy_install Flask`. I encourage you to use a virtualenv. Check the docs for complete installation and usage instructions. ~ Where are the docs? - Go to http://flask.pocoo.org/ for a prebuilt version of - the current documentation. Otherwise build them yourself + Go to http://flask.pocoo.org/docs/ for a prebuilt version + of the current documentation. Otherwise build them yourself from the sphinx sources in the docs folder. + ~ Where are the tests? + + Good that you're asking. The tests are in the + flask/testsuite package. To run the tests use the + `run-tests.py` file: + + $ python run-tests.py + + If it's not enough output for you, you can use the + `--verbose` flag: + + $ python run-tests.py --verbose + + If you just want one particular testcase to run you can + provide it on the command line: + + $ python run-tests.py test_to_run + ~ Where can I get help? Either use the #pocoo IRC channel on irc.freenode.net or From 29b7efa36bd12234a4086cafbe9b8a4099991d08 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:59:52 +0100 Subject: [PATCH 24/43] Improved the logic in which tests are found --- run-tests.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/run-tests.py b/run-tests.py index 96db7f9a..176094b6 100644 --- a/run-tests.py +++ b/run-tests.py @@ -37,21 +37,21 @@ class BetterLoader(TestLoader): if testname[len(common_prefix):] == name: return testcase - all_results = [] + all_tests = [] for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name): - all_results.append((testcase, testname)) - - if len(all_results) == 1: - return all_results[0][0] - elif not len(all_results): - error = 'could not find testcase "%s"' % name - else: - error = 'Too many matches: for "%s"\n%s' % \ - (name, '\n'.join(' - ' + n for c, n in all_results)) - - print >> sys.stderr, 'Error: %s' % error - sys.exit(1) + if testname.endswith('.' + name) or ('.' + name + '.') in testname: + all_tests.append(testcase) + + if not all_tests: + print >> sys.stderr, 'Error: could not find test case for "%s"' % name + sys.exit(1) + + if len(all_tests) == 1: + return all_tests[0] + rv = unittest.TestSuite() + for test in all_tests: + rv.addTest(test) + return rv unittest.main(testLoader=BetterLoader(), defaultTest='suite') From f30b1174b85ae44b4d215045ef3361f2c3b3a367 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:00:26 +0100 Subject: [PATCH 25/43] Also support full qualified test names --- run-tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run-tests.py b/run-tests.py index 176094b6..b74e7f71 100644 --- a/run-tests.py +++ b/run-tests.py @@ -39,7 +39,8 @@ class BetterLoader(TestLoader): all_tests = [] for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name) or ('.' + name + '.') in testname: + if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ + testname.startswith(name + '.'): all_tests.append(testcase) if not all_tests: From 5a496885544989ca702e2c9996372a8ebf2cf00d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:02:40 +0100 Subject: [PATCH 26/43] Moved loader code into the testsuite and out of the runner --- flask/testsuite/__init__.py | 52 +++++++++++++++++++++++++++++++++ run-tests.py | 57 +------------------------------------ 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 8df7a7fd..ff0224bc 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -20,6 +20,9 @@ from contextlib import contextmanager from werkzeug.utils import import_string, find_modules +common_prefix = __name__ + '.' + + def add_to_path(path): def _samefile(x, y): try: @@ -47,6 +50,25 @@ def iter_suites(): yield mod.suite() +def find_all_tests(): + suites = [suite()] + while suites: + s = suites.pop() + try: + suites.extend(s) + except TypeError: + yield s + + +def find_all_tests_with_name(): + for testcase in find_all_tests(): + yield testcase, '%s.%s.%s' % ( + testcase.__class__.__module__, + testcase.__class__.__name__, + testcase._testMethodName + ) + + @contextmanager def catch_warnings(): """Catch warnings in a with block in a list""" @@ -113,6 +135,36 @@ class FlaskTestCase(unittest.TestCase): return self.assertEqual(x, y) +class BetterLoader(unittest.TestLoader): + + def loadTestsFromName(self, name, module=None): + if name == 'suite': + return suite() + for testcase, testname in find_all_tests_with_name(): + if testname == name: + return testcase + if testname.startswith(common_prefix): + if testname[len(common_prefix):] == name: + return testcase + + all_tests = [] + for testcase, testname in find_all_tests_with_name(): + if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ + testname.startswith(name + '.'): + all_tests.append(testcase) + + if not all_tests: + print >> sys.stderr, 'Error: could not find test case for "%s"' % name + sys.exit(1) + + if len(all_tests) == 1: + return all_tests[0] + rv = unittest.TestSuite() + for test in all_tests: + rv.addTest(test) + return rv + + def suite(): setup_paths() suite = unittest.TestSuite() diff --git a/run-tests.py b/run-tests.py index b74e7f71..7d44febc 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,58 +1,3 @@ -import sys import unittest -from unittest.loader import TestLoader -from flask.testsuite import suite - -common_prefix = suite.__module__ + '.' - - -def find_all_tests(): - suites = [suite()] - while suites: - s = suites.pop() - try: - suites.extend(s) - except TypeError: - yield s - - -def find_all_tests_with_name(): - for testcase in find_all_tests(): - yield testcase, '%s.%s.%s' % ( - testcase.__class__.__module__, - testcase.__class__.__name__, - testcase._testMethodName - ) - - -class BetterLoader(TestLoader): - - def loadTestsFromName(self, name, module=None): - if name == 'suite': - return suite() - for testcase, testname in find_all_tests_with_name(): - if testname == name: - return testcase - if testname.startswith(common_prefix): - if testname[len(common_prefix):] == name: - return testcase - - all_tests = [] - for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ - testname.startswith(name + '.'): - all_tests.append(testcase) - - if not all_tests: - print >> sys.stderr, 'Error: could not find test case for "%s"' % name - sys.exit(1) - - if len(all_tests) == 1: - return all_tests[0] - rv = unittest.TestSuite() - for test in all_tests: - rv.addTest(test) - return rv - - +from flask.testsuite import BetterLoader unittest.main(testLoader=BetterLoader(), defaultTest='suite') From a082a5e0ba81af15653fe56501c8d2530d3621dc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:07:49 +0100 Subject: [PATCH 27/43] Cleanup in the test finder --- flask/testsuite/__init__.py | 51 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index ff0224bc..5ebc786e 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -20,9 +20,6 @@ from contextlib import contextmanager from werkzeug.utils import import_string, find_modules -common_prefix = __name__ + '.' - - def add_to_path(path): def _samefile(x, y): try: @@ -50,23 +47,18 @@ def iter_suites(): yield mod.suite() -def find_all_tests(): - suites = [suite()] +def find_all_tests(suite): + suites = [suite] while suites: s = suites.pop() try: suites.extend(s) except TypeError: - yield s - - -def find_all_tests_with_name(): - for testcase in find_all_tests(): - yield testcase, '%s.%s.%s' % ( - testcase.__class__.__module__, - testcase.__class__.__name__, - testcase._testMethodName - ) + yield s, '%s.%s.%s' % ( + s.__class__.__module__, + s.__class__.__name__, + s._testMethodName + ) @contextmanager @@ -111,6 +103,10 @@ def emits_module_deprecation_warning(f): class FlaskTestCase(unittest.TestCase): + """Baseclass for all the tests that Flask uses. Use these methods + for testing instead of the camelcased ones in the baseclass for + consistency. + """ def ensure_clean_request_context(self): # make sure we're not leaking a request context since we are @@ -136,20 +132,27 @@ class FlaskTestCase(unittest.TestCase): class BetterLoader(unittest.TestLoader): + """A nicer loader that solves two problems. First of all we are setting + up tests from different sources and we're doing this programmatically + which breaks the default loading logic so this is required anyways. + Secondly this loader has a nicer interpolation for test names than the + default one so you can just do ``run-tests.py ViewTestCase`` and it + will work. + """ + + def getRootSuite(self): + return suite() def loadTestsFromName(self, name, module=None): + root = self.getRootSuite() if name == 'suite': - return suite() - for testcase, testname in find_all_tests_with_name(): - if testname == name: - return testcase - if testname.startswith(common_prefix): - if testname[len(common_prefix):] == name: - return testcase + return root all_tests = [] - for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ + for testcase, testname in find_all_tests(root): + if testname == name or \ + testname.endswith('.' + name) or \ + ('.' + name + '.') in testname or \ testname.startswith(name + '.'): all_tests.append(testcase) From 5235c3e37e17b3271e3cac4646eaf97fd1cc071a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:09:55 +0100 Subject: [PATCH 28/43] Make BetterLoader() have a better api :) --- flask/testsuite/__init__.py | 3 +-- run-tests.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 5ebc786e..addbb92b 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -157,8 +157,7 @@ class BetterLoader(unittest.TestLoader): all_tests.append(testcase) if not all_tests: - print >> sys.stderr, 'Error: could not find test case for "%s"' % name - sys.exit(1) + raise LookupError('could not find test case for "%s"' % name) if len(all_tests) == 1: return all_tests[0] diff --git a/run-tests.py b/run-tests.py index 7d44febc..0c8d0bdf 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,3 +1,6 @@ import unittest from flask.testsuite import BetterLoader -unittest.main(testLoader=BetterLoader(), defaultTest='suite') +try: + unittest.main(testLoader=BetterLoader(), defaultTest='suite') +except Exception, e: + print 'Error: %s' % e From c8ec453d860ae4754331170108ef03322f29889b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 13:47:01 +0100 Subject: [PATCH 29/43] Require that cookies are enabled in the test client for session transactions --- flask/testing.py | 11 ++++++----- flask/testsuite/testing.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/flask/testing.py b/flask/testing.py index c4d18ca2..6ce96163 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -53,10 +53,12 @@ class FlaskClient(Client): :meth:`~flask.Flask.test_request_context` which are directly passed through. """ + if self.cookie_jar is None: + raise RuntimeError('Session transactions only make sense ' + 'with cookies enabled.') app = self.application environ_overrides = kwargs.pop('environ_overrides', {}) - if self.cookie_jar is not None: - self.cookie_jar.inject_wsgi(environ_overrides) + self.cookie_jar.inject_wsgi(environ_overrides) outer_reqctx = _request_ctx_stack.top with app.test_request_context(*args, **kwargs) as c: sess = app.open_session(c.request) @@ -80,9 +82,8 @@ class FlaskClient(Client): resp = app.response_class() if not app.session_interface.is_null_session(sess): app.save_session(sess, resp) - if self.cookie_jar is not None: - headers = resp.get_wsgi_headers(c.request.environ) - self.cookie_jar.extract_wsgi(c.request.environ, headers) + headers = resp.get_wsgi_headers(c.request.environ) + self.cookie_jar.extract_wsgi(c.request.environ, headers) def open(self, *args, **kwargs): if self.context_preserved: diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index f665e12c..b7a71b1a 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -84,6 +84,18 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction(): self.assert_(req is flask.request._get_current_object()) + def test_session_transaction_needs_cookies(self): + app = flask.Flask(__name__) + app.testing = True + c = app.test_client(use_cookies=False) + try: + with c.session_transaction() as s: + pass + except RuntimeError, e: + self.assert_('cookies' in str(e)) + else: + self.fail('Expected runtime error') + def test_test_client_context_binding(self): app = flask.Flask(__name__) @app.route('/') From d49221bf2eeabbfa4a5be4e537b35bae1eb6d272 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:01:46 +0100 Subject: [PATCH 30/43] The test client now properly pops response contexts on __exit__ --- CHANGES | 2 ++ docs/upgrading.rst | 5 +++++ flask/testing.py | 13 +++++++++---- flask/testsuite/testing.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index fa8c00bf..b39a2001 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,8 @@ Relase date to be decided, codename to be chosen. as defaults. - Added :attr:`flask.views.View.decorators` to support simpler decorating of pluggable (class based) views. +- Fixed an issue where the test client if used with the with statement did not + trigger the execution of the teardown handlers. Version 0.7.3 ------------- diff --git a/docs/upgrading.rst b/docs/upgrading.rst index b318292c..0ba46c13 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -36,6 +36,11 @@ longer have to handle that error to avoid an internal server error showing up for the user. If you were catching this down explicitly in the past as `ValueError` you will need to change this. +Due to a bug in the test client Flask 0.7 did not trigger teardown +handlers when the test client was used in a with statement. This was +since fixed but might require some changes in your testsuites if you +relied on this behavior. + Version 0.7 ----------- diff --git a/flask/testing.py b/flask/testing.py index 6ce96163..612b4d4d 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -86,9 +86,7 @@ class FlaskClient(Client): self.cookie_jar.extract_wsgi(c.request.environ, headers) def open(self, *args, **kwargs): - if self.context_preserved: - _request_ctx_stack.pop() - self.context_preserved = False + self._pop_reqctx_if_necessary() kwargs.setdefault('environ_overrides', {}) \ ['flask._preserve_context'] = self.preserve_context @@ -114,5 +112,12 @@ class FlaskClient(Client): def __exit__(self, exc_type, exc_value, tb): self.preserve_context = False + self._pop_reqctx_if_necessary() + + def _pop_reqctx_if_necessary(self): if self.context_preserved: - _request_ctx_stack.pop() + # we have to use _request_ctx_stack.top.pop instead of + # _request_ctx_stack.pop since we want teardown handlers + # to be executed. + _request_ctx_stack.top.pop() + self.context_preserved = False diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index b7a71b1a..32867c3f 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -126,6 +126,38 @@ class TestToolsTestCase(FlaskTestCase): else: raise AssertionError('some kind of exception expected') + def test_reuse_client(self): + app = flask.Flask(__name__) + c = app.test_client() + + with c: + self.assert_equal(c.get('/').status_code, 404) + + with c: + self.assert_equal(c.get('/').status_code, 404) + + def test_test_client_calls_teardown_handlers(self): + app = flask.Flask(__name__) + called = [] + @app.teardown_request + def remember(error): + called.append(error) + + with app.test_client() as c: + self.assert_equal(called, []) + c.get('/') + self.assert_equal(called, []) + self.assert_equal(called, [None]) + + del called[:] + with app.test_client() as c: + self.assert_equal(called, []) + c.get('/') + self.assert_equal(called, []) + c.get('/') + self.assert_equal(called, [None]) + self.assert_equal(called, [None, None]) + def suite(): suite = unittest.TestSuite() From b256e9f36c6d170e7209a577efcac2503c8e48e3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:05:08 +0100 Subject: [PATCH 31/43] make_default_options_response now tries to use Werkzeug 0.7 functionality before falling back. --- flask/app.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/flask/app.py b/flask/app.py index b4ae64a5..667f5225 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1305,17 +1305,18 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 """ - # This would be nicer in Werkzeug 0.7, which however currently - # is not released. Werkzeug 0.7 provides a method called - # allowed_methods() that returns all methods that are valid for - # a given path. - methods = [] - try: - _request_ctx_stack.top.url_adapter.match(method='--') - except MethodNotAllowed, e: - methods = e.valid_methods - except HTTPException, e: - pass + adapter = _request_ctx_stack.top.url_adapter + if hasattr(adapter, 'allowed_methods'): + methods = adapter.allowed_methods() + else: + # fallback for Werkzeug < 0.7 + methods = [] + try: + adapter.match(method='--') + except MethodNotAllowed, e: + methods = e.valid_methods + except HTTPException, e: + pass rv = self.response_class() rv.allow.update(methods) return rv From 67101c8b9331d289b3ae5428127814d0c19d643c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:41:25 +0100 Subject: [PATCH 32/43] Fake signals no better follow the blinker api --- flask/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/signals.py b/flask/signals.py index 4eedf68f..984accb7 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -34,7 +34,7 @@ except ImportError: 'not installed.') send = lambda *a, **kw: None connect = disconnect = has_receivers_for = receivers_for = \ - temporarily_connected_to = _fail + temporarily_connected_to = connected_to = _fail del _fail # the namespace for code signals. If you are not flask code, do From 367b254c787c59b208899f1dfee78904c1784c73 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:51:28 +0100 Subject: [PATCH 33/43] Make sure that there is a test for subdomain matching with ports --- flask/testsuite/basic.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index d09ec92e..947d0073 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -961,6 +961,17 @@ class SubdomainTestCase(FlaskTestCase): rv = c.get('/', 'http://mitsuhiko.localhost/') self.assert_equal(rv.data, 'index for mitsuhiko') + def test_subdomain_matching_with_ports(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost:3000' + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + c = app.test_client() + rv = c.get('/', 'http://mitsuhiko.localhost:3000/') + self.assert_equal(rv.data, 'index for mitsuhiko') + @emits_module_deprecation_warning def test_module_subdomain_support(self): app = flask.Flask(__name__) From e509d25d32965a8afd7ca98d67ee6f5a6af11cc8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 17:35:47 +0100 Subject: [PATCH 34/43] Some more cleanups in how the test runner is invoked --- flask/testsuite/__init__.py | 31 +++++++++++++++++++++++++------ run-tests.py | 8 ++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index addbb92b..3f807251 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -21,6 +21,10 @@ from werkzeug.utils import import_string, find_modules def add_to_path(path): + """Adds an entry to sys.path_info if it's not already there.""" + if not os.path.isdir(path): + raise RuntimeError('Tried to add nonexisting path') + def _samefile(x, y): try: return os.path.samefile(x, y) @@ -35,12 +39,8 @@ def add_to_path(path): sys.path.append(path) -def setup_paths(): - add_to_path(os.path.abspath(os.path.join( - os.path.dirname(__file__), 'test_apps'))) - - def iter_suites(): + """Yields all testsuites.""" for module in find_modules(__name__): mod = import_string(module) if hasattr(mod, 'suite'): @@ -48,6 +48,7 @@ def iter_suites(): def find_all_tests(suite): + """Yields all the tests and their names from a given suite.""" suites = [suite] while suites: s = suites.pop() @@ -167,9 +168,27 @@ class BetterLoader(unittest.TestLoader): return rv +def setup_path(): + add_to_path(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps'))) + + def suite(): - setup_paths() + """A testsuite that has all the Flask tests. You can use this + function to integrate the Flask tests into your own testsuite + in case you want to test that monkeypatches to Flask do not + break it. + """ + setup_path() suite = unittest.TestSuite() for other_suite in iter_suites(): suite.addTest(other_suite) return suite + + +def main(): + """Runs the testsuite as command line application.""" + try: + unittest.main(testLoader=BetterLoader(), defaultTest='suite') + except Exception, e: + print 'Error: %s' % e diff --git a/run-tests.py b/run-tests.py index 0c8d0bdf..3f9259c4 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,6 +1,2 @@ -import unittest -from flask.testsuite import BetterLoader -try: - unittest.main(testLoader=BetterLoader(), defaultTest='suite') -except Exception, e: - print 'Error: %s' % e +from flask.testsuite import main +main() From c0f42a0978d804237d7bd540f81dcdbc1643ea7f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 18:20:33 +0100 Subject: [PATCH 35/43] Added shebang to the test run file --- run-tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/run-tests.py b/run-tests.py index 3f9259c4..4ef8a72d 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,2 +1,3 @@ +#!/usr/bin/env python from flask.testsuite import main main() From 2e4c39199d3c8cdcf459675cba6e04bd30f98120 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 21:30:41 +0200 Subject: [PATCH 36/43] Refactored logging of internal server errors. Can now be customized --- flask/app.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/flask/app.py b/flask/app.py index 667f5225..1136469f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1216,14 +1216,24 @@ class Flask(_PackageBoundObject): else: raise e - self.logger.exception('Exception on %s [%s]' % ( - request.path, - request.method - )) + self.log_exception((exc_type, exc_value, tb)) if handler is None: return InternalServerError() return handler(e) + def log_exception(self, exc_info): + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error('Exception on %s [%s]' % ( + request.path, + request.method + ), exc_info=exc_info) + def raise_routing_exception(self, request): """Exceptions that are recording during routing are reraised with this method. During debug we are not reraising redirect requests From dc05722b363b4e77357fb943b2d1ee8abea94cb4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 21:42:17 +0200 Subject: [PATCH 37/43] Made the foreword less defensive. --- docs/foreword.rst | 54 ++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 616c298b..10b886bf 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -9,27 +9,30 @@ What does "micro" mean? ----------------------- To me, the "micro" in microframework refers not only to the simplicity and -small size of the framework, but also to the typically limited complexity -and size of applications that are written with the framework. Also the -fact that you can have an entire application in a single Python file. To -be approachable and concise, a microframework sacrifices a few features -that may be necessary in larger or more complex applications. - -For example, Flask uses thread-local objects internally so that you don't -have to pass objects around from function to function within a request in -order to stay threadsafe. While this is a really easy approach and saves -you a lot of time, it might also cause some troubles for very large -applications because changes on these thread-local objects can happen -anywhere in the same thread. - -Flask provides some tools to deal with the downsides of this approach but -it might be an issue for larger applications because in theory -modifications on these objects might happen anywhere in the same thread. +small size of the framework, but also the fact that it does not make much +decisions for you. While Flask does pick a templating engine for you, we +won't make such decisions for your datastore or other parts. + +For us however the term “micro” does not mean that the whole implementation +has to fit into a single Python file. + +One of the design decisions with Flask was that simple tasks should be +simple and not take up a lot of code and yet not limit yourself. Because +of that we took a few design choices that some people might find +surprising or unorthodox. For example, Flask uses thread-local objects +internally so that you don't have to pass objects around from function to +function within a request in order to stay threadsafe. While this is a +really easy approach and saves you a lot of time, it might also cause some +troubles for very large applications because changes on these thread-local +objects can happen anywhere in the same thread. In order to solve these +problems we don't hide the thread locals for you but instead embrace them +and provide you with a lot of tools to make it as pleasant as possible to +work with them. Flask is also based on convention over configuration, which means that many things are preconfigured. For example, by convention, templates and static files are in subdirectories within the Python source tree of the -application. +application. While this can be changed you usually don't have to. The main reason however why Flask is called a "microframework" is the idea to keep the core simple but extensible. There is no database abstraction @@ -40,22 +43,15 @@ was implemented in Flask itself. There are currently extensions for object relational mappers, form validation, upload handling, various open authentication technologies and more. -However Flask is not much code and it is built on a very solid foundation -and with that it is very easy to adapt for large applications. If you are -interested in that, check out the :ref:`becomingbig` chapter. +Since Flask is based on a very solid foundation there is not a lot of code +in Flask itself. As such it's easy to adapt even for lage applications +and we are making sure that you can either configure it as much as +possible by subclassing things or by forking the entire codebase. If you +are interested in that, check out the :ref:`becomingbig` chapter. If you are curious about the Flask design principles, head over to the section about :ref:`design`. -A Framework and an Example --------------------------- - -Flask is not only a microframework; it is also an example. Based on -Flask, there will be a series of blog posts that explain how to create a -framework. Flask itself is just one way to implement a framework on top -of existing libraries. Unlike many other microframeworks, Flask does not -try to implement everything on its own; it reuses existing code. - Web Development is Dangerous ---------------------------- From 718ef4d699d1de79117f92ff16feed98c3a1aa11 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:32:28 +0200 Subject: [PATCH 38/43] Added an XXX to a comment to not miss removing deprecated code later --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 0943d10a..f4eb2188 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -91,7 +91,7 @@ class RequestContext(object): self.match_request() - # Support for deprecated functionality. This is doing away with + # XXX: Support for deprecated functionality. This is doing away with # Flask 1.0 blueprint = self.request.blueprint if blueprint is not None: From 08bf538fb4d173fffcda1397e8485b069c03e718 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:36:53 +0200 Subject: [PATCH 39/43] Added a note on the behaviour of the routing system --- docs/design.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/design.rst b/docs/design.rst index 1f391b8c..6ca363a6 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -79,6 +79,22 @@ Furthermore this design makes it possible to use a factory function to create the application which is very helpful for unittesting and similar things (:ref:`app-factories`). +The Routing System +------------------ + +Flask uses the Werkzeug routing system which has was designed to +automatically order routes by complexity. This means that you can declare +routes in arbitrary order and they will still work as expected. This is a +requirement if you want to properly implement decorator based routing +since decorators could be fired in undefined order when the application is +split into multiple modules. + +Another design decision with the Werkzeug routing system is that routes +in Werkzeug try to ensure that there is that URLs are unique. Werkzeug +will go quite far with that in that it will automatically redirect to a +canonical URL if a route is ambiguous. + + One Template Engine ------------------- From bb1567dae48fed50f9742b9136c8bd1b82b7bd55 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:42:06 +0200 Subject: [PATCH 40/43] Explained why os.getcwd is used for path finding --- flask/helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index d8f7ac63..8f2dccf4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -484,6 +484,8 @@ def get_root_path(import_name): directory = os.path.dirname(sys.modules[import_name].__file__) return os.path.abspath(directory) except AttributeError: + # this is necessary in case we are running from the interactive + # python shell. It will never be used for production code however return os.getcwd() @@ -499,6 +501,7 @@ def find_package(import_name): root_mod = sys.modules[import_name.split('.')[0]] package_path = getattr(root_mod, '__file__', None) if package_path is None: + # support for the interactive python shell package_path = os.getcwd() else: package_path = os.path.abspath(os.path.dirname(package_path)) From 87f50fdc6f01160d75fcc87569f0754b800fc59f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:44:46 +0200 Subject: [PATCH 41/43] Don't lie to the user about POST redirects --- flask/debughelpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index b4f73dd3..edf8c111 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -54,7 +54,8 @@ class FormDataRoutingRedirect(AssertionError): buf.append(' Make sure to directly send your %s-request to this URL ' 'since we can\'t make browsers or HTTP clients redirect ' - 'with form data.' % request.method) + 'with form data reliably or without user interaction.' % + request.method) buf.append('\n\nNote: this exception is only raised in debug mode') AssertionError.__init__(self, ''.join(buf).encode('utf-8')) From 23bf2633f67d00418bd31c0c6918c3a99f06dead Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 29 Aug 2011 12:18:25 +0200 Subject: [PATCH 42/43] Use the _request_ctx_stack instead of the proxy for consistency with the others. --- flask/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 1136469f..609ab952 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1397,7 +1397,7 @@ class Flask(_PackageBoundObject): This also triggers the :meth:`url_value_processor` functions before the actualy :meth:`before_request` functions are called. """ - bp = request.blueprint + bp = _request_ctx_stack.top.request.blueprint funcs = self.url_value_preprocessors.get(None, ()) if bp is not None and bp in self.url_value_preprocessors: @@ -1447,7 +1447,7 @@ class Flask(_PackageBoundObject): tighter control over certain resources under testing environments. """ funcs = reversed(self.teardown_request_funcs.get(None, ())) - bp = request.blueprint + bp = _request_ctx_stack.top.request.blueprint if bp is not None and bp in self.teardown_request_funcs: funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) exc = sys.exc_info()[1] From ccf464189b116ea4ee458c2ccb24d64f9272e25b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 30 Aug 2011 14:36:50 +0200 Subject: [PATCH 43/43] Added finer control over the session cookie parameters --- CHANGES | 1 + docs/config.rst | 18 +++++++++++++++++- flask/app.py | 6 +++++- flask/sessions.py | 28 ++++++++++++++++++++++++---- flask/testsuite/basic.py | 22 ++++++++++++++++++++++ 5 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index b39a2001..2f47eab1 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Relase date to be decided, codename to be chosen. pluggable (class based) views. - Fixed an issue where the test client if used with the with statement did not trigger the execution of the teardown handlers. +- Added finer control over the session cookie parameters. Version 0.7.3 ------------- diff --git a/docs/config.rst b/docs/config.rst index df31aba0..1ed004d2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -70,6 +70,20 @@ The following configuration values are used internally by Flask: very risky). ``SECRET_KEY`` the secret key ``SESSION_COOKIE_NAME`` the name of the session cookie +``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If + this is not set, the cookie will be + valid for all subdomains of + ``SERVER_NAME``. +``SESSION_COOKIE_PATH`` the path for the session cookie. If + this is not set the cookie will be valid + for all of ``APPLICATION_ROOT`` or if + that is not set for ``'/'``. +``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set + with the httponly flag. Defaults to + `True`. +``SESSION_COOKIE_SECURE`` controls if the cookie should be set + with the secure flag. Defaults to + `False`. ``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as :class:`datetime.timedelta` object. ``USE_X_SENDFILE`` enable/disable x-sendfile @@ -142,7 +156,9 @@ The following configuration values are used internally by Flask: .. versionadded:: 0.8 ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, - ``APPLICATION_ROOT`` + ``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``, + ``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``, + ``SESSION_COOKIE_SECURE`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 609ab952..3c479df5 100644 --- a/flask/app.py +++ b/flask/app.py @@ -231,12 +231,16 @@ class Flask(_PackageBoundObject): 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, - 'SESSION_COOKIE_NAME': 'session', 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'SERVER_NAME': None, 'APPLICATION_ROOT': None, + 'SESSION_COOKIE_NAME': 'session', + 'SESSION_COOKIE_DOMAIN': None, + 'SESSION_COOKIE_PATH': None, + 'SESSION_COOKIE_HTTPONLY': True, + 'SESSION_COOKIE_SECURE': False, 'MAX_CONTENT_LENGTH': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False diff --git a/flask/sessions.py b/flask/sessions.py index fda84a25..8a9fae51 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -123,16 +123,33 @@ class SessionInterface(object): """Helpful helper method that returns the cookie domain that should be used for the session cookie if session cookies are used. """ + if app.config['SESSION_COOKIE_DOMAIN'] is not None: + return app.config['SESSION_COOKIE_DOMAIN'] if app.config['SERVER_NAME'] is not None: # chop of the port which is usually not supported by browsers return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] def get_cookie_path(self, app): """Returns the path for which the cookie should be valid. The - default implementation uses the value from the ``APPLICATION_ROOT`` - configuration variable or uses ``/`` if it's `None`. + default implementation uses the value from the SESSION_COOKIE_PATH`` + config var if it's set, and falls back to ``APPLICATION_ROOT`` or + uses ``/`` if it's `None`. """ - return app.config['APPLICATION_ROOT'] or '/' + return app.config['SESSION_COOKIE_PATH'] or \ + app.config['APPLICATION_ROOT'] or '/' + + def get_cookie_httponly(self, app): + """Returns True if the session cookie should be httponly. This + currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` + config var. + """ + return app.config['SESSION_COOKIE_HTTPONLY'] + + def get_cookie_secure(self, app): + """Returns True if the cookie should be secure. This currently + just returns the value of the ``SESSION_COOKIE_SECURE`` setting. + """ + return app.config['SESSION_COOKIE_SECURE'] def get_expiration_time(self, app, session): """A helper method that returns an expiration date for the session @@ -177,9 +194,12 @@ class SecureCookieSessionInterface(SessionInterface): expires = self.get_expiration_time(app, session) domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) + httponly = self.get_cookie_httponly(app) + secure = self.get_cookie_secure(app) if session.modified and not session: response.delete_cookie(app.session_cookie_name, path=path, domain=domain) else: session.save_cookie(response, app.session_cookie_name, path=path, - expires=expires, httponly=True, domain=domain) + expires=expires, httponly=httponly, + secure=secure, domain=domain) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 947d0073..33975b99 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -207,6 +207,28 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = app.test_client().get('/', 'http://example.com:8080/') self.assert_('path=/bar' in rv.headers['set-cookie'].lower()) + def test_session_using_session_settings(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='www.example.com:8080', + APPLICATION_ROOT='/test', + SESSION_COOKIE_DOMAIN='.example.com', + SESSION_COOKIE_HTTPONLY=False, + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_PATH='/' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://www.example.com:8080/test/') + cookie = rv.headers['set-cookie'].lower() + self.assert_('domain=.example.com' in cookie) + self.assert_('path=/;' in cookie) + self.assert_('secure' in cookie) + self.assert_('httponly' not in cookie) + def test_missing_session(self): app = flask.Flask(__name__) def expect_exception(f, *args, **kwargs):