Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
12
venv/Lib/site-packages/tornado/test/__main__.py
Normal file
12
venv/Lib/site-packages/tornado/test/__main__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
"""Shim to allow python -m tornado.test.
|
||||
|
||||
This only works in python 2.7+.
|
||||
"""
|
||||
from tornado.test.runtests import all, main
|
||||
|
||||
# tornado.testing.main autodiscovery relies on 'all' being present in
|
||||
# the main module, so import it here even though it is not used directly.
|
||||
# The following line prevents a pyflakes warning.
|
||||
all = all
|
||||
|
||||
main()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
190
venv/Lib/site-packages/tornado/test/asyncio_test.py
Normal file
190
venv/Lib/site-packages/tornado/test/asyncio_test.py
Normal file
|
@ -0,0 +1,190 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import asyncio
|
||||
import unittest
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from tornado import gen
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.platform.asyncio import (
|
||||
AsyncIOLoop,
|
||||
to_asyncio_future,
|
||||
AnyThreadEventLoopPolicy,
|
||||
)
|
||||
from tornado.testing import AsyncTestCase, gen_test
|
||||
|
||||
|
||||
class AsyncIOLoopTest(AsyncTestCase):
|
||||
def get_new_ioloop(self):
|
||||
io_loop = AsyncIOLoop()
|
||||
return io_loop
|
||||
|
||||
def test_asyncio_callback(self):
|
||||
# Basic test that the asyncio loop is set up correctly.
|
||||
asyncio.get_event_loop().call_soon(self.stop)
|
||||
self.wait()
|
||||
|
||||
@gen_test
|
||||
def test_asyncio_future(self):
|
||||
# Test that we can yield an asyncio future from a tornado coroutine.
|
||||
# Without 'yield from', we must wrap coroutines in ensure_future,
|
||||
# which was introduced during Python 3.4, deprecating the prior "async".
|
||||
if hasattr(asyncio, "ensure_future"):
|
||||
ensure_future = asyncio.ensure_future
|
||||
else:
|
||||
# async is a reserved word in Python 3.7
|
||||
ensure_future = getattr(asyncio, "async")
|
||||
|
||||
x = yield ensure_future(
|
||||
asyncio.get_event_loop().run_in_executor(None, lambda: 42)
|
||||
)
|
||||
self.assertEqual(x, 42)
|
||||
|
||||
@gen_test
|
||||
def test_asyncio_yield_from(self):
|
||||
@gen.coroutine
|
||||
def f():
|
||||
event_loop = asyncio.get_event_loop()
|
||||
x = yield from event_loop.run_in_executor(None, lambda: 42)
|
||||
return x
|
||||
|
||||
result = yield f()
|
||||
self.assertEqual(result, 42)
|
||||
|
||||
def test_asyncio_adapter(self):
|
||||
# This test demonstrates that when using the asyncio coroutine
|
||||
# runner (i.e. run_until_complete), the to_asyncio_future
|
||||
# adapter is needed. No adapter is needed in the other direction,
|
||||
# as demonstrated by other tests in the package.
|
||||
@gen.coroutine
|
||||
def tornado_coroutine():
|
||||
yield gen.moment
|
||||
raise gen.Return(42)
|
||||
|
||||
async def native_coroutine_without_adapter():
|
||||
return await tornado_coroutine()
|
||||
|
||||
async def native_coroutine_with_adapter():
|
||||
return await to_asyncio_future(tornado_coroutine())
|
||||
|
||||
# Use the adapter, but two degrees from the tornado coroutine.
|
||||
async def native_coroutine_with_adapter2():
|
||||
return await to_asyncio_future(native_coroutine_without_adapter())
|
||||
|
||||
# Tornado supports native coroutines both with and without adapters
|
||||
self.assertEqual(self.io_loop.run_sync(native_coroutine_without_adapter), 42)
|
||||
self.assertEqual(self.io_loop.run_sync(native_coroutine_with_adapter), 42)
|
||||
self.assertEqual(self.io_loop.run_sync(native_coroutine_with_adapter2), 42)
|
||||
|
||||
# Asyncio only supports coroutines that yield asyncio-compatible
|
||||
# Futures (which our Future is since 5.0).
|
||||
self.assertEqual(
|
||||
asyncio.get_event_loop().run_until_complete(
|
||||
native_coroutine_without_adapter()
|
||||
),
|
||||
42,
|
||||
)
|
||||
self.assertEqual(
|
||||
asyncio.get_event_loop().run_until_complete(
|
||||
native_coroutine_with_adapter()
|
||||
),
|
||||
42,
|
||||
)
|
||||
self.assertEqual(
|
||||
asyncio.get_event_loop().run_until_complete(
|
||||
native_coroutine_with_adapter2()
|
||||
),
|
||||
42,
|
||||
)
|
||||
|
||||
|
||||
class LeakTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Trigger a cleanup of the mapping so we start with a clean slate.
|
||||
AsyncIOLoop().close()
|
||||
# If we don't clean up after ourselves other tests may fail on
|
||||
# py34.
|
||||
self.orig_policy = asyncio.get_event_loop_policy()
|
||||
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
|
||||
|
||||
def tearDown(self):
|
||||
asyncio.get_event_loop().close()
|
||||
asyncio.set_event_loop_policy(self.orig_policy)
|
||||
|
||||
def test_ioloop_close_leak(self):
|
||||
orig_count = len(IOLoop._ioloop_for_asyncio)
|
||||
for i in range(10):
|
||||
# Create and close an AsyncIOLoop using Tornado interfaces.
|
||||
loop = AsyncIOLoop()
|
||||
loop.close()
|
||||
new_count = len(IOLoop._ioloop_for_asyncio) - orig_count
|
||||
self.assertEqual(new_count, 0)
|
||||
|
||||
def test_asyncio_close_leak(self):
|
||||
orig_count = len(IOLoop._ioloop_for_asyncio)
|
||||
for i in range(10):
|
||||
# Create and close an AsyncIOMainLoop using asyncio interfaces.
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.call_soon(IOLoop.current)
|
||||
loop.call_soon(loop.stop)
|
||||
loop.run_forever()
|
||||
loop.close()
|
||||
new_count = len(IOLoop._ioloop_for_asyncio) - orig_count
|
||||
# Because the cleanup is run on new loop creation, we have one
|
||||
# dangling entry in the map (but only one).
|
||||
self.assertEqual(new_count, 1)
|
||||
|
||||
|
||||
class AnyThreadEventLoopPolicyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.orig_policy = asyncio.get_event_loop_policy()
|
||||
self.executor = ThreadPoolExecutor(1)
|
||||
|
||||
def tearDown(self):
|
||||
asyncio.set_event_loop_policy(self.orig_policy)
|
||||
self.executor.shutdown()
|
||||
|
||||
def get_event_loop_on_thread(self):
|
||||
def get_and_close_event_loop():
|
||||
"""Get the event loop. Close it if one is returned.
|
||||
|
||||
Returns the (closed) event loop. This is a silly thing
|
||||
to do and leaves the thread in a broken state, but it's
|
||||
enough for this test. Closing the loop avoids resource
|
||||
leak warnings.
|
||||
"""
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.close()
|
||||
return loop
|
||||
|
||||
future = self.executor.submit(get_and_close_event_loop)
|
||||
return future.result()
|
||||
|
||||
def run_policy_test(self, accessor, expected_type):
|
||||
# With the default policy, non-main threads don't get an event
|
||||
# loop.
|
||||
self.assertRaises(
|
||||
(RuntimeError, AssertionError), self.executor.submit(accessor).result
|
||||
)
|
||||
# Set the policy and we can get a loop.
|
||||
asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
|
||||
self.assertIsInstance(self.executor.submit(accessor).result(), expected_type)
|
||||
# Clean up to silence leak warnings. Always use asyncio since
|
||||
# IOLoop doesn't (currently) close the underlying loop.
|
||||
self.executor.submit(lambda: asyncio.get_event_loop().close()).result() # type: ignore
|
||||
|
||||
def test_asyncio_accessor(self):
|
||||
self.run_policy_test(asyncio.get_event_loop, asyncio.AbstractEventLoop)
|
||||
|
||||
def test_tornado_accessor(self):
|
||||
self.run_policy_test(IOLoop.current, IOLoop)
|
609
venv/Lib/site-packages/tornado/test/auth_test.py
Normal file
609
venv/Lib/site-packages/tornado/test/auth_test.py
Normal file
|
@ -0,0 +1,609 @@
|
|||
# These tests do not currently do much to verify the correct implementation
|
||||
# of the openid/oauth protocols, they just exercise the major code paths
|
||||
# and ensure that it doesn't blow up (e.g. with unicode/bytes issues in
|
||||
# python 3)
|
||||
|
||||
import unittest
|
||||
|
||||
from tornado.auth import (
|
||||
OpenIdMixin,
|
||||
OAuthMixin,
|
||||
OAuth2Mixin,
|
||||
GoogleOAuth2Mixin,
|
||||
FacebookGraphMixin,
|
||||
TwitterMixin,
|
||||
)
|
||||
from tornado.escape import json_decode
|
||||
from tornado import gen
|
||||
from tornado.httpclient import HTTPClientError
|
||||
from tornado.httputil import url_concat
|
||||
from tornado.log import app_log
|
||||
from tornado.testing import AsyncHTTPTestCase, ExpectLog
|
||||
from tornado.web import RequestHandler, Application, HTTPError
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
mock = None # type: ignore
|
||||
|
||||
|
||||
class OpenIdClientLoginHandler(RequestHandler, OpenIdMixin):
|
||||
def initialize(self, test):
|
||||
self._OPENID_ENDPOINT = test.get_url("/openid/server/authenticate")
|
||||
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("openid.mode", None):
|
||||
user = yield self.get_authenticated_user(
|
||||
http_client=self.settings["http_client"]
|
||||
)
|
||||
if user is None:
|
||||
raise Exception("user is None")
|
||||
self.finish(user)
|
||||
return
|
||||
res = self.authenticate_redirect() # type: ignore
|
||||
assert res is None
|
||||
|
||||
|
||||
class OpenIdServerAuthenticateHandler(RequestHandler):
|
||||
def post(self):
|
||||
if self.get_argument("openid.mode") != "check_authentication":
|
||||
raise Exception("incorrect openid.mode %r")
|
||||
self.write("is_valid:true")
|
||||
|
||||
|
||||
class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin):
|
||||
def initialize(self, test, version):
|
||||
self._OAUTH_VERSION = version
|
||||
self._OAUTH_REQUEST_TOKEN_URL = test.get_url("/oauth1/server/request_token")
|
||||
self._OAUTH_AUTHORIZE_URL = test.get_url("/oauth1/server/authorize")
|
||||
self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/oauth1/server/access_token")
|
||||
|
||||
def _oauth_consumer_token(self):
|
||||
return dict(key="asdf", secret="qwer")
|
||||
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("oauth_token", None):
|
||||
user = yield self.get_authenticated_user(
|
||||
http_client=self.settings["http_client"]
|
||||
)
|
||||
if user is None:
|
||||
raise Exception("user is None")
|
||||
self.finish(user)
|
||||
return
|
||||
yield self.authorize_redirect(http_client=self.settings["http_client"])
|
||||
|
||||
@gen.coroutine
|
||||
def _oauth_get_user_future(self, access_token):
|
||||
if self.get_argument("fail_in_get_user", None):
|
||||
raise Exception("failing in get_user")
|
||||
if access_token != dict(key="uiop", secret="5678"):
|
||||
raise Exception("incorrect access token %r" % access_token)
|
||||
return dict(email="foo@example.com")
|
||||
|
||||
|
||||
class OAuth1ClientLoginCoroutineHandler(OAuth1ClientLoginHandler):
|
||||
"""Replaces OAuth1ClientLoginCoroutineHandler's get() with a coroutine."""
|
||||
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("oauth_token", None):
|
||||
# Ensure that any exceptions are set on the returned Future,
|
||||
# not simply thrown into the surrounding StackContext.
|
||||
try:
|
||||
yield self.get_authenticated_user()
|
||||
except Exception as e:
|
||||
self.set_status(503)
|
||||
self.write("got exception: %s" % e)
|
||||
else:
|
||||
yield self.authorize_redirect()
|
||||
|
||||
|
||||
class OAuth1ClientRequestParametersHandler(RequestHandler, OAuthMixin):
|
||||
def initialize(self, version):
|
||||
self._OAUTH_VERSION = version
|
||||
|
||||
def _oauth_consumer_token(self):
|
||||
return dict(key="asdf", secret="qwer")
|
||||
|
||||
def get(self):
|
||||
params = self._oauth_request_parameters(
|
||||
"http://www.example.com/api/asdf",
|
||||
dict(key="uiop", secret="5678"),
|
||||
parameters=dict(foo="bar"),
|
||||
)
|
||||
self.write(params)
|
||||
|
||||
|
||||
class OAuth1ServerRequestTokenHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("oauth_token=zxcv&oauth_token_secret=1234")
|
||||
|
||||
|
||||
class OAuth1ServerAccessTokenHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("oauth_token=uiop&oauth_token_secret=5678")
|
||||
|
||||
|
||||
class OAuth2ClientLoginHandler(RequestHandler, OAuth2Mixin):
|
||||
def initialize(self, test):
|
||||
self._OAUTH_AUTHORIZE_URL = test.get_url("/oauth2/server/authorize")
|
||||
|
||||
def get(self):
|
||||
res = self.authorize_redirect() # type: ignore
|
||||
assert res is None
|
||||
|
||||
|
||||
class FacebookClientLoginHandler(RequestHandler, FacebookGraphMixin):
|
||||
def initialize(self, test):
|
||||
self._OAUTH_AUTHORIZE_URL = test.get_url("/facebook/server/authorize")
|
||||
self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/facebook/server/access_token")
|
||||
self._FACEBOOK_BASE_URL = test.get_url("/facebook/server")
|
||||
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("code", None):
|
||||
user = yield self.get_authenticated_user(
|
||||
redirect_uri=self.request.full_url(),
|
||||
client_id=self.settings["facebook_api_key"],
|
||||
client_secret=self.settings["facebook_secret"],
|
||||
code=self.get_argument("code"),
|
||||
)
|
||||
self.write(user)
|
||||
else:
|
||||
self.authorize_redirect(
|
||||
redirect_uri=self.request.full_url(),
|
||||
client_id=self.settings["facebook_api_key"],
|
||||
extra_params={"scope": "read_stream,offline_access"},
|
||||
)
|
||||
|
||||
|
||||
class FacebookServerAccessTokenHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write(dict(access_token="asdf", expires_in=3600))
|
||||
|
||||
|
||||
class FacebookServerMeHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("{}")
|
||||
|
||||
|
||||
class TwitterClientHandler(RequestHandler, TwitterMixin):
|
||||
def initialize(self, test):
|
||||
self._OAUTH_REQUEST_TOKEN_URL = test.get_url("/oauth1/server/request_token")
|
||||
self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/twitter/server/access_token")
|
||||
self._OAUTH_AUTHORIZE_URL = test.get_url("/oauth1/server/authorize")
|
||||
self._OAUTH_AUTHENTICATE_URL = test.get_url("/twitter/server/authenticate")
|
||||
self._TWITTER_BASE_URL = test.get_url("/twitter/api")
|
||||
|
||||
def get_auth_http_client(self):
|
||||
return self.settings["http_client"]
|
||||
|
||||
|
||||
class TwitterClientLoginHandler(TwitterClientHandler):
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("oauth_token", None):
|
||||
user = yield self.get_authenticated_user()
|
||||
if user is None:
|
||||
raise Exception("user is None")
|
||||
self.finish(user)
|
||||
return
|
||||
yield self.authorize_redirect()
|
||||
|
||||
|
||||
class TwitterClientAuthenticateHandler(TwitterClientHandler):
|
||||
# Like TwitterClientLoginHandler, but uses authenticate_redirect
|
||||
# instead of authorize_redirect.
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("oauth_token", None):
|
||||
user = yield self.get_authenticated_user()
|
||||
if user is None:
|
||||
raise Exception("user is None")
|
||||
self.finish(user)
|
||||
return
|
||||
yield self.authenticate_redirect()
|
||||
|
||||
|
||||
class TwitterClientLoginGenCoroutineHandler(TwitterClientHandler):
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
if self.get_argument("oauth_token", None):
|
||||
user = yield self.get_authenticated_user()
|
||||
self.finish(user)
|
||||
else:
|
||||
# New style: with @gen.coroutine the result must be yielded
|
||||
# or else the request will be auto-finished too soon.
|
||||
yield self.authorize_redirect()
|
||||
|
||||
|
||||
class TwitterClientShowUserHandler(TwitterClientHandler):
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
# TODO: would be nice to go through the login flow instead of
|
||||
# cheating with a hard-coded access token.
|
||||
try:
|
||||
response = yield self.twitter_request(
|
||||
"/users/show/%s" % self.get_argument("name"),
|
||||
access_token=dict(key="hjkl", secret="vbnm"),
|
||||
)
|
||||
except HTTPClientError:
|
||||
# TODO(bdarnell): Should we catch HTTP errors and
|
||||
# transform some of them (like 403s) into AuthError?
|
||||
self.set_status(500)
|
||||
self.finish("error from twitter request")
|
||||
else:
|
||||
self.finish(response)
|
||||
|
||||
|
||||
class TwitterServerAccessTokenHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("oauth_token=hjkl&oauth_token_secret=vbnm&screen_name=foo")
|
||||
|
||||
|
||||
class TwitterServerShowUserHandler(RequestHandler):
|
||||
def get(self, screen_name):
|
||||
if screen_name == "error":
|
||||
raise HTTPError(500)
|
||||
assert "oauth_nonce" in self.request.arguments
|
||||
assert "oauth_timestamp" in self.request.arguments
|
||||
assert "oauth_signature" in self.request.arguments
|
||||
assert self.get_argument("oauth_consumer_key") == "test_twitter_consumer_key"
|
||||
assert self.get_argument("oauth_signature_method") == "HMAC-SHA1"
|
||||
assert self.get_argument("oauth_version") == "1.0"
|
||||
assert self.get_argument("oauth_token") == "hjkl"
|
||||
self.write(dict(screen_name=screen_name, name=screen_name.capitalize()))
|
||||
|
||||
|
||||
class TwitterServerVerifyCredentialsHandler(RequestHandler):
|
||||
def get(self):
|
||||
assert "oauth_nonce" in self.request.arguments
|
||||
assert "oauth_timestamp" in self.request.arguments
|
||||
assert "oauth_signature" in self.request.arguments
|
||||
assert self.get_argument("oauth_consumer_key") == "test_twitter_consumer_key"
|
||||
assert self.get_argument("oauth_signature_method") == "HMAC-SHA1"
|
||||
assert self.get_argument("oauth_version") == "1.0"
|
||||
assert self.get_argument("oauth_token") == "hjkl"
|
||||
self.write(dict(screen_name="foo", name="Foo"))
|
||||
|
||||
|
||||
class AuthTest(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return Application(
|
||||
[
|
||||
# test endpoints
|
||||
("/openid/client/login", OpenIdClientLoginHandler, dict(test=self)),
|
||||
(
|
||||
"/oauth10/client/login",
|
||||
OAuth1ClientLoginHandler,
|
||||
dict(test=self, version="1.0"),
|
||||
),
|
||||
(
|
||||
"/oauth10/client/request_params",
|
||||
OAuth1ClientRequestParametersHandler,
|
||||
dict(version="1.0"),
|
||||
),
|
||||
(
|
||||
"/oauth10a/client/login",
|
||||
OAuth1ClientLoginHandler,
|
||||
dict(test=self, version="1.0a"),
|
||||
),
|
||||
(
|
||||
"/oauth10a/client/login_coroutine",
|
||||
OAuth1ClientLoginCoroutineHandler,
|
||||
dict(test=self, version="1.0a"),
|
||||
),
|
||||
(
|
||||
"/oauth10a/client/request_params",
|
||||
OAuth1ClientRequestParametersHandler,
|
||||
dict(version="1.0a"),
|
||||
),
|
||||
("/oauth2/client/login", OAuth2ClientLoginHandler, dict(test=self)),
|
||||
("/facebook/client/login", FacebookClientLoginHandler, dict(test=self)),
|
||||
("/twitter/client/login", TwitterClientLoginHandler, dict(test=self)),
|
||||
(
|
||||
"/twitter/client/authenticate",
|
||||
TwitterClientAuthenticateHandler,
|
||||
dict(test=self),
|
||||
),
|
||||
(
|
||||
"/twitter/client/login_gen_coroutine",
|
||||
TwitterClientLoginGenCoroutineHandler,
|
||||
dict(test=self),
|
||||
),
|
||||
(
|
||||
"/twitter/client/show_user",
|
||||
TwitterClientShowUserHandler,
|
||||
dict(test=self),
|
||||
),
|
||||
# simulated servers
|
||||
("/openid/server/authenticate", OpenIdServerAuthenticateHandler),
|
||||
("/oauth1/server/request_token", OAuth1ServerRequestTokenHandler),
|
||||
("/oauth1/server/access_token", OAuth1ServerAccessTokenHandler),
|
||||
("/facebook/server/access_token", FacebookServerAccessTokenHandler),
|
||||
("/facebook/server/me", FacebookServerMeHandler),
|
||||
("/twitter/server/access_token", TwitterServerAccessTokenHandler),
|
||||
(r"/twitter/api/users/show/(.*)\.json", TwitterServerShowUserHandler),
|
||||
(
|
||||
r"/twitter/api/account/verify_credentials\.json",
|
||||
TwitterServerVerifyCredentialsHandler,
|
||||
),
|
||||
],
|
||||
http_client=self.http_client,
|
||||
twitter_consumer_key="test_twitter_consumer_key",
|
||||
twitter_consumer_secret="test_twitter_consumer_secret",
|
||||
facebook_api_key="test_facebook_api_key",
|
||||
facebook_secret="test_facebook_secret",
|
||||
)
|
||||
|
||||
def test_openid_redirect(self):
|
||||
response = self.fetch("/openid/client/login", follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue("/openid/server/authenticate?" in response.headers["Location"])
|
||||
|
||||
def test_openid_get_user(self):
|
||||
response = self.fetch(
|
||||
"/openid/client/login?openid.mode=blah"
|
||||
"&openid.ns.ax=http://openid.net/srv/ax/1.0"
|
||||
"&openid.ax.type.email=http://axschema.org/contact/email"
|
||||
"&openid.ax.value.email=foo@example.com"
|
||||
)
|
||||
response.rethrow()
|
||||
parsed = json_decode(response.body)
|
||||
self.assertEqual(parsed["email"], "foo@example.com")
|
||||
|
||||
def test_oauth10_redirect(self):
|
||||
response = self.fetch("/oauth10/client/login", follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue(
|
||||
response.headers["Location"].endswith(
|
||||
"/oauth1/server/authorize?oauth_token=zxcv"
|
||||
)
|
||||
)
|
||||
# the cookie is base64('zxcv')|base64('1234')
|
||||
self.assertTrue(
|
||||
'_oauth_request_token="enhjdg==|MTIzNA=="'
|
||||
in response.headers["Set-Cookie"],
|
||||
response.headers["Set-Cookie"],
|
||||
)
|
||||
|
||||
def test_oauth10_get_user(self):
|
||||
response = self.fetch(
|
||||
"/oauth10/client/login?oauth_token=zxcv",
|
||||
headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
|
||||
)
|
||||
response.rethrow()
|
||||
parsed = json_decode(response.body)
|
||||
self.assertEqual(parsed["email"], "foo@example.com")
|
||||
self.assertEqual(parsed["access_token"], dict(key="uiop", secret="5678"))
|
||||
|
||||
def test_oauth10_request_parameters(self):
|
||||
response = self.fetch("/oauth10/client/request_params")
|
||||
response.rethrow()
|
||||
parsed = json_decode(response.body)
|
||||
self.assertEqual(parsed["oauth_consumer_key"], "asdf")
|
||||
self.assertEqual(parsed["oauth_token"], "uiop")
|
||||
self.assertTrue("oauth_nonce" in parsed)
|
||||
self.assertTrue("oauth_signature" in parsed)
|
||||
|
||||
def test_oauth10a_redirect(self):
|
||||
response = self.fetch("/oauth10a/client/login", follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue(
|
||||
response.headers["Location"].endswith(
|
||||
"/oauth1/server/authorize?oauth_token=zxcv"
|
||||
)
|
||||
)
|
||||
# the cookie is base64('zxcv')|base64('1234')
|
||||
self.assertTrue(
|
||||
'_oauth_request_token="enhjdg==|MTIzNA=="'
|
||||
in response.headers["Set-Cookie"],
|
||||
response.headers["Set-Cookie"],
|
||||
)
|
||||
|
||||
@unittest.skipIf(mock is None, "mock package not present")
|
||||
def test_oauth10a_redirect_error(self):
|
||||
with mock.patch.object(OAuth1ServerRequestTokenHandler, "get") as get:
|
||||
get.side_effect = Exception("boom")
|
||||
with ExpectLog(app_log, "Uncaught exception"):
|
||||
response = self.fetch("/oauth10a/client/login", follow_redirects=False)
|
||||
self.assertEqual(response.code, 500)
|
||||
|
||||
def test_oauth10a_get_user(self):
|
||||
response = self.fetch(
|
||||
"/oauth10a/client/login?oauth_token=zxcv",
|
||||
headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
|
||||
)
|
||||
response.rethrow()
|
||||
parsed = json_decode(response.body)
|
||||
self.assertEqual(parsed["email"], "foo@example.com")
|
||||
self.assertEqual(parsed["access_token"], dict(key="uiop", secret="5678"))
|
||||
|
||||
def test_oauth10a_request_parameters(self):
|
||||
response = self.fetch("/oauth10a/client/request_params")
|
||||
response.rethrow()
|
||||
parsed = json_decode(response.body)
|
||||
self.assertEqual(parsed["oauth_consumer_key"], "asdf")
|
||||
self.assertEqual(parsed["oauth_token"], "uiop")
|
||||
self.assertTrue("oauth_nonce" in parsed)
|
||||
self.assertTrue("oauth_signature" in parsed)
|
||||
|
||||
def test_oauth10a_get_user_coroutine_exception(self):
|
||||
response = self.fetch(
|
||||
"/oauth10a/client/login_coroutine?oauth_token=zxcv&fail_in_get_user=true",
|
||||
headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
|
||||
)
|
||||
self.assertEqual(response.code, 503)
|
||||
|
||||
def test_oauth2_redirect(self):
|
||||
response = self.fetch("/oauth2/client/login", follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue("/oauth2/server/authorize?" in response.headers["Location"])
|
||||
|
||||
def test_facebook_login(self):
|
||||
response = self.fetch("/facebook/client/login", follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue("/facebook/server/authorize?" in response.headers["Location"])
|
||||
response = self.fetch(
|
||||
"/facebook/client/login?code=1234", follow_redirects=False
|
||||
)
|
||||
self.assertEqual(response.code, 200)
|
||||
user = json_decode(response.body)
|
||||
self.assertEqual(user["access_token"], "asdf")
|
||||
self.assertEqual(user["session_expires"], "3600")
|
||||
|
||||
def base_twitter_redirect(self, url):
|
||||
# Same as test_oauth10a_redirect
|
||||
response = self.fetch(url, follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue(
|
||||
response.headers["Location"].endswith(
|
||||
"/oauth1/server/authorize?oauth_token=zxcv"
|
||||
)
|
||||
)
|
||||
# the cookie is base64('zxcv')|base64('1234')
|
||||
self.assertTrue(
|
||||
'_oauth_request_token="enhjdg==|MTIzNA=="'
|
||||
in response.headers["Set-Cookie"],
|
||||
response.headers["Set-Cookie"],
|
||||
)
|
||||
|
||||
def test_twitter_redirect(self):
|
||||
self.base_twitter_redirect("/twitter/client/login")
|
||||
|
||||
def test_twitter_redirect_gen_coroutine(self):
|
||||
self.base_twitter_redirect("/twitter/client/login_gen_coroutine")
|
||||
|
||||
def test_twitter_authenticate_redirect(self):
|
||||
response = self.fetch("/twitter/client/authenticate", follow_redirects=False)
|
||||
self.assertEqual(response.code, 302)
|
||||
self.assertTrue(
|
||||
response.headers["Location"].endswith(
|
||||
"/twitter/server/authenticate?oauth_token=zxcv"
|
||||
),
|
||||
response.headers["Location"],
|
||||
)
|
||||
# the cookie is base64('zxcv')|base64('1234')
|
||||
self.assertTrue(
|
||||
'_oauth_request_token="enhjdg==|MTIzNA=="'
|
||||
in response.headers["Set-Cookie"],
|
||||
response.headers["Set-Cookie"],
|
||||
)
|
||||
|
||||
def test_twitter_get_user(self):
|
||||
response = self.fetch(
|
||||
"/twitter/client/login?oauth_token=zxcv",
|
||||
headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
|
||||
)
|
||||
response.rethrow()
|
||||
parsed = json_decode(response.body)
|
||||
self.assertEqual(
|
||||
parsed,
|
||||
{
|
||||
u"access_token": {
|
||||
u"key": u"hjkl",
|
||||
u"screen_name": u"foo",
|
||||
u"secret": u"vbnm",
|
||||
},
|
||||
u"name": u"Foo",
|
||||
u"screen_name": u"foo",
|
||||
u"username": u"foo",
|
||||
},
|
||||
)
|
||||
|
||||
def test_twitter_show_user(self):
|
||||
response = self.fetch("/twitter/client/show_user?name=somebody")
|
||||
response.rethrow()
|
||||
self.assertEqual(
|
||||
json_decode(response.body), {"name": "Somebody", "screen_name": "somebody"}
|
||||
)
|
||||
|
||||
def test_twitter_show_user_error(self):
|
||||
response = self.fetch("/twitter/client/show_user?name=error")
|
||||
self.assertEqual(response.code, 500)
|
||||
self.assertEqual(response.body, b"error from twitter request")
|
||||
|
||||
|
||||
class GoogleLoginHandler(RequestHandler, GoogleOAuth2Mixin):
|
||||
def initialize(self, test):
|
||||
self.test = test
|
||||
self._OAUTH_REDIRECT_URI = test.get_url("/client/login")
|
||||
self._OAUTH_AUTHORIZE_URL = test.get_url("/google/oauth2/authorize")
|
||||
self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/google/oauth2/token")
|
||||
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
code = self.get_argument("code", None)
|
||||
if code is not None:
|
||||
# retrieve authenticate google user
|
||||
access = yield self.get_authenticated_user(self._OAUTH_REDIRECT_URI, code)
|
||||
user = yield self.oauth2_request(
|
||||
self.test.get_url("/google/oauth2/userinfo"),
|
||||
access_token=access["access_token"],
|
||||
)
|
||||
# return the user and access token as json
|
||||
user["access_token"] = access["access_token"]
|
||||
self.write(user)
|
||||
else:
|
||||
self.authorize_redirect(
|
||||
redirect_uri=self._OAUTH_REDIRECT_URI,
|
||||
client_id=self.settings["google_oauth"]["key"],
|
||||
client_secret=self.settings["google_oauth"]["secret"],
|
||||
scope=["profile", "email"],
|
||||
response_type="code",
|
||||
extra_params={"prompt": "select_account"},
|
||||
)
|
||||
|
||||
|
||||
class GoogleOAuth2AuthorizeHandler(RequestHandler):
|
||||
def get(self):
|
||||
# issue a fake auth code and redirect to redirect_uri
|
||||
code = "fake-authorization-code"
|
||||
self.redirect(url_concat(self.get_argument("redirect_uri"), dict(code=code)))
|
||||
|
||||
|
||||
class GoogleOAuth2TokenHandler(RequestHandler):
|
||||
def post(self):
|
||||
assert self.get_argument("code") == "fake-authorization-code"
|
||||
# issue a fake token
|
||||
self.finish(
|
||||
{"access_token": "fake-access-token", "expires_in": "never-expires"}
|
||||
)
|
||||
|
||||
|
||||
class GoogleOAuth2UserinfoHandler(RequestHandler):
|
||||
def get(self):
|
||||
assert self.get_argument("access_token") == "fake-access-token"
|
||||
# return a fake user
|
||||
self.finish({"name": "Foo", "email": "foo@example.com"})
|
||||
|
||||
|
||||
class GoogleOAuth2Test(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return Application(
|
||||
[
|
||||
# test endpoints
|
||||
("/client/login", GoogleLoginHandler, dict(test=self)),
|
||||
# simulated google authorization server endpoints
|
||||
("/google/oauth2/authorize", GoogleOAuth2AuthorizeHandler),
|
||||
("/google/oauth2/token", GoogleOAuth2TokenHandler),
|
||||
("/google/oauth2/userinfo", GoogleOAuth2UserinfoHandler),
|
||||
],
|
||||
google_oauth={
|
||||
"key": "fake_google_client_id",
|
||||
"secret": "fake_google_client_secret",
|
||||
},
|
||||
)
|
||||
|
||||
def test_google_login(self):
|
||||
response = self.fetch("/client/login")
|
||||
self.assertDictEqual(
|
||||
{
|
||||
u"name": u"Foo",
|
||||
u"email": u"foo@example.com",
|
||||
u"access_token": u"fake-access-token",
|
||||
},
|
||||
json_decode(response.body),
|
||||
)
|
127
venv/Lib/site-packages/tornado/test/autoreload_test.py
Normal file
127
venv/Lib/site-packages/tornado/test/autoreload_test.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from subprocess import Popen
|
||||
import sys
|
||||
from tempfile import mkdtemp
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
||||
class AutoreloadTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.path = mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
shutil.rmtree(self.path)
|
||||
except OSError:
|
||||
# Windows disallows deleting files that are in use by
|
||||
# another process, and even though we've waited for our
|
||||
# child process below, it appears that its lock on these
|
||||
# files is not guaranteed to be released by this point.
|
||||
# Sleep and try again (once).
|
||||
time.sleep(1)
|
||||
shutil.rmtree(self.path)
|
||||
|
||||
def test_reload_module(self):
|
||||
main = """\
|
||||
import os
|
||||
import sys
|
||||
|
||||
from tornado import autoreload
|
||||
|
||||
# This import will fail if path is not set up correctly
|
||||
import testapp
|
||||
|
||||
print('Starting')
|
||||
if 'TESTAPP_STARTED' not in os.environ:
|
||||
os.environ['TESTAPP_STARTED'] = '1'
|
||||
sys.stdout.flush()
|
||||
autoreload._reload()
|
||||
"""
|
||||
|
||||
# Create temporary test application
|
||||
os.mkdir(os.path.join(self.path, "testapp"))
|
||||
open(os.path.join(self.path, "testapp/__init__.py"), "w").close()
|
||||
with open(os.path.join(self.path, "testapp/__main__.py"), "w") as f:
|
||||
f.write(main)
|
||||
|
||||
# Make sure the tornado module under test is available to the test
|
||||
# application
|
||||
pythonpath = os.getcwd()
|
||||
if "PYTHONPATH" in os.environ:
|
||||
pythonpath += os.pathsep + os.environ["PYTHONPATH"]
|
||||
|
||||
p = Popen(
|
||||
[sys.executable, "-m", "testapp"],
|
||||
stdout=subprocess.PIPE,
|
||||
cwd=self.path,
|
||||
env=dict(os.environ, PYTHONPATH=pythonpath),
|
||||
universal_newlines=True,
|
||||
)
|
||||
out = p.communicate()[0]
|
||||
self.assertEqual(out, "Starting\nStarting\n")
|
||||
|
||||
def test_reload_wrapper_preservation(self):
|
||||
# This test verifies that when `python -m tornado.autoreload`
|
||||
# is used on an application that also has an internal
|
||||
# autoreload, the reload wrapper is preserved on restart.
|
||||
main = """\
|
||||
import os
|
||||
import sys
|
||||
|
||||
# This import will fail if path is not set up correctly
|
||||
import testapp
|
||||
|
||||
if 'tornado.autoreload' not in sys.modules:
|
||||
raise Exception('started without autoreload wrapper')
|
||||
|
||||
import tornado.autoreload
|
||||
|
||||
print('Starting')
|
||||
sys.stdout.flush()
|
||||
if 'TESTAPP_STARTED' not in os.environ:
|
||||
os.environ['TESTAPP_STARTED'] = '1'
|
||||
# Simulate an internal autoreload (one not caused
|
||||
# by the wrapper).
|
||||
tornado.autoreload._reload()
|
||||
else:
|
||||
# Exit directly so autoreload doesn't catch it.
|
||||
os._exit(0)
|
||||
"""
|
||||
|
||||
# Create temporary test application
|
||||
os.mkdir(os.path.join(self.path, "testapp"))
|
||||
init_file = os.path.join(self.path, "testapp", "__init__.py")
|
||||
open(init_file, "w").close()
|
||||
main_file = os.path.join(self.path, "testapp", "__main__.py")
|
||||
with open(main_file, "w") as f:
|
||||
f.write(main)
|
||||
|
||||
# Make sure the tornado module under test is available to the test
|
||||
# application
|
||||
pythonpath = os.getcwd()
|
||||
if "PYTHONPATH" in os.environ:
|
||||
pythonpath += os.pathsep + os.environ["PYTHONPATH"]
|
||||
|
||||
autoreload_proc = Popen(
|
||||
[sys.executable, "-m", "tornado.autoreload", "-m", "testapp"],
|
||||
stdout=subprocess.PIPE,
|
||||
cwd=self.path,
|
||||
env=dict(os.environ, PYTHONPATH=pythonpath),
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
# This timeout needs to be fairly generous for pypy due to jit
|
||||
# warmup costs.
|
||||
for i in range(40):
|
||||
if autoreload_proc.poll() is not None:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
autoreload_proc.kill()
|
||||
raise Exception("subprocess failed to terminate")
|
||||
|
||||
out = autoreload_proc.communicate()[0]
|
||||
self.assertEqual(out, "Starting\n" * 2)
|
212
venv/Lib/site-packages/tornado/test/concurrent_test.py
Normal file
212
venv/Lib/site-packages/tornado/test/concurrent_test.py
Normal file
|
@ -0,0 +1,212 @@
|
|||
#
|
||||
# Copyright 2012 Facebook
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from concurrent import futures
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import typing
|
||||
import unittest
|
||||
|
||||
from tornado.concurrent import (
|
||||
Future,
|
||||
run_on_executor,
|
||||
future_set_result_unless_cancelled,
|
||||
)
|
||||
from tornado.escape import utf8, to_unicode
|
||||
from tornado import gen
|
||||
from tornado.iostream import IOStream
|
||||
from tornado.tcpserver import TCPServer
|
||||
from tornado.testing import AsyncTestCase, bind_unused_port, gen_test
|
||||
|
||||
|
||||
class MiscFutureTest(AsyncTestCase):
|
||||
def test_future_set_result_unless_cancelled(self):
|
||||
fut = Future() # type: Future[int]
|
||||
future_set_result_unless_cancelled(fut, 42)
|
||||
self.assertEqual(fut.result(), 42)
|
||||
self.assertFalse(fut.cancelled())
|
||||
|
||||
fut = Future()
|
||||
fut.cancel()
|
||||
is_cancelled = fut.cancelled()
|
||||
future_set_result_unless_cancelled(fut, 42)
|
||||
self.assertEqual(fut.cancelled(), is_cancelled)
|
||||
if not is_cancelled:
|
||||
self.assertEqual(fut.result(), 42)
|
||||
|
||||
|
||||
# The following series of classes demonstrate and test various styles
|
||||
# of use, with and without generators and futures.
|
||||
|
||||
|
||||
class CapServer(TCPServer):
|
||||
@gen.coroutine
|
||||
def handle_stream(self, stream, address):
|
||||
data = yield stream.read_until(b"\n")
|
||||
data = to_unicode(data)
|
||||
if data == data.upper():
|
||||
stream.write(b"error\talready capitalized\n")
|
||||
else:
|
||||
# data already has \n
|
||||
stream.write(utf8("ok\t%s" % data.upper()))
|
||||
stream.close()
|
||||
|
||||
|
||||
class CapError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseCapClient(object):
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
|
||||
def process_response(self, data):
|
||||
m = re.match("(.*)\t(.*)\n", to_unicode(data))
|
||||
if m is None:
|
||||
raise Exception("did not match")
|
||||
status, message = m.groups()
|
||||
if status == "ok":
|
||||
return message
|
||||
else:
|
||||
raise CapError(message)
|
||||
|
||||
|
||||
class GeneratorCapClient(BaseCapClient):
|
||||
@gen.coroutine
|
||||
def capitalize(self, request_data):
|
||||
logging.debug("capitalize")
|
||||
stream = IOStream(socket.socket())
|
||||
logging.debug("connecting")
|
||||
yield stream.connect(("127.0.0.1", self.port))
|
||||
stream.write(utf8(request_data + "\n"))
|
||||
logging.debug("reading")
|
||||
data = yield stream.read_until(b"\n")
|
||||
logging.debug("returning")
|
||||
stream.close()
|
||||
raise gen.Return(self.process_response(data))
|
||||
|
||||
|
||||
class ClientTestMixin(object):
|
||||
client_class = None # type: typing.Callable
|
||||
|
||||
def setUp(self):
|
||||
super().setUp() # type: ignore
|
||||
self.server = CapServer()
|
||||
sock, port = bind_unused_port()
|
||||
self.server.add_sockets([sock])
|
||||
self.client = self.client_class(port=port)
|
||||
|
||||
def tearDown(self):
|
||||
self.server.stop()
|
||||
super().tearDown() # type: ignore
|
||||
|
||||
def test_future(self: typing.Any):
|
||||
future = self.client.capitalize("hello")
|
||||
self.io_loop.add_future(future, self.stop)
|
||||
self.wait()
|
||||
self.assertEqual(future.result(), "HELLO")
|
||||
|
||||
def test_future_error(self: typing.Any):
|
||||
future = self.client.capitalize("HELLO")
|
||||
self.io_loop.add_future(future, self.stop)
|
||||
self.wait()
|
||||
self.assertRaisesRegexp(CapError, "already capitalized", future.result) # type: ignore
|
||||
|
||||
def test_generator(self: typing.Any):
|
||||
@gen.coroutine
|
||||
def f():
|
||||
result = yield self.client.capitalize("hello")
|
||||
self.assertEqual(result, "HELLO")
|
||||
|
||||
self.io_loop.run_sync(f)
|
||||
|
||||
def test_generator_error(self: typing.Any):
|
||||
@gen.coroutine
|
||||
def f():
|
||||
with self.assertRaisesRegexp(CapError, "already capitalized"):
|
||||
yield self.client.capitalize("HELLO")
|
||||
|
||||
self.io_loop.run_sync(f)
|
||||
|
||||
|
||||
class GeneratorClientTest(ClientTestMixin, AsyncTestCase):
|
||||
client_class = GeneratorCapClient
|
||||
|
||||
|
||||
class RunOnExecutorTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_no_calling(self):
|
||||
class Object(object):
|
||||
def __init__(self):
|
||||
self.executor = futures.thread.ThreadPoolExecutor(1)
|
||||
|
||||
@run_on_executor
|
||||
def f(self):
|
||||
return 42
|
||||
|
||||
o = Object()
|
||||
answer = yield o.f()
|
||||
self.assertEqual(answer, 42)
|
||||
|
||||
@gen_test
|
||||
def test_call_with_no_args(self):
|
||||
class Object(object):
|
||||
def __init__(self):
|
||||
self.executor = futures.thread.ThreadPoolExecutor(1)
|
||||
|
||||
@run_on_executor()
|
||||
def f(self):
|
||||
return 42
|
||||
|
||||
o = Object()
|
||||
answer = yield o.f()
|
||||
self.assertEqual(answer, 42)
|
||||
|
||||
@gen_test
|
||||
def test_call_with_executor(self):
|
||||
class Object(object):
|
||||
def __init__(self):
|
||||
self.__executor = futures.thread.ThreadPoolExecutor(1)
|
||||
|
||||
@run_on_executor(executor="_Object__executor")
|
||||
def f(self):
|
||||
return 42
|
||||
|
||||
o = Object()
|
||||
answer = yield o.f()
|
||||
self.assertEqual(answer, 42)
|
||||
|
||||
@gen_test
|
||||
def test_async_await(self):
|
||||
class Object(object):
|
||||
def __init__(self):
|
||||
self.executor = futures.thread.ThreadPoolExecutor(1)
|
||||
|
||||
@run_on_executor()
|
||||
def f(self):
|
||||
return 42
|
||||
|
||||
o = Object()
|
||||
|
||||
async def f():
|
||||
answer = await o.f()
|
||||
return answer
|
||||
|
||||
result = yield f()
|
||||
self.assertEqual(result, 42)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -0,0 +1 @@
|
|||
"school","école"
|
|
129
venv/Lib/site-packages/tornado/test/curl_httpclient_test.py
Normal file
129
venv/Lib/site-packages/tornado/test/curl_httpclient_test.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
from hashlib import md5
|
||||
import unittest
|
||||
|
||||
from tornado.escape import utf8
|
||||
from tornado.testing import AsyncHTTPTestCase
|
||||
from tornado.test import httpclient_test
|
||||
from tornado.web import Application, RequestHandler
|
||||
|
||||
|
||||
try:
|
||||
import pycurl
|
||||
except ImportError:
|
||||
pycurl = None # type: ignore
|
||||
|
||||
if pycurl is not None:
|
||||
from tornado.curl_httpclient import CurlAsyncHTTPClient
|
||||
|
||||
|
||||
@unittest.skipIf(pycurl is None, "pycurl module not present")
|
||||
class CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):
|
||||
def get_http_client(self):
|
||||
client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))
|
||||
# make sure AsyncHTTPClient magic doesn't give us the wrong class
|
||||
self.assertTrue(isinstance(client, CurlAsyncHTTPClient))
|
||||
return client
|
||||
|
||||
|
||||
class DigestAuthHandler(RequestHandler):
|
||||
def initialize(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def get(self):
|
||||
realm = "test"
|
||||
opaque = "asdf"
|
||||
# Real implementations would use a random nonce.
|
||||
nonce = "1234"
|
||||
|
||||
auth_header = self.request.headers.get("Authorization", None)
|
||||
if auth_header is not None:
|
||||
auth_mode, params = auth_header.split(" ", 1)
|
||||
assert auth_mode == "Digest"
|
||||
param_dict = {}
|
||||
for pair in params.split(","):
|
||||
k, v = pair.strip().split("=", 1)
|
||||
if v[0] == '"' and v[-1] == '"':
|
||||
v = v[1:-1]
|
||||
param_dict[k] = v
|
||||
assert param_dict["realm"] == realm
|
||||
assert param_dict["opaque"] == opaque
|
||||
assert param_dict["nonce"] == nonce
|
||||
assert param_dict["username"] == self.username
|
||||
assert param_dict["uri"] == self.request.path
|
||||
h1 = md5(
|
||||
utf8("%s:%s:%s" % (self.username, realm, self.password))
|
||||
).hexdigest()
|
||||
h2 = md5(
|
||||
utf8("%s:%s" % (self.request.method, self.request.path))
|
||||
).hexdigest()
|
||||
digest = md5(utf8("%s:%s:%s" % (h1, nonce, h2))).hexdigest()
|
||||
if digest == param_dict["response"]:
|
||||
self.write("ok")
|
||||
else:
|
||||
self.write("fail")
|
||||
else:
|
||||
self.set_status(401)
|
||||
self.set_header(
|
||||
"WWW-Authenticate",
|
||||
'Digest realm="%s", nonce="%s", opaque="%s"' % (realm, nonce, opaque),
|
||||
)
|
||||
|
||||
|
||||
class CustomReasonHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.set_status(200, "Custom reason")
|
||||
|
||||
|
||||
class CustomFailReasonHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.set_status(400, "Custom reason")
|
||||
|
||||
|
||||
@unittest.skipIf(pycurl is None, "pycurl module not present")
|
||||
class CurlHTTPClientTestCase(AsyncHTTPTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.http_client = self.create_client()
|
||||
|
||||
def get_app(self):
|
||||
return Application(
|
||||
[
|
||||
("/digest", DigestAuthHandler, {"username": "foo", "password": "bar"}),
|
||||
(
|
||||
"/digest_non_ascii",
|
||||
DigestAuthHandler,
|
||||
{"username": "foo", "password": "barユ£"},
|
||||
),
|
||||
("/custom_reason", CustomReasonHandler),
|
||||
("/custom_fail_reason", CustomFailReasonHandler),
|
||||
]
|
||||
)
|
||||
|
||||
def create_client(self, **kwargs):
|
||||
return CurlAsyncHTTPClient(
|
||||
force_instance=True, defaults=dict(allow_ipv6=False), **kwargs
|
||||
)
|
||||
|
||||
def test_digest_auth(self):
|
||||
response = self.fetch(
|
||||
"/digest", auth_mode="digest", auth_username="foo", auth_password="bar"
|
||||
)
|
||||
self.assertEqual(response.body, b"ok")
|
||||
|
||||
def test_custom_reason(self):
|
||||
response = self.fetch("/custom_reason")
|
||||
self.assertEqual(response.reason, "Custom reason")
|
||||
|
||||
def test_fail_custom_reason(self):
|
||||
response = self.fetch("/custom_fail_reason")
|
||||
self.assertEqual(str(response.error), "HTTP 400: Custom reason")
|
||||
|
||||
def test_digest_auth_non_ascii(self):
|
||||
response = self.fetch(
|
||||
"/digest_non_ascii",
|
||||
auth_mode="digest",
|
||||
auth_username="foo",
|
||||
auth_password="barユ£",
|
||||
)
|
||||
self.assertEqual(response.body, b"ok")
|
322
venv/Lib/site-packages/tornado/test/escape_test.py
Normal file
322
venv/Lib/site-packages/tornado/test/escape_test.py
Normal file
|
@ -0,0 +1,322 @@
|
|||
import unittest
|
||||
|
||||
import tornado.escape
|
||||
from tornado.escape import (
|
||||
utf8,
|
||||
xhtml_escape,
|
||||
xhtml_unescape,
|
||||
url_escape,
|
||||
url_unescape,
|
||||
to_unicode,
|
||||
json_decode,
|
||||
json_encode,
|
||||
squeeze,
|
||||
recursive_unicode,
|
||||
)
|
||||
from tornado.util import unicode_type
|
||||
|
||||
from typing import List, Tuple, Union, Dict, Any # noqa: F401
|
||||
|
||||
linkify_tests = [
|
||||
# (input, linkify_kwargs, expected_output)
|
||||
(
|
||||
"hello http://world.com/!",
|
||||
{},
|
||||
u'hello <a href="http://world.com/">http://world.com/</a>!',
|
||||
),
|
||||
(
|
||||
"hello http://world.com/with?param=true&stuff=yes",
|
||||
{},
|
||||
u'hello <a href="http://world.com/with?param=true&stuff=yes">http://world.com/with?param=true&stuff=yes</a>', # noqa: E501
|
||||
),
|
||||
# an opened paren followed by many chars killed Gruber's regex
|
||||
(
|
||||
"http://url.com/w(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
{},
|
||||
u'<a href="http://url.com/w">http://url.com/w</a>(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', # noqa: E501
|
||||
),
|
||||
# as did too many dots at the end
|
||||
(
|
||||
"http://url.com/withmany.......................................",
|
||||
{},
|
||||
u'<a href="http://url.com/withmany">http://url.com/withmany</a>.......................................', # noqa: E501
|
||||
),
|
||||
(
|
||||
"http://url.com/withmany((((((((((((((((((((((((((((((((((a)",
|
||||
{},
|
||||
u'<a href="http://url.com/withmany">http://url.com/withmany</a>((((((((((((((((((((((((((((((((((a)', # noqa: E501
|
||||
),
|
||||
# some examples from http://daringfireball.net/2009/11/liberal_regex_for_matching_urls
|
||||
# plus a fex extras (such as multiple parentheses).
|
||||
(
|
||||
"http://foo.com/blah_blah",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_blah">http://foo.com/blah_blah</a>',
|
||||
),
|
||||
(
|
||||
"http://foo.com/blah_blah/",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_blah/">http://foo.com/blah_blah/</a>',
|
||||
),
|
||||
(
|
||||
"(Something like http://foo.com/blah_blah)",
|
||||
{},
|
||||
u'(Something like <a href="http://foo.com/blah_blah">http://foo.com/blah_blah</a>)',
|
||||
),
|
||||
(
|
||||
"http://foo.com/blah_blah_(wikipedia)",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_blah_(wikipedia)">http://foo.com/blah_blah_(wikipedia)</a>',
|
||||
),
|
||||
(
|
||||
"http://foo.com/blah_(blah)_(wikipedia)_blah",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_(blah)_(wikipedia)_blah">http://foo.com/blah_(blah)_(wikipedia)_blah</a>', # noqa: E501
|
||||
),
|
||||
(
|
||||
"(Something like http://foo.com/blah_blah_(wikipedia))",
|
||||
{},
|
||||
u'(Something like <a href="http://foo.com/blah_blah_(wikipedia)">http://foo.com/blah_blah_(wikipedia)</a>)', # noqa: E501
|
||||
),
|
||||
(
|
||||
"http://foo.com/blah_blah.",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_blah">http://foo.com/blah_blah</a>.',
|
||||
),
|
||||
(
|
||||
"http://foo.com/blah_blah/.",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_blah/">http://foo.com/blah_blah/</a>.',
|
||||
),
|
||||
(
|
||||
"<http://foo.com/blah_blah>",
|
||||
{},
|
||||
u'<<a href="http://foo.com/blah_blah">http://foo.com/blah_blah</a>>',
|
||||
),
|
||||
(
|
||||
"<http://foo.com/blah_blah/>",
|
||||
{},
|
||||
u'<<a href="http://foo.com/blah_blah/">http://foo.com/blah_blah/</a>>',
|
||||
),
|
||||
(
|
||||
"http://foo.com/blah_blah,",
|
||||
{},
|
||||
u'<a href="http://foo.com/blah_blah">http://foo.com/blah_blah</a>,',
|
||||
),
|
||||
(
|
||||
"http://www.example.com/wpstyle/?p=364.",
|
||||
{},
|
||||
u'<a href="http://www.example.com/wpstyle/?p=364">http://www.example.com/wpstyle/?p=364</a>.', # noqa: E501
|
||||
),
|
||||
(
|
||||
"rdar://1234",
|
||||
{"permitted_protocols": ["http", "rdar"]},
|
||||
u'<a href="rdar://1234">rdar://1234</a>',
|
||||
),
|
||||
(
|
||||
"rdar:/1234",
|
||||
{"permitted_protocols": ["rdar"]},
|
||||
u'<a href="rdar:/1234">rdar:/1234</a>',
|
||||
),
|
||||
(
|
||||
"http://userid:password@example.com:8080",
|
||||
{},
|
||||
u'<a href="http://userid:password@example.com:8080">http://userid:password@example.com:8080</a>', # noqa: E501
|
||||
),
|
||||
(
|
||||
"http://userid@example.com",
|
||||
{},
|
||||
u'<a href="http://userid@example.com">http://userid@example.com</a>',
|
||||
),
|
||||
(
|
||||
"http://userid@example.com:8080",
|
||||
{},
|
||||
u'<a href="http://userid@example.com:8080">http://userid@example.com:8080</a>',
|
||||
),
|
||||
(
|
||||
"http://userid:password@example.com",
|
||||
{},
|
||||
u'<a href="http://userid:password@example.com">http://userid:password@example.com</a>',
|
||||
),
|
||||
(
|
||||
"message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e",
|
||||
{"permitted_protocols": ["http", "message"]},
|
||||
u'<a href="message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e">'
|
||||
u"message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e</a>",
|
||||
),
|
||||
(
|
||||
u"http://\u27a1.ws/\u4a39",
|
||||
{},
|
||||
u'<a href="http://\u27a1.ws/\u4a39">http://\u27a1.ws/\u4a39</a>',
|
||||
),
|
||||
(
|
||||
"<tag>http://example.com</tag>",
|
||||
{},
|
||||
u'<tag><a href="http://example.com">http://example.com</a></tag>',
|
||||
),
|
||||
(
|
||||
"Just a www.example.com link.",
|
||||
{},
|
||||
u'Just a <a href="http://www.example.com">www.example.com</a> link.',
|
||||
),
|
||||
(
|
||||
"Just a www.example.com link.",
|
||||
{"require_protocol": True},
|
||||
u"Just a www.example.com link.",
|
||||
),
|
||||
(
|
||||
"A http://reallylong.com/link/that/exceedsthelenglimit.html",
|
||||
{"require_protocol": True, "shorten": True},
|
||||
u'A <a href="http://reallylong.com/link/that/exceedsthelenglimit.html"'
|
||||
u' title="http://reallylong.com/link/that/exceedsthelenglimit.html">http://reallylong.com/link...</a>', # noqa: E501
|
||||
),
|
||||
(
|
||||
"A http://reallylongdomainnamethatwillbetoolong.com/hi!",
|
||||
{"shorten": True},
|
||||
u'A <a href="http://reallylongdomainnamethatwillbetoolong.com/hi"'
|
||||
u' title="http://reallylongdomainnamethatwillbetoolong.com/hi">http://reallylongdomainnametha...</a>!', # noqa: E501
|
||||
),
|
||||
(
|
||||
"A file:///passwords.txt and http://web.com link",
|
||||
{},
|
||||
u'A file:///passwords.txt and <a href="http://web.com">http://web.com</a> link',
|
||||
),
|
||||
(
|
||||
"A file:///passwords.txt and http://web.com link",
|
||||
{"permitted_protocols": ["file"]},
|
||||
u'A <a href="file:///passwords.txt">file:///passwords.txt</a> and http://web.com link',
|
||||
),
|
||||
(
|
||||
"www.external-link.com",
|
||||
{"extra_params": 'rel="nofollow" class="external"'},
|
||||
u'<a href="http://www.external-link.com" rel="nofollow" class="external">www.external-link.com</a>', # noqa: E501
|
||||
),
|
||||
(
|
||||
"www.external-link.com and www.internal-link.com/blogs extra",
|
||||
{
|
||||
"extra_params": lambda href: 'class="internal"'
|
||||
if href.startswith("http://www.internal-link.com")
|
||||
else 'rel="nofollow" class="external"'
|
||||
},
|
||||
u'<a href="http://www.external-link.com" rel="nofollow" class="external">www.external-link.com</a>' # noqa: E501
|
||||
u' and <a href="http://www.internal-link.com/blogs" class="internal">www.internal-link.com/blogs</a> extra', # noqa: E501
|
||||
),
|
||||
(
|
||||
"www.external-link.com",
|
||||
{"extra_params": lambda href: ' rel="nofollow" class="external" '},
|
||||
u'<a href="http://www.external-link.com" rel="nofollow" class="external">www.external-link.com</a>', # noqa: E501
|
||||
),
|
||||
] # type: List[Tuple[Union[str, bytes], Dict[str, Any], str]]
|
||||
|
||||
|
||||
class EscapeTestCase(unittest.TestCase):
|
||||
def test_linkify(self):
|
||||
for text, kwargs, html in linkify_tests:
|
||||
linked = tornado.escape.linkify(text, **kwargs)
|
||||
self.assertEqual(linked, html)
|
||||
|
||||
def test_xhtml_escape(self):
|
||||
tests = [
|
||||
("<foo>", "<foo>"),
|
||||
(u"<foo>", u"<foo>"),
|
||||
(b"<foo>", b"<foo>"),
|
||||
("<>&\"'", "<>&"'"),
|
||||
("&", "&amp;"),
|
||||
(u"<\u00e9>", u"<\u00e9>"),
|
||||
(b"<\xc3\xa9>", b"<\xc3\xa9>"),
|
||||
] # type: List[Tuple[Union[str, bytes], Union[str, bytes]]]
|
||||
for unescaped, escaped in tests:
|
||||
self.assertEqual(utf8(xhtml_escape(unescaped)), utf8(escaped))
|
||||
self.assertEqual(utf8(unescaped), utf8(xhtml_unescape(escaped)))
|
||||
|
||||
def test_xhtml_unescape_numeric(self):
|
||||
tests = [
|
||||
("foo bar", "foo bar"),
|
||||
("foo bar", "foo bar"),
|
||||
("foo bar", "foo bar"),
|
||||
("foo઼bar", u"foo\u0abcbar"),
|
||||
("foo&#xyz;bar", "foo&#xyz;bar"), # invalid encoding
|
||||
("foo&#;bar", "foo&#;bar"), # invalid encoding
|
||||
("foo&#x;bar", "foo&#x;bar"), # invalid encoding
|
||||
]
|
||||
for escaped, unescaped in tests:
|
||||
self.assertEqual(unescaped, xhtml_unescape(escaped))
|
||||
|
||||
def test_url_escape_unicode(self):
|
||||
tests = [
|
||||
# byte strings are passed through as-is
|
||||
(u"\u00e9".encode("utf8"), "%C3%A9"),
|
||||
(u"\u00e9".encode("latin1"), "%E9"),
|
||||
# unicode strings become utf8
|
||||
(u"\u00e9", "%C3%A9"),
|
||||
] # type: List[Tuple[Union[str, bytes], str]]
|
||||
for unescaped, escaped in tests:
|
||||
self.assertEqual(url_escape(unescaped), escaped)
|
||||
|
||||
def test_url_unescape_unicode(self):
|
||||
tests = [
|
||||
("%C3%A9", u"\u00e9", "utf8"),
|
||||
("%C3%A9", u"\u00c3\u00a9", "latin1"),
|
||||
("%C3%A9", utf8(u"\u00e9"), None),
|
||||
]
|
||||
for escaped, unescaped, encoding in tests:
|
||||
# input strings to url_unescape should only contain ascii
|
||||
# characters, but make sure the function accepts both byte
|
||||
# and unicode strings.
|
||||
self.assertEqual(url_unescape(to_unicode(escaped), encoding), unescaped)
|
||||
self.assertEqual(url_unescape(utf8(escaped), encoding), unescaped)
|
||||
|
||||
def test_url_escape_quote_plus(self):
|
||||
unescaped = "+ #%"
|
||||
plus_escaped = "%2B+%23%25"
|
||||
escaped = "%2B%20%23%25"
|
||||
self.assertEqual(url_escape(unescaped), plus_escaped)
|
||||
self.assertEqual(url_escape(unescaped, plus=False), escaped)
|
||||
self.assertEqual(url_unescape(plus_escaped), unescaped)
|
||||
self.assertEqual(url_unescape(escaped, plus=False), unescaped)
|
||||
self.assertEqual(url_unescape(plus_escaped, encoding=None), utf8(unescaped))
|
||||
self.assertEqual(
|
||||
url_unescape(escaped, encoding=None, plus=False), utf8(unescaped)
|
||||
)
|
||||
|
||||
def test_escape_return_types(self):
|
||||
# On python2 the escape methods should generally return the same
|
||||
# type as their argument
|
||||
self.assertEqual(type(xhtml_escape("foo")), str)
|
||||
self.assertEqual(type(xhtml_escape(u"foo")), unicode_type)
|
||||
|
||||
def test_json_decode(self):
|
||||
# json_decode accepts both bytes and unicode, but strings it returns
|
||||
# are always unicode.
|
||||
self.assertEqual(json_decode(b'"foo"'), u"foo")
|
||||
self.assertEqual(json_decode(u'"foo"'), u"foo")
|
||||
|
||||
# Non-ascii bytes are interpreted as utf8
|
||||
self.assertEqual(json_decode(utf8(u'"\u00e9"')), u"\u00e9")
|
||||
|
||||
def test_json_encode(self):
|
||||
# json deals with strings, not bytes. On python 2 byte strings will
|
||||
# convert automatically if they are utf8; on python 3 byte strings
|
||||
# are not allowed.
|
||||
self.assertEqual(json_decode(json_encode(u"\u00e9")), u"\u00e9")
|
||||
if bytes is str:
|
||||
self.assertEqual(json_decode(json_encode(utf8(u"\u00e9"))), u"\u00e9")
|
||||
self.assertRaises(UnicodeDecodeError, json_encode, b"\xe9")
|
||||
|
||||
def test_squeeze(self):
|
||||
self.assertEqual(
|
||||
squeeze(u"sequences of whitespace chars"),
|
||||
u"sequences of whitespace chars",
|
||||
)
|
||||
|
||||
def test_recursive_unicode(self):
|
||||
tests = {
|
||||
"dict": {b"foo": b"bar"},
|
||||
"list": [b"foo", b"bar"],
|
||||
"tuple": (b"foo", b"bar"),
|
||||
"bytes": b"foo",
|
||||
}
|
||||
self.assertEqual(recursive_unicode(tests["dict"]), {u"foo": u"bar"})
|
||||
self.assertEqual(recursive_unicode(tests["list"]), [u"foo", u"bar"])
|
||||
self.assertEqual(recursive_unicode(tests["tuple"]), (u"foo", u"bar"))
|
||||
self.assertEqual(recursive_unicode(tests["bytes"]), u"foo")
|
1119
venv/Lib/site-packages/tornado/test/gen_test.py
Normal file
1119
venv/Lib/site-packages/tornado/test/gen_test.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -0,0 +1,47 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 11:05+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: extract_me.py:11
|
||||
msgid "school"
|
||||
msgstr "école"
|
||||
|
||||
#: extract_me.py:12
|
||||
msgctxt "law"
|
||||
msgid "right"
|
||||
msgstr "le droit"
|
||||
|
||||
#: extract_me.py:13
|
||||
msgctxt "good"
|
||||
msgid "right"
|
||||
msgstr "le bien"
|
||||
|
||||
#: extract_me.py:14
|
||||
msgctxt "organization"
|
||||
msgid "club"
|
||||
msgid_plural "clubs"
|
||||
msgstr[0] "le club"
|
||||
msgstr[1] "les clubs"
|
||||
|
||||
#: extract_me.py:15
|
||||
msgctxt "stick"
|
||||
msgid "club"
|
||||
msgid_plural "clubs"
|
||||
msgstr[0] "le bâton"
|
||||
msgstr[1] "les bâtons"
|
61
venv/Lib/site-packages/tornado/test/http1connection_test.py
Normal file
61
venv/Lib/site-packages/tornado/test/http1connection_test.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
import socket
|
||||
import typing
|
||||
|
||||
from tornado.http1connection import HTTP1Connection
|
||||
from tornado.httputil import HTTPMessageDelegate
|
||||
from tornado.iostream import IOStream
|
||||
from tornado.locks import Event
|
||||
from tornado.netutil import add_accept_handler
|
||||
from tornado.testing import AsyncTestCase, bind_unused_port, gen_test
|
||||
|
||||
|
||||
class HTTP1ConnectionTest(AsyncTestCase):
|
||||
code = None # type: typing.Optional[int]
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.asyncSetUp()
|
||||
|
||||
@gen_test
|
||||
def asyncSetUp(self):
|
||||
listener, port = bind_unused_port()
|
||||
event = Event()
|
||||
|
||||
def accept_callback(conn, addr):
|
||||
self.server_stream = IOStream(conn)
|
||||
self.addCleanup(self.server_stream.close)
|
||||
event.set()
|
||||
|
||||
add_accept_handler(listener, accept_callback)
|
||||
self.client_stream = IOStream(socket.socket())
|
||||
self.addCleanup(self.client_stream.close)
|
||||
yield [self.client_stream.connect(("127.0.0.1", port)), event.wait()]
|
||||
self.io_loop.remove_handler(listener)
|
||||
listener.close()
|
||||
|
||||
@gen_test
|
||||
def test_http10_no_content_length(self):
|
||||
# Regression test for a bug in which can_keep_alive would crash
|
||||
# for an HTTP/1.0 (not 1.1) response with no content-length.
|
||||
conn = HTTP1Connection(self.client_stream, True)
|
||||
self.server_stream.write(b"HTTP/1.0 200 Not Modified\r\n\r\nhello")
|
||||
self.server_stream.close()
|
||||
|
||||
event = Event()
|
||||
test = self
|
||||
body = []
|
||||
|
||||
class Delegate(HTTPMessageDelegate):
|
||||
def headers_received(self, start_line, headers):
|
||||
test.code = start_line.code
|
||||
|
||||
def data_received(self, data):
|
||||
body.append(data)
|
||||
|
||||
def finish(self):
|
||||
event.set()
|
||||
|
||||
yield conn.read_response(Delegate())
|
||||
yield event.wait()
|
||||
self.assertEqual(self.code, 200)
|
||||
self.assertEqual(b"".join(body), b"hello")
|
898
venv/Lib/site-packages/tornado/test/httpclient_test.py
Normal file
898
venv/Lib/site-packages/tornado/test/httpclient_test.py
Normal file
|
@ -0,0 +1,898 @@
|
|||
import base64
|
||||
import binascii
|
||||
from contextlib import closing
|
||||
import copy
|
||||
import gzip
|
||||
import threading
|
||||
import datetime
|
||||
from io import BytesIO
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import typing # noqa: F401
|
||||
import unicodedata
|
||||
import unittest
|
||||
|
||||
from tornado.escape import utf8, native_str, to_unicode
|
||||
from tornado import gen
|
||||
from tornado.httpclient import (
|
||||
HTTPRequest,
|
||||
HTTPResponse,
|
||||
_RequestProxy,
|
||||
HTTPError,
|
||||
HTTPClient,
|
||||
)
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.iostream import IOStream
|
||||
from tornado.log import gen_log, app_log
|
||||
from tornado import netutil
|
||||
from tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog
|
||||
from tornado.test.util import skipOnTravis
|
||||
from tornado.web import Application, RequestHandler, url
|
||||
from tornado.httputil import format_timestamp, HTTPHeaders
|
||||
|
||||
|
||||
class HelloWorldHandler(RequestHandler):
|
||||
def get(self):
|
||||
name = self.get_argument("name", "world")
|
||||
self.set_header("Content-Type", "text/plain")
|
||||
self.finish("Hello %s!" % name)
|
||||
|
||||
|
||||
class PostHandler(RequestHandler):
|
||||
def post(self):
|
||||
self.finish(
|
||||
"Post arg1: %s, arg2: %s"
|
||||
% (self.get_argument("arg1"), self.get_argument("arg2"))
|
||||
)
|
||||
|
||||
|
||||
class PutHandler(RequestHandler):
|
||||
def put(self):
|
||||
self.write("Put body: ")
|
||||
self.write(self.request.body)
|
||||
|
||||
|
||||
class RedirectHandler(RequestHandler):
|
||||
def prepare(self):
|
||||
self.write("redirects can have bodies too")
|
||||
self.redirect(
|
||||
self.get_argument("url"), status=int(self.get_argument("status", "302"))
|
||||
)
|
||||
|
||||
|
||||
class RedirectWithoutLocationHandler(RequestHandler):
|
||||
def prepare(self):
|
||||
# For testing error handling of a redirect with no location header.
|
||||
self.set_status(301)
|
||||
self.finish()
|
||||
|
||||
|
||||
class ChunkHandler(RequestHandler):
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
self.write("asdf")
|
||||
self.flush()
|
||||
# Wait a bit to ensure the chunks are sent and received separately.
|
||||
yield gen.sleep(0.01)
|
||||
self.write("qwer")
|
||||
|
||||
|
||||
class AuthHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.finish(self.request.headers["Authorization"])
|
||||
|
||||
|
||||
class CountdownHandler(RequestHandler):
|
||||
def get(self, count):
|
||||
count = int(count)
|
||||
if count > 0:
|
||||
self.redirect(self.reverse_url("countdown", count - 1))
|
||||
else:
|
||||
self.write("Zero")
|
||||
|
||||
|
||||
class EchoPostHandler(RequestHandler):
|
||||
def post(self):
|
||||
self.write(self.request.body)
|
||||
|
||||
|
||||
class UserAgentHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write(self.request.headers.get("User-Agent", "User agent not set"))
|
||||
|
||||
|
||||
class ContentLength304Handler(RequestHandler):
|
||||
def get(self):
|
||||
self.set_status(304)
|
||||
self.set_header("Content-Length", 42)
|
||||
|
||||
def _clear_representation_headers(self):
|
||||
# Tornado strips content-length from 304 responses, but here we
|
||||
# want to simulate servers that include the headers anyway.
|
||||
pass
|
||||
|
||||
|
||||
class PatchHandler(RequestHandler):
|
||||
def patch(self):
|
||||
"Return the request payload - so we can check it is being kept"
|
||||
self.write(self.request.body)
|
||||
|
||||
|
||||
class AllMethodsHandler(RequestHandler):
|
||||
SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + ("OTHER",) # type: ignore
|
||||
|
||||
def method(self):
|
||||
assert self.request.method is not None
|
||||
self.write(self.request.method)
|
||||
|
||||
get = head = post = put = delete = options = patch = other = method # type: ignore
|
||||
|
||||
|
||||
class SetHeaderHandler(RequestHandler):
|
||||
def get(self):
|
||||
# Use get_arguments for keys to get strings, but
|
||||
# request.arguments for values to get bytes.
|
||||
for k, v in zip(self.get_arguments("k"), self.request.arguments["v"]):
|
||||
self.set_header(k, v)
|
||||
|
||||
|
||||
class InvalidGzipHandler(RequestHandler):
|
||||
def get(self):
|
||||
# set Content-Encoding manually to avoid automatic gzip encoding
|
||||
self.set_header("Content-Type", "text/plain")
|
||||
self.set_header("Content-Encoding", "gzip")
|
||||
# Triggering the potential bug seems to depend on input length.
|
||||
# This length is taken from the bad-response example reported in
|
||||
# https://github.com/tornadoweb/tornado/pull/2875 (uncompressed).
|
||||
body = "".join("Hello World {}\n".format(i) for i in range(9000))[:149051]
|
||||
body = gzip.compress(body.encode(), compresslevel=6) + b"\00"
|
||||
self.write(body)
|
||||
|
||||
|
||||
# These tests end up getting run redundantly: once here with the default
|
||||
# HTTPClient implementation, and then again in each implementation's own
|
||||
# test suite.
|
||||
|
||||
|
||||
class HTTPClientCommonTestCase(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return Application(
|
||||
[
|
||||
url("/hello", HelloWorldHandler),
|
||||
url("/post", PostHandler),
|
||||
url("/put", PutHandler),
|
||||
url("/redirect", RedirectHandler),
|
||||
url("/redirect_without_location", RedirectWithoutLocationHandler),
|
||||
url("/chunk", ChunkHandler),
|
||||
url("/auth", AuthHandler),
|
||||
url("/countdown/([0-9]+)", CountdownHandler, name="countdown"),
|
||||
url("/echopost", EchoPostHandler),
|
||||
url("/user_agent", UserAgentHandler),
|
||||
url("/304_with_content_length", ContentLength304Handler),
|
||||
url("/all_methods", AllMethodsHandler),
|
||||
url("/patch", PatchHandler),
|
||||
url("/set_header", SetHeaderHandler),
|
||||
url("/invalid_gzip", InvalidGzipHandler),
|
||||
],
|
||||
gzip=True,
|
||||
)
|
||||
|
||||
def test_patch_receives_payload(self):
|
||||
body = b"some patch data"
|
||||
response = self.fetch("/patch", method="PATCH", body=body)
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.body, body)
|
||||
|
||||
@skipOnTravis
|
||||
def test_hello_world(self):
|
||||
response = self.fetch("/hello")
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.headers["Content-Type"], "text/plain")
|
||||
self.assertEqual(response.body, b"Hello world!")
|
||||
assert response.request_time is not None
|
||||
self.assertEqual(int(response.request_time), 0)
|
||||
|
||||
response = self.fetch("/hello?name=Ben")
|
||||
self.assertEqual(response.body, b"Hello Ben!")
|
||||
|
||||
def test_streaming_callback(self):
|
||||
# streaming_callback is also tested in test_chunked
|
||||
chunks = [] # type: typing.List[bytes]
|
||||
response = self.fetch("/hello", streaming_callback=chunks.append)
|
||||
# with streaming_callback, data goes to the callback and not response.body
|
||||
self.assertEqual(chunks, [b"Hello world!"])
|
||||
self.assertFalse(response.body)
|
||||
|
||||
def test_post(self):
|
||||
response = self.fetch("/post", method="POST", body="arg1=foo&arg2=bar")
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.body, b"Post arg1: foo, arg2: bar")
|
||||
|
||||
def test_chunked(self):
|
||||
response = self.fetch("/chunk")
|
||||
self.assertEqual(response.body, b"asdfqwer")
|
||||
|
||||
chunks = [] # type: typing.List[bytes]
|
||||
response = self.fetch("/chunk", streaming_callback=chunks.append)
|
||||
self.assertEqual(chunks, [b"asdf", b"qwer"])
|
||||
self.assertFalse(response.body)
|
||||
|
||||
def test_chunked_close(self):
|
||||
# test case in which chunks spread read-callback processing
|
||||
# over several ioloop iterations, but the connection is already closed.
|
||||
sock, port = bind_unused_port()
|
||||
with closing(sock):
|
||||
|
||||
@gen.coroutine
|
||||
def accept_callback(conn, address):
|
||||
# fake an HTTP server using chunked encoding where the final chunks
|
||||
# and connection close all happen at once
|
||||
stream = IOStream(conn)
|
||||
request_data = yield stream.read_until(b"\r\n\r\n")
|
||||
if b"HTTP/1." not in request_data:
|
||||
self.skipTest("requires HTTP/1.x")
|
||||
yield stream.write(
|
||||
b"""\
|
||||
HTTP/1.1 200 OK
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
1
|
||||
1
|
||||
1
|
||||
2
|
||||
0
|
||||
|
||||
""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
)
|
||||
stream.close()
|
||||
|
||||
netutil.add_accept_handler(sock, accept_callback) # type: ignore
|
||||
resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
resp.rethrow()
|
||||
self.assertEqual(resp.body, b"12")
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
|
||||
def test_basic_auth(self):
|
||||
# This test data appears in section 2 of RFC 7617.
|
||||
self.assertEqual(
|
||||
self.fetch(
|
||||
"/auth", auth_username="Aladdin", auth_password="open sesame"
|
||||
).body,
|
||||
b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
|
||||
)
|
||||
|
||||
def test_basic_auth_explicit_mode(self):
|
||||
self.assertEqual(
|
||||
self.fetch(
|
||||
"/auth",
|
||||
auth_username="Aladdin",
|
||||
auth_password="open sesame",
|
||||
auth_mode="basic",
|
||||
).body,
|
||||
b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
|
||||
)
|
||||
|
||||
def test_basic_auth_unicode(self):
|
||||
# This test data appears in section 2.1 of RFC 7617.
|
||||
self.assertEqual(
|
||||
self.fetch("/auth", auth_username="test", auth_password="123£").body,
|
||||
b"Basic dGVzdDoxMjPCow==",
|
||||
)
|
||||
|
||||
# The standard mandates NFC. Give it a decomposed username
|
||||
# and ensure it is normalized to composed form.
|
||||
username = unicodedata.normalize("NFD", u"josé")
|
||||
self.assertEqual(
|
||||
self.fetch("/auth", auth_username=username, auth_password="səcrət").body,
|
||||
b"Basic am9zw6k6c8mZY3LJmXQ=",
|
||||
)
|
||||
|
||||
def test_unsupported_auth_mode(self):
|
||||
# curl and simple clients handle errors a bit differently; the
|
||||
# important thing is that they don't fall back to basic auth
|
||||
# on an unknown mode.
|
||||
with ExpectLog(gen_log, "uncaught exception", required=False):
|
||||
with self.assertRaises((ValueError, HTTPError)): # type: ignore
|
||||
self.fetch(
|
||||
"/auth",
|
||||
auth_username="Aladdin",
|
||||
auth_password="open sesame",
|
||||
auth_mode="asdf",
|
||||
raise_error=True,
|
||||
)
|
||||
|
||||
def test_follow_redirect(self):
|
||||
response = self.fetch("/countdown/2", follow_redirects=False)
|
||||
self.assertEqual(302, response.code)
|
||||
self.assertTrue(response.headers["Location"].endswith("/countdown/1"))
|
||||
|
||||
response = self.fetch("/countdown/2")
|
||||
self.assertEqual(200, response.code)
|
||||
self.assertTrue(response.effective_url.endswith("/countdown/0"))
|
||||
self.assertEqual(b"Zero", response.body)
|
||||
|
||||
def test_redirect_without_location(self):
|
||||
response = self.fetch("/redirect_without_location", follow_redirects=True)
|
||||
# If there is no location header, the redirect response should
|
||||
# just be returned as-is. (This should arguably raise an
|
||||
# error, but libcurl doesn't treat this as an error, so we
|
||||
# don't either).
|
||||
self.assertEqual(301, response.code)
|
||||
|
||||
def test_redirect_put_with_body(self):
|
||||
response = self.fetch(
|
||||
"/redirect?url=/put&status=307", method="PUT", body="hello"
|
||||
)
|
||||
self.assertEqual(response.body, b"Put body: hello")
|
||||
|
||||
def test_redirect_put_without_body(self):
|
||||
# This "without body" edge case is similar to what happens with body_producer.
|
||||
response = self.fetch(
|
||||
"/redirect?url=/put&status=307",
|
||||
method="PUT",
|
||||
allow_nonstandard_methods=True,
|
||||
)
|
||||
self.assertEqual(response.body, b"Put body: ")
|
||||
|
||||
def test_method_after_redirect(self):
|
||||
# Legacy redirect codes (301, 302) convert POST requests to GET.
|
||||
for status in [301, 302, 303]:
|
||||
url = "/redirect?url=/all_methods&status=%d" % status
|
||||
resp = self.fetch(url, method="POST", body=b"")
|
||||
self.assertEqual(b"GET", resp.body)
|
||||
|
||||
# Other methods are left alone, except for 303 redirect, depending on client
|
||||
for method in ["GET", "OPTIONS", "PUT", "DELETE"]:
|
||||
resp = self.fetch(url, method=method, allow_nonstandard_methods=True)
|
||||
if status in [301, 302]:
|
||||
self.assertEqual(utf8(method), resp.body)
|
||||
else:
|
||||
self.assertIn(resp.body, [utf8(method), b"GET"])
|
||||
|
||||
# HEAD is different so check it separately.
|
||||
resp = self.fetch(url, method="HEAD")
|
||||
self.assertEqual(200, resp.code)
|
||||
self.assertEqual(b"", resp.body)
|
||||
|
||||
# Newer redirects always preserve the original method.
|
||||
for status in [307, 308]:
|
||||
url = "/redirect?url=/all_methods&status=307"
|
||||
for method in ["GET", "OPTIONS", "POST", "PUT", "DELETE"]:
|
||||
resp = self.fetch(url, method=method, allow_nonstandard_methods=True)
|
||||
self.assertEqual(method, to_unicode(resp.body))
|
||||
resp = self.fetch(url, method="HEAD")
|
||||
self.assertEqual(200, resp.code)
|
||||
self.assertEqual(b"", resp.body)
|
||||
|
||||
def test_credentials_in_url(self):
|
||||
url = self.get_url("/auth").replace("http://", "http://me:secret@")
|
||||
response = self.fetch(url)
|
||||
self.assertEqual(b"Basic " + base64.b64encode(b"me:secret"), response.body)
|
||||
|
||||
def test_body_encoding(self):
|
||||
unicode_body = u"\xe9"
|
||||
byte_body = binascii.a2b_hex(b"e9")
|
||||
|
||||
# unicode string in body gets converted to utf8
|
||||
response = self.fetch(
|
||||
"/echopost",
|
||||
method="POST",
|
||||
body=unicode_body,
|
||||
headers={"Content-Type": "application/blah"},
|
||||
)
|
||||
self.assertEqual(response.headers["Content-Length"], "2")
|
||||
self.assertEqual(response.body, utf8(unicode_body))
|
||||
|
||||
# byte strings pass through directly
|
||||
response = self.fetch(
|
||||
"/echopost",
|
||||
method="POST",
|
||||
body=byte_body,
|
||||
headers={"Content-Type": "application/blah"},
|
||||
)
|
||||
self.assertEqual(response.headers["Content-Length"], "1")
|
||||
self.assertEqual(response.body, byte_body)
|
||||
|
||||
# Mixing unicode in headers and byte string bodies shouldn't
|
||||
# break anything
|
||||
response = self.fetch(
|
||||
"/echopost",
|
||||
method="POST",
|
||||
body=byte_body,
|
||||
headers={"Content-Type": "application/blah"},
|
||||
user_agent=u"foo",
|
||||
)
|
||||
self.assertEqual(response.headers["Content-Length"], "1")
|
||||
self.assertEqual(response.body, byte_body)
|
||||
|
||||
def test_types(self):
|
||||
response = self.fetch("/hello")
|
||||
self.assertEqual(type(response.body), bytes)
|
||||
self.assertEqual(type(response.headers["Content-Type"]), str)
|
||||
self.assertEqual(type(response.code), int)
|
||||
self.assertEqual(type(response.effective_url), str)
|
||||
|
||||
def test_gzip(self):
|
||||
# All the tests in this file should be using gzip, but this test
|
||||
# ensures that it is in fact getting compressed, and also tests
|
||||
# the httpclient's decompress=False option.
|
||||
# Setting Accept-Encoding manually bypasses the client's
|
||||
# decompression so we can see the raw data.
|
||||
response = self.fetch(
|
||||
"/chunk", decompress_response=False, headers={"Accept-Encoding": "gzip"}
|
||||
)
|
||||
self.assertEqual(response.headers["Content-Encoding"], "gzip")
|
||||
self.assertNotEqual(response.body, b"asdfqwer")
|
||||
# Our test data gets bigger when gzipped. Oops. :)
|
||||
# Chunked encoding bypasses the MIN_LENGTH check.
|
||||
self.assertEqual(len(response.body), 34)
|
||||
f = gzip.GzipFile(mode="r", fileobj=response.buffer)
|
||||
self.assertEqual(f.read(), b"asdfqwer")
|
||||
|
||||
def test_invalid_gzip(self):
|
||||
# test if client hangs on tricky invalid gzip
|
||||
# curl/simple httpclient have different behavior (exception, logging)
|
||||
with ExpectLog(
|
||||
app_log, "(Uncaught exception|Exception in callback)", required=False
|
||||
):
|
||||
try:
|
||||
response = self.fetch("/invalid_gzip")
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.body[:14], b"Hello World 0\n")
|
||||
except HTTPError:
|
||||
pass # acceptable
|
||||
|
||||
def test_header_callback(self):
|
||||
first_line = []
|
||||
headers = {}
|
||||
chunks = []
|
||||
|
||||
def header_callback(header_line):
|
||||
if header_line.startswith("HTTP/1.1 101"):
|
||||
# Upgrading to HTTP/2
|
||||
pass
|
||||
elif header_line.startswith("HTTP/"):
|
||||
first_line.append(header_line)
|
||||
elif header_line != "\r\n":
|
||||
k, v = header_line.split(":", 1)
|
||||
headers[k.lower()] = v.strip()
|
||||
|
||||
def streaming_callback(chunk):
|
||||
# All header callbacks are run before any streaming callbacks,
|
||||
# so the header data is available to process the data as it
|
||||
# comes in.
|
||||
self.assertEqual(headers["content-type"], "text/html; charset=UTF-8")
|
||||
chunks.append(chunk)
|
||||
|
||||
self.fetch(
|
||||
"/chunk",
|
||||
header_callback=header_callback,
|
||||
streaming_callback=streaming_callback,
|
||||
)
|
||||
self.assertEqual(len(first_line), 1, first_line)
|
||||
self.assertRegexpMatches(first_line[0], "HTTP/[0-9]\\.[0-9] 200.*\r\n")
|
||||
self.assertEqual(chunks, [b"asdf", b"qwer"])
|
||||
|
||||
@gen_test
|
||||
def test_configure_defaults(self):
|
||||
defaults = dict(user_agent="TestDefaultUserAgent", allow_ipv6=False)
|
||||
# Construct a new instance of the configured client class
|
||||
client = self.http_client.__class__(force_instance=True, defaults=defaults)
|
||||
try:
|
||||
response = yield client.fetch(self.get_url("/user_agent"))
|
||||
self.assertEqual(response.body, b"TestDefaultUserAgent")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
def test_header_types(self):
|
||||
# Header values may be passed as character or utf8 byte strings,
|
||||
# in a plain dictionary or an HTTPHeaders object.
|
||||
# Keys must always be the native str type.
|
||||
# All combinations should have the same results on the wire.
|
||||
for value in [u"MyUserAgent", b"MyUserAgent"]:
|
||||
for container in [dict, HTTPHeaders]:
|
||||
headers = container()
|
||||
headers["User-Agent"] = value
|
||||
resp = self.fetch("/user_agent", headers=headers)
|
||||
self.assertEqual(
|
||||
resp.body,
|
||||
b"MyUserAgent",
|
||||
"response=%r, value=%r, container=%r"
|
||||
% (resp.body, value, container),
|
||||
)
|
||||
|
||||
def test_multi_line_headers(self):
|
||||
# Multi-line http headers are rare but rfc-allowed
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
sock, port = bind_unused_port()
|
||||
with closing(sock):
|
||||
|
||||
@gen.coroutine
|
||||
def accept_callback(conn, address):
|
||||
stream = IOStream(conn)
|
||||
request_data = yield stream.read_until(b"\r\n\r\n")
|
||||
if b"HTTP/1." not in request_data:
|
||||
self.skipTest("requires HTTP/1.x")
|
||||
yield stream.write(
|
||||
b"""\
|
||||
HTTP/1.1 200 OK
|
||||
X-XSS-Protection: 1;
|
||||
\tmode=block
|
||||
|
||||
""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
)
|
||||
stream.close()
|
||||
|
||||
netutil.add_accept_handler(sock, accept_callback) # type: ignore
|
||||
try:
|
||||
resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
resp.rethrow()
|
||||
self.assertEqual(resp.headers["X-XSS-Protection"], "1; mode=block")
|
||||
finally:
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
|
||||
def test_304_with_content_length(self):
|
||||
# According to the spec 304 responses SHOULD NOT include
|
||||
# Content-Length or other entity headers, but some servers do it
|
||||
# anyway.
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
|
||||
response = self.fetch("/304_with_content_length")
|
||||
self.assertEqual(response.code, 304)
|
||||
self.assertEqual(response.headers["Content-Length"], "42")
|
||||
|
||||
@gen_test
|
||||
def test_future_interface(self):
|
||||
response = yield self.http_client.fetch(self.get_url("/hello"))
|
||||
self.assertEqual(response.body, b"Hello world!")
|
||||
|
||||
@gen_test
|
||||
def test_future_http_error(self):
|
||||
with self.assertRaises(HTTPError) as context:
|
||||
yield self.http_client.fetch(self.get_url("/notfound"))
|
||||
assert context.exception is not None
|
||||
assert context.exception.response is not None
|
||||
self.assertEqual(context.exception.code, 404)
|
||||
self.assertEqual(context.exception.response.code, 404)
|
||||
|
||||
@gen_test
|
||||
def test_future_http_error_no_raise(self):
|
||||
response = yield self.http_client.fetch(
|
||||
self.get_url("/notfound"), raise_error=False
|
||||
)
|
||||
self.assertEqual(response.code, 404)
|
||||
|
||||
@gen_test
|
||||
def test_reuse_request_from_response(self):
|
||||
# The response.request attribute should be an HTTPRequest, not
|
||||
# a _RequestProxy.
|
||||
# This test uses self.http_client.fetch because self.fetch calls
|
||||
# self.get_url on the input unconditionally.
|
||||
url = self.get_url("/hello")
|
||||
response = yield self.http_client.fetch(url)
|
||||
self.assertEqual(response.request.url, url)
|
||||
self.assertTrue(isinstance(response.request, HTTPRequest))
|
||||
response2 = yield self.http_client.fetch(response.request)
|
||||
self.assertEqual(response2.body, b"Hello world!")
|
||||
|
||||
@gen_test
|
||||
def test_bind_source_ip(self):
|
||||
url = self.get_url("/hello")
|
||||
request = HTTPRequest(url, network_interface="127.0.0.1")
|
||||
response = yield self.http_client.fetch(request)
|
||||
self.assertEqual(response.code, 200)
|
||||
|
||||
with self.assertRaises((ValueError, HTTPError)) as context: # type: ignore
|
||||
request = HTTPRequest(url, network_interface="not-interface-or-ip")
|
||||
yield self.http_client.fetch(request)
|
||||
self.assertIn("not-interface-or-ip", str(context.exception))
|
||||
|
||||
def test_all_methods(self):
|
||||
for method in ["GET", "DELETE", "OPTIONS"]:
|
||||
response = self.fetch("/all_methods", method=method)
|
||||
self.assertEqual(response.body, utf8(method))
|
||||
for method in ["POST", "PUT", "PATCH"]:
|
||||
response = self.fetch("/all_methods", method=method, body=b"")
|
||||
self.assertEqual(response.body, utf8(method))
|
||||
response = self.fetch("/all_methods", method="HEAD")
|
||||
self.assertEqual(response.body, b"")
|
||||
response = self.fetch(
|
||||
"/all_methods", method="OTHER", allow_nonstandard_methods=True
|
||||
)
|
||||
self.assertEqual(response.body, b"OTHER")
|
||||
|
||||
def test_body_sanity_checks(self):
|
||||
# These methods require a body.
|
||||
for method in ("POST", "PUT", "PATCH"):
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.fetch("/all_methods", method=method, raise_error=True)
|
||||
self.assertIn("must not be None", str(context.exception))
|
||||
|
||||
resp = self.fetch(
|
||||
"/all_methods", method=method, allow_nonstandard_methods=True
|
||||
)
|
||||
self.assertEqual(resp.code, 200)
|
||||
|
||||
# These methods don't allow a body.
|
||||
for method in ("GET", "DELETE", "OPTIONS"):
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.fetch(
|
||||
"/all_methods", method=method, body=b"asdf", raise_error=True
|
||||
)
|
||||
self.assertIn("must be None", str(context.exception))
|
||||
|
||||
# In most cases this can be overridden, but curl_httpclient
|
||||
# does not allow body with a GET at all.
|
||||
if method != "GET":
|
||||
self.fetch(
|
||||
"/all_methods",
|
||||
method=method,
|
||||
body=b"asdf",
|
||||
allow_nonstandard_methods=True,
|
||||
raise_error=True,
|
||||
)
|
||||
self.assertEqual(resp.code, 200)
|
||||
|
||||
# This test causes odd failures with the combination of
|
||||
# curl_httpclient (at least with the version of libcurl available
|
||||
# on ubuntu 12.04), TwistedIOLoop, and epoll. For POST (but not PUT),
|
||||
# curl decides the response came back too soon and closes the connection
|
||||
# to start again. It does this *before* telling the socket callback to
|
||||
# unregister the FD. Some IOLoop implementations have special kernel
|
||||
# integration to discover this immediately. Tornado's IOLoops
|
||||
# ignore errors on remove_handler to accommodate this behavior, but
|
||||
# Twisted's reactor does not. The removeReader call fails and so
|
||||
# do all future removeAll calls (which our tests do at cleanup).
|
||||
#
|
||||
# def test_post_307(self):
|
||||
# response = self.fetch("/redirect?status=307&url=/post",
|
||||
# method="POST", body=b"arg1=foo&arg2=bar")
|
||||
# self.assertEqual(response.body, b"Post arg1: foo, arg2: bar")
|
||||
|
||||
def test_put_307(self):
|
||||
response = self.fetch(
|
||||
"/redirect?status=307&url=/put", method="PUT", body=b"hello"
|
||||
)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"Put body: hello")
|
||||
|
||||
def test_non_ascii_header(self):
|
||||
# Non-ascii headers are sent as latin1.
|
||||
response = self.fetch("/set_header?k=foo&v=%E9")
|
||||
response.rethrow()
|
||||
self.assertEqual(response.headers["Foo"], native_str(u"\u00e9"))
|
||||
|
||||
def test_response_times(self):
|
||||
# A few simple sanity checks of the response time fields to
|
||||
# make sure they're using the right basis (between the
|
||||
# wall-time and monotonic clocks).
|
||||
start_time = time.time()
|
||||
response = self.fetch("/hello")
|
||||
response.rethrow()
|
||||
self.assertGreaterEqual(response.request_time, 0)
|
||||
self.assertLess(response.request_time, 1.0)
|
||||
# A very crude check to make sure that start_time is based on
|
||||
# wall time and not the monotonic clock.
|
||||
assert response.start_time is not None
|
||||
self.assertLess(abs(response.start_time - start_time), 1.0)
|
||||
|
||||
for k, v in response.time_info.items():
|
||||
self.assertTrue(0 <= v < 1.0, "time_info[%s] out of bounds: %s" % (k, v))
|
||||
|
||||
def test_zero_timeout(self):
|
||||
response = self.fetch("/hello", connect_timeout=0)
|
||||
self.assertEqual(response.code, 200)
|
||||
|
||||
response = self.fetch("/hello", request_timeout=0)
|
||||
self.assertEqual(response.code, 200)
|
||||
|
||||
response = self.fetch("/hello", connect_timeout=0, request_timeout=0)
|
||||
self.assertEqual(response.code, 200)
|
||||
|
||||
@gen_test
|
||||
def test_error_after_cancel(self):
|
||||
fut = self.http_client.fetch(self.get_url("/404"))
|
||||
self.assertTrue(fut.cancel())
|
||||
with ExpectLog(app_log, "Exception after Future was cancelled") as el:
|
||||
# We can't wait on the cancelled Future any more, so just
|
||||
# let the IOLoop run until the exception gets logged (or
|
||||
# not, in which case we exit the loop and ExpectLog will
|
||||
# raise).
|
||||
for i in range(100):
|
||||
yield gen.sleep(0.01)
|
||||
if el.logged_stack:
|
||||
break
|
||||
|
||||
|
||||
class RequestProxyTest(unittest.TestCase):
|
||||
def test_request_set(self):
|
||||
proxy = _RequestProxy(
|
||||
HTTPRequest("http://example.com/", user_agent="foo"), dict()
|
||||
)
|
||||
self.assertEqual(proxy.user_agent, "foo")
|
||||
|
||||
def test_default_set(self):
|
||||
proxy = _RequestProxy(
|
||||
HTTPRequest("http://example.com/"), dict(network_interface="foo")
|
||||
)
|
||||
self.assertEqual(proxy.network_interface, "foo")
|
||||
|
||||
def test_both_set(self):
|
||||
proxy = _RequestProxy(
|
||||
HTTPRequest("http://example.com/", proxy_host="foo"), dict(proxy_host="bar")
|
||||
)
|
||||
self.assertEqual(proxy.proxy_host, "foo")
|
||||
|
||||
def test_neither_set(self):
|
||||
proxy = _RequestProxy(HTTPRequest("http://example.com/"), dict())
|
||||
self.assertIs(proxy.auth_username, None)
|
||||
|
||||
def test_bad_attribute(self):
|
||||
proxy = _RequestProxy(HTTPRequest("http://example.com/"), dict())
|
||||
with self.assertRaises(AttributeError):
|
||||
proxy.foo
|
||||
|
||||
def test_defaults_none(self):
|
||||
proxy = _RequestProxy(HTTPRequest("http://example.com/"), None)
|
||||
self.assertIs(proxy.auth_username, None)
|
||||
|
||||
|
||||
class HTTPResponseTestCase(unittest.TestCase):
|
||||
def test_str(self):
|
||||
response = HTTPResponse( # type: ignore
|
||||
HTTPRequest("http://example.com"), 200, buffer=BytesIO()
|
||||
)
|
||||
s = str(response)
|
||||
self.assertTrue(s.startswith("HTTPResponse("))
|
||||
self.assertIn("code=200", s)
|
||||
|
||||
|
||||
class SyncHTTPClientTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.server_ioloop = IOLoop()
|
||||
event = threading.Event()
|
||||
|
||||
@gen.coroutine
|
||||
def init_server():
|
||||
sock, self.port = bind_unused_port()
|
||||
app = Application([("/", HelloWorldHandler)])
|
||||
self.server = HTTPServer(app)
|
||||
self.server.add_socket(sock)
|
||||
event.set()
|
||||
|
||||
def start():
|
||||
self.server_ioloop.run_sync(init_server)
|
||||
self.server_ioloop.start()
|
||||
|
||||
self.server_thread = threading.Thread(target=start)
|
||||
self.server_thread.start()
|
||||
event.wait()
|
||||
|
||||
self.http_client = HTTPClient()
|
||||
|
||||
def tearDown(self):
|
||||
def stop_server():
|
||||
self.server.stop()
|
||||
# Delay the shutdown of the IOLoop by several iterations because
|
||||
# the server may still have some cleanup work left when
|
||||
# the client finishes with the response (this is noticeable
|
||||
# with http/2, which leaves a Future with an unexamined
|
||||
# StreamClosedError on the loop).
|
||||
|
||||
@gen.coroutine
|
||||
def slow_stop():
|
||||
yield self.server.close_all_connections()
|
||||
# The number of iterations is difficult to predict. Typically,
|
||||
# one is sufficient, although sometimes it needs more.
|
||||
for i in range(5):
|
||||
yield
|
||||
self.server_ioloop.stop()
|
||||
|
||||
self.server_ioloop.add_callback(slow_stop)
|
||||
|
||||
self.server_ioloop.add_callback(stop_server)
|
||||
self.server_thread.join()
|
||||
self.http_client.close()
|
||||
self.server_ioloop.close(all_fds=True)
|
||||
|
||||
def get_url(self, path):
|
||||
return "http://127.0.0.1:%d%s" % (self.port, path)
|
||||
|
||||
def test_sync_client(self):
|
||||
response = self.http_client.fetch(self.get_url("/"))
|
||||
self.assertEqual(b"Hello world!", response.body)
|
||||
|
||||
def test_sync_client_error(self):
|
||||
# Synchronous HTTPClient raises errors directly; no need for
|
||||
# response.rethrow()
|
||||
with self.assertRaises(HTTPError) as assertion:
|
||||
self.http_client.fetch(self.get_url("/notfound"))
|
||||
self.assertEqual(assertion.exception.code, 404)
|
||||
|
||||
|
||||
class SyncHTTPClientSubprocessTest(unittest.TestCase):
|
||||
def test_destructor_log(self):
|
||||
# Regression test for
|
||||
# https://github.com/tornadoweb/tornado/issues/2539
|
||||
#
|
||||
# In the past, the following program would log an
|
||||
# "inconsistent AsyncHTTPClient cache" error from a destructor
|
||||
# when the process is shutting down. The shutdown process is
|
||||
# subtle and I don't fully understand it; the failure does not
|
||||
# manifest if that lambda isn't there or is a simpler object
|
||||
# like an int (nor does it manifest in the tornado test suite
|
||||
# as a whole, which is why we use this subprocess).
|
||||
proc = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
"from tornado.httpclient import HTTPClient; f = lambda: None; c = HTTPClient()",
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True,
|
||||
timeout=5,
|
||||
)
|
||||
if proc.stdout:
|
||||
print("STDOUT:")
|
||||
print(to_unicode(proc.stdout))
|
||||
if proc.stdout:
|
||||
self.fail("subprocess produced unexpected output")
|
||||
|
||||
|
||||
class HTTPRequestTestCase(unittest.TestCase):
|
||||
def test_headers(self):
|
||||
request = HTTPRequest("http://example.com", headers={"foo": "bar"})
|
||||
self.assertEqual(request.headers, {"foo": "bar"})
|
||||
|
||||
def test_headers_setter(self):
|
||||
request = HTTPRequest("http://example.com")
|
||||
request.headers = {"bar": "baz"} # type: ignore
|
||||
self.assertEqual(request.headers, {"bar": "baz"})
|
||||
|
||||
def test_null_headers_setter(self):
|
||||
request = HTTPRequest("http://example.com")
|
||||
request.headers = None # type: ignore
|
||||
self.assertEqual(request.headers, {})
|
||||
|
||||
def test_body(self):
|
||||
request = HTTPRequest("http://example.com", body="foo")
|
||||
self.assertEqual(request.body, utf8("foo"))
|
||||
|
||||
def test_body_setter(self):
|
||||
request = HTTPRequest("http://example.com")
|
||||
request.body = "foo" # type: ignore
|
||||
self.assertEqual(request.body, utf8("foo"))
|
||||
|
||||
def test_if_modified_since(self):
|
||||
http_date = datetime.datetime.utcnow()
|
||||
request = HTTPRequest("http://example.com", if_modified_since=http_date)
|
||||
self.assertEqual(
|
||||
request.headers, {"If-Modified-Since": format_timestamp(http_date)}
|
||||
)
|
||||
|
||||
|
||||
class HTTPErrorTestCase(unittest.TestCase):
|
||||
def test_copy(self):
|
||||
e = HTTPError(403)
|
||||
e2 = copy.copy(e)
|
||||
self.assertIsNot(e, e2)
|
||||
self.assertEqual(e.code, e2.code)
|
||||
|
||||
def test_plain_error(self):
|
||||
e = HTTPError(403)
|
||||
self.assertEqual(str(e), "HTTP 403: Forbidden")
|
||||
self.assertEqual(repr(e), "HTTP 403: Forbidden")
|
||||
|
||||
def test_error_with_response(self):
|
||||
resp = HTTPResponse(HTTPRequest("http://example.com/"), 403)
|
||||
with self.assertRaises(HTTPError) as cm:
|
||||
resp.rethrow()
|
||||
e = cm.exception
|
||||
self.assertEqual(str(e), "HTTP 403: Forbidden")
|
||||
self.assertEqual(repr(e), "HTTP 403: Forbidden")
|
1339
venv/Lib/site-packages/tornado/test/httpserver_test.py
Normal file
1339
venv/Lib/site-packages/tornado/test/httpserver_test.py
Normal file
File diff suppressed because it is too large
Load diff
521
venv/Lib/site-packages/tornado/test/httputil_test.py
Normal file
521
venv/Lib/site-packages/tornado/test/httputil_test.py
Normal file
|
@ -0,0 +1,521 @@
|
|||
from tornado.httputil import (
|
||||
url_concat,
|
||||
parse_multipart_form_data,
|
||||
HTTPHeaders,
|
||||
format_timestamp,
|
||||
HTTPServerRequest,
|
||||
parse_request_start_line,
|
||||
parse_cookie,
|
||||
qs_to_qsl,
|
||||
HTTPInputError,
|
||||
HTTPFile,
|
||||
)
|
||||
from tornado.escape import utf8, native_str
|
||||
from tornado.log import gen_log
|
||||
from tornado.testing import ExpectLog
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import logging
|
||||
import pickle
|
||||
import time
|
||||
import urllib.parse
|
||||
import unittest
|
||||
|
||||
from typing import Tuple, Dict, List
|
||||
|
||||
|
||||
def form_data_args() -> Tuple[Dict[str, List[bytes]], Dict[str, List[HTTPFile]]]:
|
||||
"""Return two empty dicts suitable for use with parse_multipart_form_data.
|
||||
|
||||
mypy insists on type annotations for dict literals, so this lets us avoid
|
||||
the verbose types throughout this test.
|
||||
"""
|
||||
return {}, {}
|
||||
|
||||
|
||||
class TestUrlConcat(unittest.TestCase):
|
||||
def test_url_concat_no_query_params(self):
|
||||
url = url_concat("https://localhost/path", [("y", "y"), ("z", "z")])
|
||||
self.assertEqual(url, "https://localhost/path?y=y&z=z")
|
||||
|
||||
def test_url_concat_encode_args(self):
|
||||
url = url_concat("https://localhost/path", [("y", "/y"), ("z", "z")])
|
||||
self.assertEqual(url, "https://localhost/path?y=%2Fy&z=z")
|
||||
|
||||
def test_url_concat_trailing_q(self):
|
||||
url = url_concat("https://localhost/path?", [("y", "y"), ("z", "z")])
|
||||
self.assertEqual(url, "https://localhost/path?y=y&z=z")
|
||||
|
||||
def test_url_concat_q_with_no_trailing_amp(self):
|
||||
url = url_concat("https://localhost/path?x", [("y", "y"), ("z", "z")])
|
||||
self.assertEqual(url, "https://localhost/path?x=&y=y&z=z")
|
||||
|
||||
def test_url_concat_trailing_amp(self):
|
||||
url = url_concat("https://localhost/path?x&", [("y", "y"), ("z", "z")])
|
||||
self.assertEqual(url, "https://localhost/path?x=&y=y&z=z")
|
||||
|
||||
def test_url_concat_mult_params(self):
|
||||
url = url_concat("https://localhost/path?a=1&b=2", [("y", "y"), ("z", "z")])
|
||||
self.assertEqual(url, "https://localhost/path?a=1&b=2&y=y&z=z")
|
||||
|
||||
def test_url_concat_no_params(self):
|
||||
url = url_concat("https://localhost/path?r=1&t=2", [])
|
||||
self.assertEqual(url, "https://localhost/path?r=1&t=2")
|
||||
|
||||
def test_url_concat_none_params(self):
|
||||
url = url_concat("https://localhost/path?r=1&t=2", None)
|
||||
self.assertEqual(url, "https://localhost/path?r=1&t=2")
|
||||
|
||||
def test_url_concat_with_frag(self):
|
||||
url = url_concat("https://localhost/path#tab", [("y", "y")])
|
||||
self.assertEqual(url, "https://localhost/path?y=y#tab")
|
||||
|
||||
def test_url_concat_multi_same_params(self):
|
||||
url = url_concat("https://localhost/path", [("y", "y1"), ("y", "y2")])
|
||||
self.assertEqual(url, "https://localhost/path?y=y1&y=y2")
|
||||
|
||||
def test_url_concat_multi_same_query_params(self):
|
||||
url = url_concat("https://localhost/path?r=1&r=2", [("y", "y")])
|
||||
self.assertEqual(url, "https://localhost/path?r=1&r=2&y=y")
|
||||
|
||||
def test_url_concat_dict_params(self):
|
||||
url = url_concat("https://localhost/path", dict(y="y"))
|
||||
self.assertEqual(url, "https://localhost/path?y=y")
|
||||
|
||||
|
||||
class QsParseTest(unittest.TestCase):
|
||||
def test_parsing(self):
|
||||
qsstring = "a=1&b=2&a=3"
|
||||
qs = urllib.parse.parse_qs(qsstring)
|
||||
qsl = list(qs_to_qsl(qs))
|
||||
self.assertIn(("a", "1"), qsl)
|
||||
self.assertIn(("a", "3"), qsl)
|
||||
self.assertIn(("b", "2"), qsl)
|
||||
|
||||
|
||||
class MultipartFormDataTest(unittest.TestCase):
|
||||
def test_file_upload(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; name="files"; filename="ab.txt"
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
file = files["files"][0]
|
||||
self.assertEqual(file["filename"], "ab.txt")
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
def test_unquoted_names(self):
|
||||
# quotes are optional unless special characters are present
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; name=files; filename=ab.txt
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
file = files["files"][0]
|
||||
self.assertEqual(file["filename"], "ab.txt")
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
def test_special_filenames(self):
|
||||
filenames = [
|
||||
"a;b.txt",
|
||||
'a"b.txt',
|
||||
'a";b.txt',
|
||||
'a;"b.txt',
|
||||
'a";";.txt',
|
||||
'a\\"b.txt',
|
||||
"a\\b.txt",
|
||||
]
|
||||
for filename in filenames:
|
||||
logging.debug("trying filename %r", filename)
|
||||
str_data = """\
|
||||
--1234
|
||||
Content-Disposition: form-data; name="files"; filename="%s"
|
||||
|
||||
Foo
|
||||
--1234--""" % filename.replace(
|
||||
"\\", "\\\\"
|
||||
).replace(
|
||||
'"', '\\"'
|
||||
)
|
||||
data = utf8(str_data.replace("\n", "\r\n"))
|
||||
args, files = form_data_args()
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
file = files["files"][0]
|
||||
self.assertEqual(file["filename"], filename)
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
def test_non_ascii_filename(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; name="files"; filename="ab.txt"; filename*=UTF-8''%C3%A1b.txt
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
file = files["files"][0]
|
||||
self.assertEqual(file["filename"], u"áb.txt")
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
def test_boundary_starts_and_ends_with_quotes(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; name="files"; filename="ab.txt"
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
parse_multipart_form_data(b'"1234"', data, args, files)
|
||||
file = files["files"][0]
|
||||
self.assertEqual(file["filename"], "ab.txt")
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
def test_missing_headers(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
with ExpectLog(gen_log, "multipart/form-data missing headers"):
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
self.assertEqual(files, {})
|
||||
|
||||
def test_invalid_content_disposition(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: invalid; name="files"; filename="ab.txt"
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
with ExpectLog(gen_log, "Invalid multipart/form-data"):
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
self.assertEqual(files, {})
|
||||
|
||||
def test_line_does_not_end_with_correct_line_break(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; name="files"; filename="ab.txt"
|
||||
|
||||
Foo--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
with ExpectLog(gen_log, "Invalid multipart/form-data"):
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
self.assertEqual(files, {})
|
||||
|
||||
def test_content_disposition_header_without_name_parameter(self):
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; filename="ab.txt"
|
||||
|
||||
Foo
|
||||
--1234--""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
with ExpectLog(gen_log, "multipart/form-data value missing name"):
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
self.assertEqual(files, {})
|
||||
|
||||
def test_data_after_final_boundary(self):
|
||||
# The spec requires that data after the final boundary be ignored.
|
||||
# http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
|
||||
# In practice, some libraries include an extra CRLF after the boundary.
|
||||
data = b"""\
|
||||
--1234
|
||||
Content-Disposition: form-data; name="files"; filename="ab.txt"
|
||||
|
||||
Foo
|
||||
--1234--
|
||||
""".replace(
|
||||
b"\n", b"\r\n"
|
||||
)
|
||||
args, files = form_data_args()
|
||||
parse_multipart_form_data(b"1234", data, args, files)
|
||||
file = files["files"][0]
|
||||
self.assertEqual(file["filename"], "ab.txt")
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
|
||||
class HTTPHeadersTest(unittest.TestCase):
|
||||
def test_multi_line(self):
|
||||
# Lines beginning with whitespace are appended to the previous line
|
||||
# with any leading whitespace replaced by a single space.
|
||||
# Note that while multi-line headers are a part of the HTTP spec,
|
||||
# their use is strongly discouraged.
|
||||
data = """\
|
||||
Foo: bar
|
||||
baz
|
||||
Asdf: qwer
|
||||
\tzxcv
|
||||
Foo: even
|
||||
more
|
||||
lines
|
||||
""".replace(
|
||||
"\n", "\r\n"
|
||||
)
|
||||
headers = HTTPHeaders.parse(data)
|
||||
self.assertEqual(headers["asdf"], "qwer zxcv")
|
||||
self.assertEqual(headers.get_list("asdf"), ["qwer zxcv"])
|
||||
self.assertEqual(headers["Foo"], "bar baz,even more lines")
|
||||
self.assertEqual(headers.get_list("foo"), ["bar baz", "even more lines"])
|
||||
self.assertEqual(
|
||||
sorted(list(headers.get_all())),
|
||||
[("Asdf", "qwer zxcv"), ("Foo", "bar baz"), ("Foo", "even more lines")],
|
||||
)
|
||||
|
||||
def test_malformed_continuation(self):
|
||||
# If the first line starts with whitespace, it's a
|
||||
# continuation line with nothing to continue, so reject it
|
||||
# (with a proper error).
|
||||
data = " Foo: bar"
|
||||
self.assertRaises(HTTPInputError, HTTPHeaders.parse, data)
|
||||
|
||||
def test_unicode_newlines(self):
|
||||
# Ensure that only \r\n is recognized as a header separator, and not
|
||||
# the other newline-like unicode characters.
|
||||
# Characters that are likely to be problematic can be found in
|
||||
# http://unicode.org/standard/reports/tr13/tr13-5.html
|
||||
# and cpython's unicodeobject.c (which defines the implementation
|
||||
# of unicode_type.splitlines(), and uses a different list than TR13).
|
||||
newlines = [
|
||||
u"\u001b", # VERTICAL TAB
|
||||
u"\u001c", # FILE SEPARATOR
|
||||
u"\u001d", # GROUP SEPARATOR
|
||||
u"\u001e", # RECORD SEPARATOR
|
||||
u"\u0085", # NEXT LINE
|
||||
u"\u2028", # LINE SEPARATOR
|
||||
u"\u2029", # PARAGRAPH SEPARATOR
|
||||
]
|
||||
for newline in newlines:
|
||||
# Try the utf8 and latin1 representations of each newline
|
||||
for encoding in ["utf8", "latin1"]:
|
||||
try:
|
||||
try:
|
||||
encoded = newline.encode(encoding)
|
||||
except UnicodeEncodeError:
|
||||
# Some chars cannot be represented in latin1
|
||||
continue
|
||||
data = b"Cookie: foo=" + encoded + b"bar"
|
||||
# parse() wants a native_str, so decode through latin1
|
||||
# in the same way the real parser does.
|
||||
headers = HTTPHeaders.parse(native_str(data.decode("latin1")))
|
||||
expected = [
|
||||
(
|
||||
"Cookie",
|
||||
"foo=" + native_str(encoded.decode("latin1")) + "bar",
|
||||
)
|
||||
]
|
||||
self.assertEqual(expected, list(headers.get_all()))
|
||||
except Exception:
|
||||
gen_log.warning("failed while trying %r in %s", newline, encoding)
|
||||
raise
|
||||
|
||||
def test_optional_cr(self):
|
||||
# Both CRLF and LF should be accepted as separators. CR should not be
|
||||
# part of the data when followed by LF, but it is a normal char
|
||||
# otherwise (or should bare CR be an error?)
|
||||
headers = HTTPHeaders.parse("CRLF: crlf\r\nLF: lf\nCR: cr\rMore: more\r\n")
|
||||
self.assertEqual(
|
||||
sorted(headers.get_all()),
|
||||
[("Cr", "cr\rMore: more"), ("Crlf", "crlf"), ("Lf", "lf")],
|
||||
)
|
||||
|
||||
def test_copy(self):
|
||||
all_pairs = [("A", "1"), ("A", "2"), ("B", "c")]
|
||||
h1 = HTTPHeaders()
|
||||
for k, v in all_pairs:
|
||||
h1.add(k, v)
|
||||
h2 = h1.copy()
|
||||
h3 = copy.copy(h1)
|
||||
h4 = copy.deepcopy(h1)
|
||||
for headers in [h1, h2, h3, h4]:
|
||||
# All the copies are identical, no matter how they were
|
||||
# constructed.
|
||||
self.assertEqual(list(sorted(headers.get_all())), all_pairs)
|
||||
for headers in [h2, h3, h4]:
|
||||
# Neither the dict or its member lists are reused.
|
||||
self.assertIsNot(headers, h1)
|
||||
self.assertIsNot(headers.get_list("A"), h1.get_list("A"))
|
||||
|
||||
def test_pickle_roundtrip(self):
|
||||
headers = HTTPHeaders()
|
||||
headers.add("Set-Cookie", "a=b")
|
||||
headers.add("Set-Cookie", "c=d")
|
||||
headers.add("Content-Type", "text/html")
|
||||
pickled = pickle.dumps(headers)
|
||||
unpickled = pickle.loads(pickled)
|
||||
self.assertEqual(sorted(headers.get_all()), sorted(unpickled.get_all()))
|
||||
self.assertEqual(sorted(headers.items()), sorted(unpickled.items()))
|
||||
|
||||
def test_setdefault(self):
|
||||
headers = HTTPHeaders()
|
||||
headers["foo"] = "bar"
|
||||
# If a value is present, setdefault returns it without changes.
|
||||
self.assertEqual(headers.setdefault("foo", "baz"), "bar")
|
||||
self.assertEqual(headers["foo"], "bar")
|
||||
# If a value is not present, setdefault sets it for future use.
|
||||
self.assertEqual(headers.setdefault("quux", "xyzzy"), "xyzzy")
|
||||
self.assertEqual(headers["quux"], "xyzzy")
|
||||
self.assertEqual(sorted(headers.get_all()), [("Foo", "bar"), ("Quux", "xyzzy")])
|
||||
|
||||
def test_string(self):
|
||||
headers = HTTPHeaders()
|
||||
headers.add("Foo", "1")
|
||||
headers.add("Foo", "2")
|
||||
headers.add("Foo", "3")
|
||||
headers2 = HTTPHeaders.parse(str(headers))
|
||||
self.assertEqual(headers, headers2)
|
||||
|
||||
|
||||
class FormatTimestampTest(unittest.TestCase):
|
||||
# Make sure that all the input types are supported.
|
||||
TIMESTAMP = 1359312200.503611
|
||||
EXPECTED = "Sun, 27 Jan 2013 18:43:20 GMT"
|
||||
|
||||
def check(self, value):
|
||||
self.assertEqual(format_timestamp(value), self.EXPECTED)
|
||||
|
||||
def test_unix_time_float(self):
|
||||
self.check(self.TIMESTAMP)
|
||||
|
||||
def test_unix_time_int(self):
|
||||
self.check(int(self.TIMESTAMP))
|
||||
|
||||
def test_struct_time(self):
|
||||
self.check(time.gmtime(self.TIMESTAMP))
|
||||
|
||||
def test_time_tuple(self):
|
||||
tup = tuple(time.gmtime(self.TIMESTAMP))
|
||||
self.assertEqual(9, len(tup))
|
||||
self.check(tup)
|
||||
|
||||
def test_datetime(self):
|
||||
self.check(datetime.datetime.utcfromtimestamp(self.TIMESTAMP))
|
||||
|
||||
|
||||
# HTTPServerRequest is mainly tested incidentally to the server itself,
|
||||
# but this tests the parts of the class that can be tested in isolation.
|
||||
class HTTPServerRequestTest(unittest.TestCase):
|
||||
def test_default_constructor(self):
|
||||
# All parameters are formally optional, but uri is required
|
||||
# (and has been for some time). This test ensures that no
|
||||
# more required parameters slip in.
|
||||
HTTPServerRequest(uri="/")
|
||||
|
||||
def test_body_is_a_byte_string(self):
|
||||
requets = HTTPServerRequest(uri="/")
|
||||
self.assertIsInstance(requets.body, bytes)
|
||||
|
||||
def test_repr_does_not_contain_headers(self):
|
||||
request = HTTPServerRequest(
|
||||
uri="/", headers=HTTPHeaders({"Canary": ["Coal Mine"]})
|
||||
)
|
||||
self.assertTrue("Canary" not in repr(request))
|
||||
|
||||
|
||||
class ParseRequestStartLineTest(unittest.TestCase):
|
||||
METHOD = "GET"
|
||||
PATH = "/foo"
|
||||
VERSION = "HTTP/1.1"
|
||||
|
||||
def test_parse_request_start_line(self):
|
||||
start_line = " ".join([self.METHOD, self.PATH, self.VERSION])
|
||||
parsed_start_line = parse_request_start_line(start_line)
|
||||
self.assertEqual(parsed_start_line.method, self.METHOD)
|
||||
self.assertEqual(parsed_start_line.path, self.PATH)
|
||||
self.assertEqual(parsed_start_line.version, self.VERSION)
|
||||
|
||||
|
||||
class ParseCookieTest(unittest.TestCase):
|
||||
# These tests copied from Django:
|
||||
# https://github.com/django/django/pull/6277/commits/da810901ada1cae9fc1f018f879f11a7fb467b28
|
||||
def test_python_cookies(self):
|
||||
"""
|
||||
Test cases copied from Python's Lib/test/test_http_cookies.py
|
||||
"""
|
||||
self.assertEqual(
|
||||
parse_cookie("chips=ahoy; vienna=finger"),
|
||||
{"chips": "ahoy", "vienna": "finger"},
|
||||
)
|
||||
# Here parse_cookie() differs from Python's cookie parsing in that it
|
||||
# treats all semicolons as delimiters, even within quotes.
|
||||
self.assertEqual(
|
||||
parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'),
|
||||
{"keebler": '"E=mc2', "L": '\\"Loves\\"', "fudge": "\\012", "": '"'},
|
||||
)
|
||||
# Illegal cookies that have an '=' char in an unquoted value.
|
||||
self.assertEqual(parse_cookie("keebler=E=mc2"), {"keebler": "E=mc2"})
|
||||
# Cookies with ':' character in their name.
|
||||
self.assertEqual(
|
||||
parse_cookie("key:term=value:term"), {"key:term": "value:term"}
|
||||
)
|
||||
# Cookies with '[' and ']'.
|
||||
self.assertEqual(
|
||||
parse_cookie("a=b; c=[; d=r; f=h"), {"a": "b", "c": "[", "d": "r", "f": "h"}
|
||||
)
|
||||
|
||||
def test_cookie_edgecases(self):
|
||||
# Cookies that RFC6265 allows.
|
||||
self.assertEqual(
|
||||
parse_cookie("a=b; Domain=example.com"), {"a": "b", "Domain": "example.com"}
|
||||
)
|
||||
# parse_cookie() has historically kept only the last cookie with the
|
||||
# same name.
|
||||
self.assertEqual(parse_cookie("a=b; h=i; a=c"), {"a": "c", "h": "i"})
|
||||
|
||||
def test_invalid_cookies(self):
|
||||
"""
|
||||
Cookie strings that go against RFC6265 but browsers will send if set
|
||||
via document.cookie.
|
||||
"""
|
||||
# Chunks without an equals sign appear as unnamed values per
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
|
||||
self.assertIn(
|
||||
"django_language",
|
||||
parse_cookie("abc=def; unnamed; django_language=en").keys(),
|
||||
)
|
||||
# Even a double quote may be an unamed value.
|
||||
self.assertEqual(parse_cookie('a=b; "; c=d'), {"a": "b", "": '"', "c": "d"})
|
||||
# Spaces in names and values, and an equals sign in values.
|
||||
self.assertEqual(
|
||||
parse_cookie("a b c=d e = f; gh=i"), {"a b c": "d e = f", "gh": "i"}
|
||||
)
|
||||
# More characters the spec forbids.
|
||||
self.assertEqual(
|
||||
parse_cookie('a b,c<>@:/[]?{}=d " =e,f g'),
|
||||
{"a b,c<>@:/[]?{}": 'd " =e,f g'},
|
||||
)
|
||||
# Unicode characters. The spec only allows ASCII.
|
||||
self.assertEqual(
|
||||
parse_cookie("saint=André Bessette"),
|
||||
{"saint": native_str("André Bessette")},
|
||||
)
|
||||
# Browsers don't send extra whitespace or semicolons in Cookie headers,
|
||||
# but parse_cookie() should parse whitespace the same way
|
||||
# document.cookie parses whitespace.
|
||||
self.assertEqual(
|
||||
parse_cookie(" = b ; ; = ; c = ; "), {"": "b", "c": ""}
|
||||
)
|
66
venv/Lib/site-packages/tornado/test/import_test.py
Normal file
66
venv/Lib/site-packages/tornado/test/import_test.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# flake8: noqa
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
_import_everything = b"""
|
||||
# The event loop is not fork-safe, and it's easy to initialize an asyncio.Future
|
||||
# at startup, which in turn creates the default event loop and prevents forking.
|
||||
# Explicitly disallow the default event loop so that an error will be raised
|
||||
# if something tries to touch it.
|
||||
import asyncio
|
||||
asyncio.set_event_loop(None)
|
||||
|
||||
import tornado.auth
|
||||
import tornado.autoreload
|
||||
import tornado.concurrent
|
||||
import tornado.escape
|
||||
import tornado.gen
|
||||
import tornado.http1connection
|
||||
import tornado.httpclient
|
||||
import tornado.httpserver
|
||||
import tornado.httputil
|
||||
import tornado.ioloop
|
||||
import tornado.iostream
|
||||
import tornado.locale
|
||||
import tornado.log
|
||||
import tornado.netutil
|
||||
import tornado.options
|
||||
import tornado.process
|
||||
import tornado.simple_httpclient
|
||||
import tornado.tcpserver
|
||||
import tornado.tcpclient
|
||||
import tornado.template
|
||||
import tornado.testing
|
||||
import tornado.util
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.wsgi
|
||||
|
||||
try:
|
||||
import pycurl
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
import tornado.curl_httpclient
|
||||
"""
|
||||
|
||||
|
||||
class ImportTest(unittest.TestCase):
|
||||
def test_import_everything(self):
|
||||
# Test that all Tornado modules can be imported without side effects,
|
||||
# specifically without initializing the default asyncio event loop.
|
||||
# Since we can't tell which modules may have already beein imported
|
||||
# in our process, do it in a subprocess for a clean slate.
|
||||
proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE)
|
||||
proc.communicate(_import_everything)
|
||||
self.assertEqual(proc.returncode, 0)
|
||||
|
||||
def test_import_aliases(self):
|
||||
# Ensure we don't delete formerly-documented aliases accidentally.
|
||||
import tornado.ioloop
|
||||
import tornado.gen
|
||||
import tornado.util
|
||||
|
||||
self.assertIs(tornado.ioloop.TimeoutError, tornado.util.TimeoutError)
|
||||
self.assertIs(tornado.gen.TimeoutError, tornado.util.TimeoutError)
|
725
venv/Lib/site-packages/tornado/test/ioloop_test.py
Normal file
725
venv/Lib/site-packages/tornado/test/ioloop_test.py
Normal file
|
@ -0,0 +1,725 @@
|
|||
from concurrent.futures import ThreadPoolExecutor
|
||||
from concurrent import futures
|
||||
import contextlib
|
||||
import datetime
|
||||
import functools
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from unittest import mock
|
||||
import unittest
|
||||
|
||||
from tornado.escape import native_str
|
||||
from tornado import gen
|
||||
from tornado.ioloop import IOLoop, TimeoutError, PeriodicCallback
|
||||
from tornado.log import app_log
|
||||
from tornado.testing import AsyncTestCase, bind_unused_port, ExpectLog, gen_test
|
||||
from tornado.test.util import skipIfNonUnix, skipOnTravis
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import List # noqa: F401
|
||||
|
||||
|
||||
class TestIOLoop(AsyncTestCase):
|
||||
def test_add_callback_return_sequence(self):
|
||||
# A callback returning {} or [] shouldn't spin the CPU, see Issue #1803.
|
||||
self.calls = 0
|
||||
|
||||
loop = self.io_loop
|
||||
test = self
|
||||
old_add_callback = loop.add_callback
|
||||
|
||||
def add_callback(self, callback, *args, **kwargs):
|
||||
test.calls += 1
|
||||
old_add_callback(callback, *args, **kwargs)
|
||||
|
||||
loop.add_callback = types.MethodType(add_callback, loop) # type: ignore
|
||||
loop.add_callback(lambda: {}) # type: ignore
|
||||
loop.add_callback(lambda: []) # type: ignore
|
||||
loop.add_timeout(datetime.timedelta(milliseconds=50), loop.stop)
|
||||
loop.start()
|
||||
self.assertLess(self.calls, 10)
|
||||
|
||||
@skipOnTravis
|
||||
def test_add_callback_wakeup(self):
|
||||
# Make sure that add_callback from inside a running IOLoop
|
||||
# wakes up the IOLoop immediately instead of waiting for a timeout.
|
||||
def callback():
|
||||
self.called = True
|
||||
self.stop()
|
||||
|
||||
def schedule_callback():
|
||||
self.called = False
|
||||
self.io_loop.add_callback(callback)
|
||||
# Store away the time so we can check if we woke up immediately
|
||||
self.start_time = time.time()
|
||||
|
||||
self.io_loop.add_timeout(self.io_loop.time(), schedule_callback)
|
||||
self.wait()
|
||||
self.assertAlmostEqual(time.time(), self.start_time, places=2)
|
||||
self.assertTrue(self.called)
|
||||
|
||||
@skipOnTravis
|
||||
def test_add_callback_wakeup_other_thread(self):
|
||||
def target():
|
||||
# sleep a bit to let the ioloop go into its poll loop
|
||||
time.sleep(0.01)
|
||||
self.stop_time = time.time()
|
||||
self.io_loop.add_callback(self.stop)
|
||||
|
||||
thread = threading.Thread(target=target)
|
||||
self.io_loop.add_callback(thread.start)
|
||||
self.wait()
|
||||
delta = time.time() - self.stop_time
|
||||
self.assertLess(delta, 0.1)
|
||||
thread.join()
|
||||
|
||||
def test_add_timeout_timedelta(self):
|
||||
self.io_loop.add_timeout(datetime.timedelta(microseconds=1), self.stop)
|
||||
self.wait()
|
||||
|
||||
def test_multiple_add(self):
|
||||
sock, port = bind_unused_port()
|
||||
try:
|
||||
self.io_loop.add_handler(
|
||||
sock.fileno(), lambda fd, events: None, IOLoop.READ
|
||||
)
|
||||
# Attempting to add the same handler twice fails
|
||||
# (with a platform-dependent exception)
|
||||
self.assertRaises(
|
||||
Exception,
|
||||
self.io_loop.add_handler,
|
||||
sock.fileno(),
|
||||
lambda fd, events: None,
|
||||
IOLoop.READ,
|
||||
)
|
||||
finally:
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
sock.close()
|
||||
|
||||
def test_remove_without_add(self):
|
||||
# remove_handler should not throw an exception if called on an fd
|
||||
# was never added.
|
||||
sock, port = bind_unused_port()
|
||||
try:
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
def test_add_callback_from_signal(self):
|
||||
# cheat a little bit and just run this normally, since we can't
|
||||
# easily simulate the races that happen with real signal handlers
|
||||
self.io_loop.add_callback_from_signal(self.stop)
|
||||
self.wait()
|
||||
|
||||
def test_add_callback_from_signal_other_thread(self):
|
||||
# Very crude test, just to make sure that we cover this case.
|
||||
# This also happens to be the first test where we run an IOLoop in
|
||||
# a non-main thread.
|
||||
other_ioloop = IOLoop()
|
||||
thread = threading.Thread(target=other_ioloop.start)
|
||||
thread.start()
|
||||
other_ioloop.add_callback_from_signal(other_ioloop.stop)
|
||||
thread.join()
|
||||
other_ioloop.close()
|
||||
|
||||
def test_add_callback_while_closing(self):
|
||||
# add_callback should not fail if it races with another thread
|
||||
# closing the IOLoop. The callbacks are dropped silently
|
||||
# without executing.
|
||||
closing = threading.Event()
|
||||
|
||||
def target():
|
||||
other_ioloop.add_callback(other_ioloop.stop)
|
||||
other_ioloop.start()
|
||||
closing.set()
|
||||
other_ioloop.close(all_fds=True)
|
||||
|
||||
other_ioloop = IOLoop()
|
||||
thread = threading.Thread(target=target)
|
||||
thread.start()
|
||||
closing.wait()
|
||||
for i in range(1000):
|
||||
other_ioloop.add_callback(lambda: None)
|
||||
|
||||
@skipIfNonUnix # just because socketpair is so convenient
|
||||
def test_read_while_writeable(self):
|
||||
# Ensure that write events don't come in while we're waiting for
|
||||
# a read and haven't asked for writeability. (the reverse is
|
||||
# difficult to test for)
|
||||
client, server = socket.socketpair()
|
||||
try:
|
||||
|
||||
def handler(fd, events):
|
||||
self.assertEqual(events, IOLoop.READ)
|
||||
self.stop()
|
||||
|
||||
self.io_loop.add_handler(client.fileno(), handler, IOLoop.READ)
|
||||
self.io_loop.add_timeout(
|
||||
self.io_loop.time() + 0.01, functools.partial(server.send, b"asdf") # type: ignore
|
||||
)
|
||||
self.wait()
|
||||
self.io_loop.remove_handler(client.fileno())
|
||||
finally:
|
||||
client.close()
|
||||
server.close()
|
||||
|
||||
def test_remove_timeout_after_fire(self):
|
||||
# It is not an error to call remove_timeout after it has run.
|
||||
handle = self.io_loop.add_timeout(self.io_loop.time(), self.stop)
|
||||
self.wait()
|
||||
self.io_loop.remove_timeout(handle)
|
||||
|
||||
def test_remove_timeout_cleanup(self):
|
||||
# Add and remove enough callbacks to trigger cleanup.
|
||||
# Not a very thorough test, but it ensures that the cleanup code
|
||||
# gets executed and doesn't blow up. This test is only really useful
|
||||
# on PollIOLoop subclasses, but it should run silently on any
|
||||
# implementation.
|
||||
for i in range(2000):
|
||||
timeout = self.io_loop.add_timeout(self.io_loop.time() + 3600, lambda: None)
|
||||
self.io_loop.remove_timeout(timeout)
|
||||
# HACK: wait two IOLoop iterations for the GC to happen.
|
||||
self.io_loop.add_callback(lambda: self.io_loop.add_callback(self.stop))
|
||||
self.wait()
|
||||
|
||||
def test_remove_timeout_from_timeout(self):
|
||||
calls = [False, False]
|
||||
|
||||
# Schedule several callbacks and wait for them all to come due at once.
|
||||
# t2 should be cancelled by t1, even though it is already scheduled to
|
||||
# be run before the ioloop even looks at it.
|
||||
now = self.io_loop.time()
|
||||
|
||||
def t1():
|
||||
calls[0] = True
|
||||
self.io_loop.remove_timeout(t2_handle)
|
||||
|
||||
self.io_loop.add_timeout(now + 0.01, t1)
|
||||
|
||||
def t2():
|
||||
calls[1] = True
|
||||
|
||||
t2_handle = self.io_loop.add_timeout(now + 0.02, t2)
|
||||
self.io_loop.add_timeout(now + 0.03, self.stop)
|
||||
time.sleep(0.03)
|
||||
self.wait()
|
||||
self.assertEqual(calls, [True, False])
|
||||
|
||||
def test_timeout_with_arguments(self):
|
||||
# This tests that all the timeout methods pass through *args correctly.
|
||||
results = [] # type: List[int]
|
||||
self.io_loop.add_timeout(self.io_loop.time(), results.append, 1)
|
||||
self.io_loop.add_timeout(datetime.timedelta(seconds=0), results.append, 2)
|
||||
self.io_loop.call_at(self.io_loop.time(), results.append, 3)
|
||||
self.io_loop.call_later(0, results.append, 4)
|
||||
self.io_loop.call_later(0, self.stop)
|
||||
self.wait()
|
||||
# The asyncio event loop does not guarantee the order of these
|
||||
# callbacks.
|
||||
self.assertEqual(sorted(results), [1, 2, 3, 4])
|
||||
|
||||
def test_add_timeout_return(self):
|
||||
# All the timeout methods return non-None handles that can be
|
||||
# passed to remove_timeout.
|
||||
handle = self.io_loop.add_timeout(self.io_loop.time(), lambda: None)
|
||||
self.assertFalse(handle is None)
|
||||
self.io_loop.remove_timeout(handle)
|
||||
|
||||
def test_call_at_return(self):
|
||||
handle = self.io_loop.call_at(self.io_loop.time(), lambda: None)
|
||||
self.assertFalse(handle is None)
|
||||
self.io_loop.remove_timeout(handle)
|
||||
|
||||
def test_call_later_return(self):
|
||||
handle = self.io_loop.call_later(0, lambda: None)
|
||||
self.assertFalse(handle is None)
|
||||
self.io_loop.remove_timeout(handle)
|
||||
|
||||
def test_close_file_object(self):
|
||||
"""When a file object is used instead of a numeric file descriptor,
|
||||
the object should be closed (by IOLoop.close(all_fds=True),
|
||||
not just the fd.
|
||||
"""
|
||||
# Use a socket since they are supported by IOLoop on all platforms.
|
||||
# Unfortunately, sockets don't support the .closed attribute for
|
||||
# inspecting their close status, so we must use a wrapper.
|
||||
class SocketWrapper(object):
|
||||
def __init__(self, sockobj):
|
||||
self.sockobj = sockobj
|
||||
self.closed = False
|
||||
|
||||
def fileno(self):
|
||||
return self.sockobj.fileno()
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
self.sockobj.close()
|
||||
|
||||
sockobj, port = bind_unused_port()
|
||||
socket_wrapper = SocketWrapper(sockobj)
|
||||
io_loop = IOLoop()
|
||||
io_loop.add_handler(socket_wrapper, lambda fd, events: None, IOLoop.READ)
|
||||
io_loop.close(all_fds=True)
|
||||
self.assertTrue(socket_wrapper.closed)
|
||||
|
||||
def test_handler_callback_file_object(self):
|
||||
"""The handler callback receives the same fd object it passed in."""
|
||||
server_sock, port = bind_unused_port()
|
||||
fds = []
|
||||
|
||||
def handle_connection(fd, events):
|
||||
fds.append(fd)
|
||||
conn, addr = server_sock.accept()
|
||||
conn.close()
|
||||
self.stop()
|
||||
|
||||
self.io_loop.add_handler(server_sock, handle_connection, IOLoop.READ)
|
||||
with contextlib.closing(socket.socket()) as client_sock:
|
||||
client_sock.connect(("127.0.0.1", port))
|
||||
self.wait()
|
||||
self.io_loop.remove_handler(server_sock)
|
||||
self.io_loop.add_handler(server_sock.fileno(), handle_connection, IOLoop.READ)
|
||||
with contextlib.closing(socket.socket()) as client_sock:
|
||||
client_sock.connect(("127.0.0.1", port))
|
||||
self.wait()
|
||||
self.assertIs(fds[0], server_sock)
|
||||
self.assertEqual(fds[1], server_sock.fileno())
|
||||
self.io_loop.remove_handler(server_sock.fileno())
|
||||
server_sock.close()
|
||||
|
||||
def test_mixed_fd_fileobj(self):
|
||||
server_sock, port = bind_unused_port()
|
||||
|
||||
def f(fd, events):
|
||||
pass
|
||||
|
||||
self.io_loop.add_handler(server_sock, f, IOLoop.READ)
|
||||
with self.assertRaises(Exception):
|
||||
# The exact error is unspecified - some implementations use
|
||||
# IOError, others use ValueError.
|
||||
self.io_loop.add_handler(server_sock.fileno(), f, IOLoop.READ)
|
||||
self.io_loop.remove_handler(server_sock.fileno())
|
||||
server_sock.close()
|
||||
|
||||
def test_reentrant(self):
|
||||
"""Calling start() twice should raise an error, not deadlock."""
|
||||
returned_from_start = [False]
|
||||
got_exception = [False]
|
||||
|
||||
def callback():
|
||||
try:
|
||||
self.io_loop.start()
|
||||
returned_from_start[0] = True
|
||||
except Exception:
|
||||
got_exception[0] = True
|
||||
self.stop()
|
||||
|
||||
self.io_loop.add_callback(callback)
|
||||
self.wait()
|
||||
self.assertTrue(got_exception[0])
|
||||
self.assertFalse(returned_from_start[0])
|
||||
|
||||
def test_exception_logging(self):
|
||||
"""Uncaught exceptions get logged by the IOLoop."""
|
||||
self.io_loop.add_callback(lambda: 1 / 0)
|
||||
self.io_loop.add_callback(self.stop)
|
||||
with ExpectLog(app_log, "Exception in callback"):
|
||||
self.wait()
|
||||
|
||||
def test_exception_logging_future(self):
|
||||
"""The IOLoop examines exceptions from Futures and logs them."""
|
||||
|
||||
@gen.coroutine
|
||||
def callback():
|
||||
self.io_loop.add_callback(self.stop)
|
||||
1 / 0
|
||||
|
||||
self.io_loop.add_callback(callback)
|
||||
with ExpectLog(app_log, "Exception in callback"):
|
||||
self.wait()
|
||||
|
||||
def test_exception_logging_native_coro(self):
|
||||
"""The IOLoop examines exceptions from awaitables and logs them."""
|
||||
|
||||
async def callback():
|
||||
# Stop the IOLoop two iterations after raising an exception
|
||||
# to give the exception time to be logged.
|
||||
self.io_loop.add_callback(self.io_loop.add_callback, self.stop)
|
||||
1 / 0
|
||||
|
||||
self.io_loop.add_callback(callback)
|
||||
with ExpectLog(app_log, "Exception in callback"):
|
||||
self.wait()
|
||||
|
||||
def test_spawn_callback(self):
|
||||
# Both add_callback and spawn_callback run directly on the IOLoop,
|
||||
# so their errors are logged without stopping the test.
|
||||
self.io_loop.add_callback(lambda: 1 / 0)
|
||||
self.io_loop.add_callback(self.stop)
|
||||
with ExpectLog(app_log, "Exception in callback"):
|
||||
self.wait()
|
||||
# A spawned callback is run directly on the IOLoop, so it will be
|
||||
# logged without stopping the test.
|
||||
self.io_loop.spawn_callback(lambda: 1 / 0)
|
||||
self.io_loop.add_callback(self.stop)
|
||||
with ExpectLog(app_log, "Exception in callback"):
|
||||
self.wait()
|
||||
|
||||
@skipIfNonUnix
|
||||
def test_remove_handler_from_handler(self):
|
||||
# Create two sockets with simultaneous read events.
|
||||
client, server = socket.socketpair()
|
||||
try:
|
||||
client.send(b"abc")
|
||||
server.send(b"abc")
|
||||
|
||||
# After reading from one fd, remove the other from the IOLoop.
|
||||
chunks = []
|
||||
|
||||
def handle_read(fd, events):
|
||||
chunks.append(fd.recv(1024))
|
||||
if fd is client:
|
||||
self.io_loop.remove_handler(server)
|
||||
else:
|
||||
self.io_loop.remove_handler(client)
|
||||
|
||||
self.io_loop.add_handler(client, handle_read, self.io_loop.READ)
|
||||
self.io_loop.add_handler(server, handle_read, self.io_loop.READ)
|
||||
self.io_loop.call_later(0.1, self.stop)
|
||||
self.wait()
|
||||
|
||||
# Only one fd was read; the other was cleanly removed.
|
||||
self.assertEqual(chunks, [b"abc"])
|
||||
finally:
|
||||
client.close()
|
||||
server.close()
|
||||
|
||||
@skipIfNonUnix
|
||||
@gen_test
|
||||
def test_init_close_race(self):
|
||||
# Regression test for #2367
|
||||
#
|
||||
# Skipped on windows because of what looks like a bug in the
|
||||
# proactor event loop when started and stopped on non-main
|
||||
# threads.
|
||||
def f():
|
||||
for i in range(10):
|
||||
loop = IOLoop()
|
||||
loop.close()
|
||||
|
||||
yield gen.multi([self.io_loop.run_in_executor(None, f) for i in range(2)])
|
||||
|
||||
|
||||
# Deliberately not a subclass of AsyncTestCase so the IOLoop isn't
|
||||
# automatically set as current.
|
||||
class TestIOLoopCurrent(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.io_loop = None # type: typing.Optional[IOLoop]
|
||||
IOLoop.clear_current()
|
||||
|
||||
def tearDown(self):
|
||||
if self.io_loop is not None:
|
||||
self.io_loop.close()
|
||||
|
||||
def test_default_current(self):
|
||||
self.io_loop = IOLoop()
|
||||
# The first IOLoop with default arguments is made current.
|
||||
self.assertIs(self.io_loop, IOLoop.current())
|
||||
# A second IOLoop can be created but is not made current.
|
||||
io_loop2 = IOLoop()
|
||||
self.assertIs(self.io_loop, IOLoop.current())
|
||||
io_loop2.close()
|
||||
|
||||
def test_non_current(self):
|
||||
self.io_loop = IOLoop(make_current=False)
|
||||
# The new IOLoop is not initially made current.
|
||||
self.assertIsNone(IOLoop.current(instance=False))
|
||||
# Starting the IOLoop makes it current, and stopping the loop
|
||||
# makes it non-current. This process is repeatable.
|
||||
for i in range(3):
|
||||
|
||||
def f():
|
||||
self.current_io_loop = IOLoop.current()
|
||||
assert self.io_loop is not None
|
||||
self.io_loop.stop()
|
||||
|
||||
self.io_loop.add_callback(f)
|
||||
self.io_loop.start()
|
||||
self.assertIs(self.current_io_loop, self.io_loop)
|
||||
# Now that the loop is stopped, it is no longer current.
|
||||
self.assertIsNone(IOLoop.current(instance=False))
|
||||
|
||||
def test_force_current(self):
|
||||
self.io_loop = IOLoop(make_current=True)
|
||||
self.assertIs(self.io_loop, IOLoop.current())
|
||||
with self.assertRaises(RuntimeError):
|
||||
# A second make_current=True construction cannot succeed.
|
||||
IOLoop(make_current=True)
|
||||
# current() was not affected by the failed construction.
|
||||
self.assertIs(self.io_loop, IOLoop.current())
|
||||
|
||||
|
||||
class TestIOLoopCurrentAsync(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_clear_without_current(self):
|
||||
# If there is no current IOLoop, clear_current is a no-op (but
|
||||
# should not fail). Use a thread so we see the threading.Local
|
||||
# in a pristine state.
|
||||
with ThreadPoolExecutor(1) as e:
|
||||
yield e.submit(IOLoop.clear_current)
|
||||
|
||||
|
||||
class TestIOLoopFutures(AsyncTestCase):
|
||||
def test_add_future_threads(self):
|
||||
with futures.ThreadPoolExecutor(1) as pool:
|
||||
|
||||
def dummy():
|
||||
pass
|
||||
|
||||
self.io_loop.add_future(
|
||||
pool.submit(dummy), lambda future: self.stop(future)
|
||||
)
|
||||
future = self.wait()
|
||||
self.assertTrue(future.done())
|
||||
self.assertTrue(future.result() is None)
|
||||
|
||||
@gen_test
|
||||
def test_run_in_executor_gen(self):
|
||||
event1 = threading.Event()
|
||||
event2 = threading.Event()
|
||||
|
||||
def sync_func(self_event, other_event):
|
||||
self_event.set()
|
||||
other_event.wait()
|
||||
# Note that return value doesn't actually do anything,
|
||||
# it is just passed through to our final assertion to
|
||||
# make sure it is passed through properly.
|
||||
return self_event
|
||||
|
||||
# Run two synchronous functions, which would deadlock if not
|
||||
# run in parallel.
|
||||
res = yield [
|
||||
IOLoop.current().run_in_executor(None, sync_func, event1, event2),
|
||||
IOLoop.current().run_in_executor(None, sync_func, event2, event1),
|
||||
]
|
||||
|
||||
self.assertEqual([event1, event2], res)
|
||||
|
||||
@gen_test
|
||||
def test_run_in_executor_native(self):
|
||||
event1 = threading.Event()
|
||||
event2 = threading.Event()
|
||||
|
||||
def sync_func(self_event, other_event):
|
||||
self_event.set()
|
||||
other_event.wait()
|
||||
return self_event
|
||||
|
||||
# Go through an async wrapper to ensure that the result of
|
||||
# run_in_executor works with await and not just gen.coroutine
|
||||
# (simply passing the underlying concurrent future would do that).
|
||||
async def async_wrapper(self_event, other_event):
|
||||
return await IOLoop.current().run_in_executor(
|
||||
None, sync_func, self_event, other_event
|
||||
)
|
||||
|
||||
res = yield [async_wrapper(event1, event2), async_wrapper(event2, event1)]
|
||||
|
||||
self.assertEqual([event1, event2], res)
|
||||
|
||||
@gen_test
|
||||
def test_set_default_executor(self):
|
||||
count = [0]
|
||||
|
||||
class MyExecutor(futures.ThreadPoolExecutor):
|
||||
def submit(self, func, *args):
|
||||
count[0] += 1
|
||||
return super().submit(func, *args)
|
||||
|
||||
event = threading.Event()
|
||||
|
||||
def sync_func():
|
||||
event.set()
|
||||
|
||||
executor = MyExecutor(1)
|
||||
loop = IOLoop.current()
|
||||
loop.set_default_executor(executor)
|
||||
yield loop.run_in_executor(None, sync_func)
|
||||
self.assertEqual(1, count[0])
|
||||
self.assertTrue(event.is_set())
|
||||
|
||||
|
||||
class TestIOLoopRunSync(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.io_loop = IOLoop()
|
||||
|
||||
def tearDown(self):
|
||||
self.io_loop.close()
|
||||
|
||||
def test_sync_result(self):
|
||||
with self.assertRaises(gen.BadYieldError):
|
||||
self.io_loop.run_sync(lambda: 42)
|
||||
|
||||
def test_sync_exception(self):
|
||||
with self.assertRaises(ZeroDivisionError):
|
||||
self.io_loop.run_sync(lambda: 1 / 0)
|
||||
|
||||
def test_async_result(self):
|
||||
@gen.coroutine
|
||||
def f():
|
||||
yield gen.moment
|
||||
raise gen.Return(42)
|
||||
|
||||
self.assertEqual(self.io_loop.run_sync(f), 42)
|
||||
|
||||
def test_async_exception(self):
|
||||
@gen.coroutine
|
||||
def f():
|
||||
yield gen.moment
|
||||
1 / 0
|
||||
|
||||
with self.assertRaises(ZeroDivisionError):
|
||||
self.io_loop.run_sync(f)
|
||||
|
||||
def test_current(self):
|
||||
def f():
|
||||
self.assertIs(IOLoop.current(), self.io_loop)
|
||||
|
||||
self.io_loop.run_sync(f)
|
||||
|
||||
def test_timeout(self):
|
||||
@gen.coroutine
|
||||
def f():
|
||||
yield gen.sleep(1)
|
||||
|
||||
self.assertRaises(TimeoutError, self.io_loop.run_sync, f, timeout=0.01)
|
||||
|
||||
def test_native_coroutine(self):
|
||||
@gen.coroutine
|
||||
def f1():
|
||||
yield gen.moment
|
||||
|
||||
async def f2():
|
||||
await f1()
|
||||
|
||||
self.io_loop.run_sync(f2)
|
||||
|
||||
|
||||
class TestPeriodicCallbackMath(unittest.TestCase):
|
||||
def simulate_calls(self, pc, durations):
|
||||
"""Simulate a series of calls to the PeriodicCallback.
|
||||
|
||||
Pass a list of call durations in seconds (negative values
|
||||
work to simulate clock adjustments during the call, or more or
|
||||
less equivalently, between calls). This method returns the
|
||||
times at which each call would be made.
|
||||
"""
|
||||
calls = []
|
||||
now = 1000
|
||||
pc._next_timeout = now
|
||||
for d in durations:
|
||||
pc._update_next(now)
|
||||
calls.append(pc._next_timeout)
|
||||
now = pc._next_timeout + d
|
||||
return calls
|
||||
|
||||
def dummy(self):
|
||||
pass
|
||||
|
||||
def test_basic(self):
|
||||
pc = PeriodicCallback(self.dummy, 10000)
|
||||
self.assertEqual(
|
||||
self.simulate_calls(pc, [0] * 5), [1010, 1020, 1030, 1040, 1050]
|
||||
)
|
||||
|
||||
def test_overrun(self):
|
||||
# If a call runs for too long, we skip entire cycles to get
|
||||
# back on schedule.
|
||||
call_durations = [9, 9, 10, 11, 20, 20, 35, 35, 0, 0, 0]
|
||||
expected = [
|
||||
1010,
|
||||
1020,
|
||||
1030, # first 3 calls on schedule
|
||||
1050,
|
||||
1070, # next 2 delayed one cycle
|
||||
1100,
|
||||
1130, # next 2 delayed 2 cycles
|
||||
1170,
|
||||
1210, # next 2 delayed 3 cycles
|
||||
1220,
|
||||
1230, # then back on schedule.
|
||||
]
|
||||
|
||||
pc = PeriodicCallback(self.dummy, 10000)
|
||||
self.assertEqual(self.simulate_calls(pc, call_durations), expected)
|
||||
|
||||
def test_clock_backwards(self):
|
||||
pc = PeriodicCallback(self.dummy, 10000)
|
||||
# Backwards jumps are ignored, potentially resulting in a
|
||||
# slightly slow schedule (although we assume that when
|
||||
# time.time() and time.monotonic() are different, time.time()
|
||||
# is getting adjusted by NTP and is therefore more accurate)
|
||||
self.assertEqual(
|
||||
self.simulate_calls(pc, [-2, -1, -3, -2, 0]), [1010, 1020, 1030, 1040, 1050]
|
||||
)
|
||||
|
||||
# For big jumps, we should perhaps alter the schedule, but we
|
||||
# don't currently. This trace shows that we run callbacks
|
||||
# every 10s of time.time(), but the first and second calls are
|
||||
# 110s of real time apart because the backwards jump is
|
||||
# ignored.
|
||||
self.assertEqual(self.simulate_calls(pc, [-100, 0, 0]), [1010, 1020, 1030])
|
||||
|
||||
def test_jitter(self):
|
||||
random_times = [0.5, 1, 0, 0.75]
|
||||
expected = [1010, 1022.5, 1030, 1041.25]
|
||||
call_durations = [0] * len(random_times)
|
||||
pc = PeriodicCallback(self.dummy, 10000, jitter=0.5)
|
||||
|
||||
def mock_random():
|
||||
return random_times.pop(0)
|
||||
|
||||
with mock.patch("random.random", mock_random):
|
||||
self.assertEqual(self.simulate_calls(pc, call_durations), expected)
|
||||
|
||||
|
||||
class TestIOLoopConfiguration(unittest.TestCase):
|
||||
def run_python(self, *statements):
|
||||
stmt_list = [
|
||||
"from tornado.ioloop import IOLoop",
|
||||
"classname = lambda x: x.__class__.__name__",
|
||||
] + list(statements)
|
||||
args = [sys.executable, "-c", "; ".join(stmt_list)]
|
||||
return native_str(subprocess.check_output(args)).strip()
|
||||
|
||||
def test_default(self):
|
||||
# When asyncio is available, it is used by default.
|
||||
cls = self.run_python("print(classname(IOLoop.current()))")
|
||||
self.assertEqual(cls, "AsyncIOMainLoop")
|
||||
cls = self.run_python("print(classname(IOLoop()))")
|
||||
self.assertEqual(cls, "AsyncIOLoop")
|
||||
|
||||
def test_asyncio(self):
|
||||
cls = self.run_python(
|
||||
'IOLoop.configure("tornado.platform.asyncio.AsyncIOLoop")',
|
||||
"print(classname(IOLoop.current()))",
|
||||
)
|
||||
self.assertEqual(cls, "AsyncIOMainLoop")
|
||||
|
||||
def test_asyncio_main(self):
|
||||
cls = self.run_python(
|
||||
"from tornado.platform.asyncio import AsyncIOMainLoop",
|
||||
"AsyncIOMainLoop().install()",
|
||||
"print(classname(IOLoop.current()))",
|
||||
)
|
||||
self.assertEqual(cls, "AsyncIOMainLoop")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
1282
venv/Lib/site-packages/tornado/test/iostream_test.py
Normal file
1282
venv/Lib/site-packages/tornado/test/iostream_test.py
Normal file
File diff suppressed because it is too large
Load diff
151
venv/Lib/site-packages/tornado/test/locale_test.py
Normal file
151
venv/Lib/site-packages/tornado/test/locale_test.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import tornado.locale
|
||||
from tornado.escape import utf8, to_unicode
|
||||
from tornado.util import unicode_type
|
||||
|
||||
|
||||
class TranslationLoaderTest(unittest.TestCase):
|
||||
# TODO: less hacky way to get isolated tests
|
||||
SAVE_VARS = ["_translations", "_supported_locales", "_use_gettext"]
|
||||
|
||||
def clear_locale_cache(self):
|
||||
tornado.locale.Locale._cache = {}
|
||||
|
||||
def setUp(self):
|
||||
self.saved = {} # type: dict
|
||||
for var in TranslationLoaderTest.SAVE_VARS:
|
||||
self.saved[var] = getattr(tornado.locale, var)
|
||||
self.clear_locale_cache()
|
||||
|
||||
def tearDown(self):
|
||||
for k, v in self.saved.items():
|
||||
setattr(tornado.locale, k, v)
|
||||
self.clear_locale_cache()
|
||||
|
||||
def test_csv(self):
|
||||
tornado.locale.load_translations(
|
||||
os.path.join(os.path.dirname(__file__), "csv_translations")
|
||||
)
|
||||
locale = tornado.locale.get("fr_FR")
|
||||
self.assertTrue(isinstance(locale, tornado.locale.CSVLocale))
|
||||
self.assertEqual(locale.translate("school"), u"\u00e9cole")
|
||||
|
||||
def test_csv_bom(self):
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), "csv_translations", "fr_FR.csv"),
|
||||
"rb",
|
||||
) as f:
|
||||
char_data = to_unicode(f.read())
|
||||
# Re-encode our input data (which is utf-8 without BOM) in
|
||||
# encodings that use the BOM and ensure that we can still load
|
||||
# it. Note that utf-16-le and utf-16-be do not write a BOM,
|
||||
# so we only test whichver variant is native to our platform.
|
||||
for encoding in ["utf-8-sig", "utf-16"]:
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
with open(os.path.join(tmpdir, "fr_FR.csv"), "wb") as f:
|
||||
f.write(char_data.encode(encoding))
|
||||
tornado.locale.load_translations(tmpdir)
|
||||
locale = tornado.locale.get("fr_FR")
|
||||
self.assertIsInstance(locale, tornado.locale.CSVLocale)
|
||||
self.assertEqual(locale.translate("school"), u"\u00e9cole")
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def test_gettext(self):
|
||||
tornado.locale.load_gettext_translations(
|
||||
os.path.join(os.path.dirname(__file__), "gettext_translations"),
|
||||
"tornado_test",
|
||||
)
|
||||
locale = tornado.locale.get("fr_FR")
|
||||
self.assertTrue(isinstance(locale, tornado.locale.GettextLocale))
|
||||
self.assertEqual(locale.translate("school"), u"\u00e9cole")
|
||||
self.assertEqual(locale.pgettext("law", "right"), u"le droit")
|
||||
self.assertEqual(locale.pgettext("good", "right"), u"le bien")
|
||||
self.assertEqual(
|
||||
locale.pgettext("organization", "club", "clubs", 1), u"le club"
|
||||
)
|
||||
self.assertEqual(
|
||||
locale.pgettext("organization", "club", "clubs", 2), u"les clubs"
|
||||
)
|
||||
self.assertEqual(locale.pgettext("stick", "club", "clubs", 1), u"le b\xe2ton")
|
||||
self.assertEqual(locale.pgettext("stick", "club", "clubs", 2), u"les b\xe2tons")
|
||||
|
||||
|
||||
class LocaleDataTest(unittest.TestCase):
|
||||
def test_non_ascii_name(self):
|
||||
name = tornado.locale.LOCALE_NAMES["es_LA"]["name"]
|
||||
self.assertTrue(isinstance(name, unicode_type))
|
||||
self.assertEqual(name, u"Espa\u00f1ol")
|
||||
self.assertEqual(utf8(name), b"Espa\xc3\xb1ol")
|
||||
|
||||
|
||||
class EnglishTest(unittest.TestCase):
|
||||
def test_format_date(self):
|
||||
locale = tornado.locale.get("en_US")
|
||||
date = datetime.datetime(2013, 4, 28, 18, 35)
|
||||
self.assertEqual(
|
||||
locale.format_date(date, full_format=True), "April 28, 2013 at 6:35 pm"
|
||||
)
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
|
||||
self.assertEqual(
|
||||
locale.format_date(now - datetime.timedelta(seconds=2), full_format=False),
|
||||
"2 seconds ago",
|
||||
)
|
||||
self.assertEqual(
|
||||
locale.format_date(now - datetime.timedelta(minutes=2), full_format=False),
|
||||
"2 minutes ago",
|
||||
)
|
||||
self.assertEqual(
|
||||
locale.format_date(now - datetime.timedelta(hours=2), full_format=False),
|
||||
"2 hours ago",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
locale.format_date(
|
||||
now - datetime.timedelta(days=1), full_format=False, shorter=True
|
||||
),
|
||||
"yesterday",
|
||||
)
|
||||
|
||||
date = now - datetime.timedelta(days=2)
|
||||
self.assertEqual(
|
||||
locale.format_date(date, full_format=False, shorter=True),
|
||||
locale._weekdays[date.weekday()],
|
||||
)
|
||||
|
||||
date = now - datetime.timedelta(days=300)
|
||||
self.assertEqual(
|
||||
locale.format_date(date, full_format=False, shorter=True),
|
||||
"%s %d" % (locale._months[date.month - 1], date.day),
|
||||
)
|
||||
|
||||
date = now - datetime.timedelta(days=500)
|
||||
self.assertEqual(
|
||||
locale.format_date(date, full_format=False, shorter=True),
|
||||
"%s %d, %d" % (locale._months[date.month - 1], date.day, date.year),
|
||||
)
|
||||
|
||||
def test_friendly_number(self):
|
||||
locale = tornado.locale.get("en_US")
|
||||
self.assertEqual(locale.friendly_number(1000000), "1,000,000")
|
||||
|
||||
def test_list(self):
|
||||
locale = tornado.locale.get("en_US")
|
||||
self.assertEqual(locale.list([]), "")
|
||||
self.assertEqual(locale.list(["A"]), "A")
|
||||
self.assertEqual(locale.list(["A", "B"]), "A and B")
|
||||
self.assertEqual(locale.list(["A", "B", "C"]), "A, B and C")
|
||||
|
||||
def test_format_day(self):
|
||||
locale = tornado.locale.get("en_US")
|
||||
date = datetime.datetime(2013, 4, 28, 18, 35)
|
||||
self.assertEqual(locale.format_day(date=date, dow=True), "Sunday, April 28")
|
||||
self.assertEqual(locale.format_day(date=date, dow=False), "April 28")
|
535
venv/Lib/site-packages/tornado/test/locks_test.py
Normal file
535
venv/Lib/site-packages/tornado/test/locks_test.py
Normal file
|
@ -0,0 +1,535 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import typing # noqa: F401
|
||||
import unittest
|
||||
|
||||
from tornado import gen, locks
|
||||
from tornado.gen import TimeoutError
|
||||
from tornado.testing import gen_test, AsyncTestCase
|
||||
|
||||
|
||||
class ConditionTest(AsyncTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.history = [] # type: typing.List[typing.Union[int, str]]
|
||||
|
||||
def record_done(self, future, key):
|
||||
"""Record the resolution of a Future returned by Condition.wait."""
|
||||
|
||||
def callback(_):
|
||||
if not future.result():
|
||||
# wait() resolved to False, meaning it timed out.
|
||||
self.history.append("timeout")
|
||||
else:
|
||||
self.history.append(key)
|
||||
|
||||
future.add_done_callback(callback)
|
||||
|
||||
def loop_briefly(self):
|
||||
"""Run all queued callbacks on the IOLoop.
|
||||
|
||||
In these tests, this method is used after calling notify() to
|
||||
preserve the pre-5.0 behavior in which callbacks ran
|
||||
synchronously.
|
||||
"""
|
||||
self.io_loop.add_callback(self.stop)
|
||||
self.wait()
|
||||
|
||||
def test_repr(self):
|
||||
c = locks.Condition()
|
||||
self.assertIn("Condition", repr(c))
|
||||
self.assertNotIn("waiters", repr(c))
|
||||
c.wait()
|
||||
self.assertIn("waiters", repr(c))
|
||||
|
||||
@gen_test
|
||||
def test_notify(self):
|
||||
c = locks.Condition()
|
||||
self.io_loop.call_later(0.01, c.notify)
|
||||
yield c.wait()
|
||||
|
||||
def test_notify_1(self):
|
||||
c = locks.Condition()
|
||||
self.record_done(c.wait(), "wait1")
|
||||
self.record_done(c.wait(), "wait2")
|
||||
c.notify(1)
|
||||
self.loop_briefly()
|
||||
self.history.append("notify1")
|
||||
c.notify(1)
|
||||
self.loop_briefly()
|
||||
self.history.append("notify2")
|
||||
self.assertEqual(["wait1", "notify1", "wait2", "notify2"], self.history)
|
||||
|
||||
def test_notify_n(self):
|
||||
c = locks.Condition()
|
||||
for i in range(6):
|
||||
self.record_done(c.wait(), i)
|
||||
|
||||
c.notify(3)
|
||||
self.loop_briefly()
|
||||
|
||||
# Callbacks execute in the order they were registered.
|
||||
self.assertEqual(list(range(3)), self.history)
|
||||
c.notify(1)
|
||||
self.loop_briefly()
|
||||
self.assertEqual(list(range(4)), self.history)
|
||||
c.notify(2)
|
||||
self.loop_briefly()
|
||||
self.assertEqual(list(range(6)), self.history)
|
||||
|
||||
def test_notify_all(self):
|
||||
c = locks.Condition()
|
||||
for i in range(4):
|
||||
self.record_done(c.wait(), i)
|
||||
|
||||
c.notify_all()
|
||||
self.loop_briefly()
|
||||
self.history.append("notify_all")
|
||||
|
||||
# Callbacks execute in the order they were registered.
|
||||
self.assertEqual(list(range(4)) + ["notify_all"], self.history) # type: ignore
|
||||
|
||||
@gen_test
|
||||
def test_wait_timeout(self):
|
||||
c = locks.Condition()
|
||||
wait = c.wait(timedelta(seconds=0.01))
|
||||
self.io_loop.call_later(0.02, c.notify) # Too late.
|
||||
yield gen.sleep(0.03)
|
||||
self.assertFalse((yield wait))
|
||||
|
||||
@gen_test
|
||||
def test_wait_timeout_preempted(self):
|
||||
c = locks.Condition()
|
||||
|
||||
# This fires before the wait times out.
|
||||
self.io_loop.call_later(0.01, c.notify)
|
||||
wait = c.wait(timedelta(seconds=0.02))
|
||||
yield gen.sleep(0.03)
|
||||
yield wait # No TimeoutError.
|
||||
|
||||
@gen_test
|
||||
def test_notify_n_with_timeout(self):
|
||||
# Register callbacks 0, 1, 2, and 3. Callback 1 has a timeout.
|
||||
# Wait for that timeout to expire, then do notify(2) and make
|
||||
# sure everyone runs. Verifies that a timed-out callback does
|
||||
# not count against the 'n' argument to notify().
|
||||
c = locks.Condition()
|
||||
self.record_done(c.wait(), 0)
|
||||
self.record_done(c.wait(timedelta(seconds=0.01)), 1)
|
||||
self.record_done(c.wait(), 2)
|
||||
self.record_done(c.wait(), 3)
|
||||
|
||||
# Wait for callback 1 to time out.
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(["timeout"], self.history)
|
||||
|
||||
c.notify(2)
|
||||
yield gen.sleep(0.01)
|
||||
self.assertEqual(["timeout", 0, 2], self.history)
|
||||
self.assertEqual(["timeout", 0, 2], self.history)
|
||||
c.notify()
|
||||
yield
|
||||
self.assertEqual(["timeout", 0, 2, 3], self.history)
|
||||
|
||||
@gen_test
|
||||
def test_notify_all_with_timeout(self):
|
||||
c = locks.Condition()
|
||||
self.record_done(c.wait(), 0)
|
||||
self.record_done(c.wait(timedelta(seconds=0.01)), 1)
|
||||
self.record_done(c.wait(), 2)
|
||||
|
||||
# Wait for callback 1 to time out.
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(["timeout"], self.history)
|
||||
|
||||
c.notify_all()
|
||||
yield
|
||||
self.assertEqual(["timeout", 0, 2], self.history)
|
||||
|
||||
@gen_test
|
||||
def test_nested_notify(self):
|
||||
# Ensure no notifications lost, even if notify() is reentered by a
|
||||
# waiter calling notify().
|
||||
c = locks.Condition()
|
||||
|
||||
# Three waiters.
|
||||
futures = [asyncio.ensure_future(c.wait()) for _ in range(3)]
|
||||
|
||||
# First and second futures resolved. Second future reenters notify(),
|
||||
# resolving third future.
|
||||
futures[1].add_done_callback(lambda _: c.notify())
|
||||
c.notify(2)
|
||||
yield
|
||||
self.assertTrue(all(f.done() for f in futures))
|
||||
|
||||
@gen_test
|
||||
def test_garbage_collection(self):
|
||||
# Test that timed-out waiters are occasionally cleaned from the queue.
|
||||
c = locks.Condition()
|
||||
for _ in range(101):
|
||||
c.wait(timedelta(seconds=0.01))
|
||||
|
||||
future = asyncio.ensure_future(c.wait())
|
||||
self.assertEqual(102, len(c._waiters))
|
||||
|
||||
# Let first 101 waiters time out, triggering a collection.
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(1, len(c._waiters))
|
||||
|
||||
# Final waiter is still active.
|
||||
self.assertFalse(future.done())
|
||||
c.notify()
|
||||
self.assertTrue(future.done())
|
||||
|
||||
|
||||
class EventTest(AsyncTestCase):
|
||||
def test_repr(self):
|
||||
event = locks.Event()
|
||||
self.assertTrue("clear" in str(event))
|
||||
self.assertFalse("set" in str(event))
|
||||
event.set()
|
||||
self.assertFalse("clear" in str(event))
|
||||
self.assertTrue("set" in str(event))
|
||||
|
||||
def test_event(self):
|
||||
e = locks.Event()
|
||||
future_0 = asyncio.ensure_future(e.wait())
|
||||
e.set()
|
||||
future_1 = asyncio.ensure_future(e.wait())
|
||||
e.clear()
|
||||
future_2 = asyncio.ensure_future(e.wait())
|
||||
|
||||
self.assertTrue(future_0.done())
|
||||
self.assertTrue(future_1.done())
|
||||
self.assertFalse(future_2.done())
|
||||
|
||||
@gen_test
|
||||
def test_event_timeout(self):
|
||||
e = locks.Event()
|
||||
with self.assertRaises(TimeoutError):
|
||||
yield e.wait(timedelta(seconds=0.01))
|
||||
|
||||
# After a timed-out waiter, normal operation works.
|
||||
self.io_loop.add_timeout(timedelta(seconds=0.01), e.set)
|
||||
yield e.wait(timedelta(seconds=1))
|
||||
|
||||
def test_event_set_multiple(self):
|
||||
e = locks.Event()
|
||||
e.set()
|
||||
e.set()
|
||||
self.assertTrue(e.is_set())
|
||||
|
||||
def test_event_wait_clear(self):
|
||||
e = locks.Event()
|
||||
f0 = asyncio.ensure_future(e.wait())
|
||||
e.clear()
|
||||
f1 = asyncio.ensure_future(e.wait())
|
||||
e.set()
|
||||
self.assertTrue(f0.done())
|
||||
self.assertTrue(f1.done())
|
||||
|
||||
|
||||
class SemaphoreTest(AsyncTestCase):
|
||||
def test_negative_value(self):
|
||||
self.assertRaises(ValueError, locks.Semaphore, value=-1)
|
||||
|
||||
def test_repr(self):
|
||||
sem = locks.Semaphore()
|
||||
self.assertIn("Semaphore", repr(sem))
|
||||
self.assertIn("unlocked,value:1", repr(sem))
|
||||
sem.acquire()
|
||||
self.assertIn("locked", repr(sem))
|
||||
self.assertNotIn("waiters", repr(sem))
|
||||
sem.acquire()
|
||||
self.assertIn("waiters", repr(sem))
|
||||
|
||||
def test_acquire(self):
|
||||
sem = locks.Semaphore()
|
||||
f0 = asyncio.ensure_future(sem.acquire())
|
||||
self.assertTrue(f0.done())
|
||||
|
||||
# Wait for release().
|
||||
f1 = asyncio.ensure_future(sem.acquire())
|
||||
self.assertFalse(f1.done())
|
||||
f2 = asyncio.ensure_future(sem.acquire())
|
||||
sem.release()
|
||||
self.assertTrue(f1.done())
|
||||
self.assertFalse(f2.done())
|
||||
sem.release()
|
||||
self.assertTrue(f2.done())
|
||||
|
||||
sem.release()
|
||||
# Now acquire() is instant.
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
self.assertEqual(0, len(sem._waiters))
|
||||
|
||||
@gen_test
|
||||
def test_acquire_timeout(self):
|
||||
sem = locks.Semaphore(2)
|
||||
yield sem.acquire()
|
||||
yield sem.acquire()
|
||||
acquire = sem.acquire(timedelta(seconds=0.01))
|
||||
self.io_loop.call_later(0.02, sem.release) # Too late.
|
||||
yield gen.sleep(0.3)
|
||||
with self.assertRaises(gen.TimeoutError):
|
||||
yield acquire
|
||||
|
||||
sem.acquire()
|
||||
f = asyncio.ensure_future(sem.acquire())
|
||||
self.assertFalse(f.done())
|
||||
sem.release()
|
||||
self.assertTrue(f.done())
|
||||
|
||||
@gen_test
|
||||
def test_acquire_timeout_preempted(self):
|
||||
sem = locks.Semaphore(1)
|
||||
yield sem.acquire()
|
||||
|
||||
# This fires before the wait times out.
|
||||
self.io_loop.call_later(0.01, sem.release)
|
||||
acquire = sem.acquire(timedelta(seconds=0.02))
|
||||
yield gen.sleep(0.03)
|
||||
yield acquire # No TimeoutError.
|
||||
|
||||
def test_release_unacquired(self):
|
||||
# Unbounded releases are allowed, and increment the semaphore's value.
|
||||
sem = locks.Semaphore()
|
||||
sem.release()
|
||||
sem.release()
|
||||
|
||||
# Now the counter is 3. We can acquire three times before blocking.
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
self.assertFalse(asyncio.ensure_future(sem.acquire()).done())
|
||||
|
||||
@gen_test
|
||||
def test_garbage_collection(self):
|
||||
# Test that timed-out waiters are occasionally cleaned from the queue.
|
||||
sem = locks.Semaphore(value=0)
|
||||
futures = [
|
||||
asyncio.ensure_future(sem.acquire(timedelta(seconds=0.01)))
|
||||
for _ in range(101)
|
||||
]
|
||||
|
||||
future = asyncio.ensure_future(sem.acquire())
|
||||
self.assertEqual(102, len(sem._waiters))
|
||||
|
||||
# Let first 101 waiters time out, triggering a collection.
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(1, len(sem._waiters))
|
||||
|
||||
# Final waiter is still active.
|
||||
self.assertFalse(future.done())
|
||||
sem.release()
|
||||
self.assertTrue(future.done())
|
||||
|
||||
# Prevent "Future exception was never retrieved" messages.
|
||||
for future in futures:
|
||||
self.assertRaises(TimeoutError, future.result)
|
||||
|
||||
|
||||
class SemaphoreContextManagerTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_context_manager(self):
|
||||
sem = locks.Semaphore()
|
||||
with (yield sem.acquire()) as yielded:
|
||||
self.assertTrue(yielded is None)
|
||||
|
||||
# Semaphore was released and can be acquired again.
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
|
||||
@gen_test
|
||||
def test_context_manager_async_await(self):
|
||||
# Repeat the above test using 'async with'.
|
||||
sem = locks.Semaphore()
|
||||
|
||||
async def f():
|
||||
async with sem as yielded:
|
||||
self.assertTrue(yielded is None)
|
||||
|
||||
yield f()
|
||||
|
||||
# Semaphore was released and can be acquired again.
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
|
||||
@gen_test
|
||||
def test_context_manager_exception(self):
|
||||
sem = locks.Semaphore()
|
||||
with self.assertRaises(ZeroDivisionError):
|
||||
with (yield sem.acquire()):
|
||||
1 / 0
|
||||
|
||||
# Semaphore was released and can be acquired again.
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
|
||||
@gen_test
|
||||
def test_context_manager_timeout(self):
|
||||
sem = locks.Semaphore()
|
||||
with (yield sem.acquire(timedelta(seconds=0.01))):
|
||||
pass
|
||||
|
||||
# Semaphore was released and can be acquired again.
|
||||
self.assertTrue(asyncio.ensure_future(sem.acquire()).done())
|
||||
|
||||
@gen_test
|
||||
def test_context_manager_timeout_error(self):
|
||||
sem = locks.Semaphore(value=0)
|
||||
with self.assertRaises(gen.TimeoutError):
|
||||
with (yield sem.acquire(timedelta(seconds=0.01))):
|
||||
pass
|
||||
|
||||
# Counter is still 0.
|
||||
self.assertFalse(asyncio.ensure_future(sem.acquire()).done())
|
||||
|
||||
@gen_test
|
||||
def test_context_manager_contended(self):
|
||||
sem = locks.Semaphore()
|
||||
history = []
|
||||
|
||||
@gen.coroutine
|
||||
def f(index):
|
||||
with (yield sem.acquire()):
|
||||
history.append("acquired %d" % index)
|
||||
yield gen.sleep(0.01)
|
||||
history.append("release %d" % index)
|
||||
|
||||
yield [f(i) for i in range(2)]
|
||||
|
||||
expected_history = []
|
||||
for i in range(2):
|
||||
expected_history.extend(["acquired %d" % i, "release %d" % i])
|
||||
|
||||
self.assertEqual(expected_history, history)
|
||||
|
||||
@gen_test
|
||||
def test_yield_sem(self):
|
||||
# Ensure we catch a "with (yield sem)", which should be
|
||||
# "with (yield sem.acquire())".
|
||||
with self.assertRaises(gen.BadYieldError):
|
||||
with (yield locks.Semaphore()):
|
||||
pass
|
||||
|
||||
def test_context_manager_misuse(self):
|
||||
# Ensure we catch a "with sem", which should be
|
||||
# "with (yield sem.acquire())".
|
||||
with self.assertRaises(RuntimeError):
|
||||
with locks.Semaphore():
|
||||
pass
|
||||
|
||||
|
||||
class BoundedSemaphoreTest(AsyncTestCase):
|
||||
def test_release_unacquired(self):
|
||||
sem = locks.BoundedSemaphore()
|
||||
self.assertRaises(ValueError, sem.release)
|
||||
# Value is 0.
|
||||
sem.acquire()
|
||||
# Block on acquire().
|
||||
future = asyncio.ensure_future(sem.acquire())
|
||||
self.assertFalse(future.done())
|
||||
sem.release()
|
||||
self.assertTrue(future.done())
|
||||
# Value is 1.
|
||||
sem.release()
|
||||
self.assertRaises(ValueError, sem.release)
|
||||
|
||||
|
||||
class LockTests(AsyncTestCase):
|
||||
def test_repr(self):
|
||||
lock = locks.Lock()
|
||||
# No errors.
|
||||
repr(lock)
|
||||
lock.acquire()
|
||||
repr(lock)
|
||||
|
||||
def test_acquire_release(self):
|
||||
lock = locks.Lock()
|
||||
self.assertTrue(asyncio.ensure_future(lock.acquire()).done())
|
||||
future = asyncio.ensure_future(lock.acquire())
|
||||
self.assertFalse(future.done())
|
||||
lock.release()
|
||||
self.assertTrue(future.done())
|
||||
|
||||
@gen_test
|
||||
def test_acquire_fifo(self):
|
||||
lock = locks.Lock()
|
||||
self.assertTrue(asyncio.ensure_future(lock.acquire()).done())
|
||||
N = 5
|
||||
history = []
|
||||
|
||||
@gen.coroutine
|
||||
def f(idx):
|
||||
with (yield lock.acquire()):
|
||||
history.append(idx)
|
||||
|
||||
futures = [f(i) for i in range(N)]
|
||||
self.assertFalse(any(future.done() for future in futures))
|
||||
lock.release()
|
||||
yield futures
|
||||
self.assertEqual(list(range(N)), history)
|
||||
|
||||
@gen_test
|
||||
def test_acquire_fifo_async_with(self):
|
||||
# Repeat the above test using `async with lock:`
|
||||
# instead of `with (yield lock.acquire()):`.
|
||||
lock = locks.Lock()
|
||||
self.assertTrue(asyncio.ensure_future(lock.acquire()).done())
|
||||
N = 5
|
||||
history = []
|
||||
|
||||
async def f(idx):
|
||||
async with lock:
|
||||
history.append(idx)
|
||||
|
||||
futures = [f(i) for i in range(N)]
|
||||
lock.release()
|
||||
yield futures
|
||||
self.assertEqual(list(range(N)), history)
|
||||
|
||||
@gen_test
|
||||
def test_acquire_timeout(self):
|
||||
lock = locks.Lock()
|
||||
lock.acquire()
|
||||
with self.assertRaises(gen.TimeoutError):
|
||||
yield lock.acquire(timeout=timedelta(seconds=0.01))
|
||||
|
||||
# Still locked.
|
||||
self.assertFalse(asyncio.ensure_future(lock.acquire()).done())
|
||||
|
||||
def test_multi_release(self):
|
||||
lock = locks.Lock()
|
||||
self.assertRaises(RuntimeError, lock.release)
|
||||
lock.acquire()
|
||||
lock.release()
|
||||
self.assertRaises(RuntimeError, lock.release)
|
||||
|
||||
@gen_test
|
||||
def test_yield_lock(self):
|
||||
# Ensure we catch a "with (yield lock)", which should be
|
||||
# "with (yield lock.acquire())".
|
||||
with self.assertRaises(gen.BadYieldError):
|
||||
with (yield locks.Lock()):
|
||||
pass
|
||||
|
||||
def test_context_manager_misuse(self):
|
||||
# Ensure we catch a "with lock", which should be
|
||||
# "with (yield lock.acquire())".
|
||||
with self.assertRaises(RuntimeError):
|
||||
with locks.Lock():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
245
venv/Lib/site-packages/tornado/test/log_test.py
Normal file
245
venv/Lib/site-packages/tornado/test/log_test.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
#
|
||||
# Copyright 2012 Facebook
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import contextlib
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from tornado.escape import utf8
|
||||
from tornado.log import LogFormatter, define_logging_options, enable_pretty_logging
|
||||
from tornado.options import OptionParser
|
||||
from tornado.util import basestring_type
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignore_bytes_warning():
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", category=BytesWarning)
|
||||
yield
|
||||
|
||||
|
||||
class LogFormatterTest(unittest.TestCase):
|
||||
# Matches the output of a single logging call (which may be multiple lines
|
||||
# if a traceback was included, so we use the DOTALL option)
|
||||
LINE_RE = re.compile(
|
||||
b"(?s)\x01\\[E [0-9]{6} [0-9]{2}:[0-9]{2}:[0-9]{2} log_test:[0-9]+\\]\x02 (.*)"
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.formatter = LogFormatter(color=False)
|
||||
# Fake color support. We can't guarantee anything about the $TERM
|
||||
# variable when the tests are run, so just patch in some values
|
||||
# for testing. (testing with color off fails to expose some potential
|
||||
# encoding issues from the control characters)
|
||||
self.formatter._colors = {logging.ERROR: u"\u0001"}
|
||||
self.formatter._normal = u"\u0002"
|
||||
# construct a Logger directly to bypass getLogger's caching
|
||||
self.logger = logging.Logger("LogFormatterTest")
|
||||
self.logger.propagate = False
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
self.filename = os.path.join(self.tempdir, "log.out")
|
||||
self.handler = self.make_handler(self.filename)
|
||||
self.handler.setFormatter(self.formatter)
|
||||
self.logger.addHandler(self.handler)
|
||||
|
||||
def tearDown(self):
|
||||
self.handler.close()
|
||||
os.unlink(self.filename)
|
||||
os.rmdir(self.tempdir)
|
||||
|
||||
def make_handler(self, filename):
|
||||
# Base case: default setup without explicit encoding.
|
||||
# In python 2, supports arbitrary byte strings and unicode objects
|
||||
# that contain only ascii. In python 3, supports ascii-only unicode
|
||||
# strings (but byte strings will be repr'd automatically).
|
||||
return logging.FileHandler(filename)
|
||||
|
||||
def get_output(self):
|
||||
with open(self.filename, "rb") as f:
|
||||
line = f.read().strip()
|
||||
m = LogFormatterTest.LINE_RE.match(line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
else:
|
||||
raise Exception("output didn't match regex: %r" % line)
|
||||
|
||||
def test_basic_logging(self):
|
||||
self.logger.error("foo")
|
||||
self.assertEqual(self.get_output(), b"foo")
|
||||
|
||||
def test_bytes_logging(self):
|
||||
with ignore_bytes_warning():
|
||||
# This will be "\xe9" on python 2 or "b'\xe9'" on python 3
|
||||
self.logger.error(b"\xe9")
|
||||
self.assertEqual(self.get_output(), utf8(repr(b"\xe9")))
|
||||
|
||||
def test_utf8_logging(self):
|
||||
with ignore_bytes_warning():
|
||||
self.logger.error(u"\u00e9".encode("utf8"))
|
||||
if issubclass(bytes, basestring_type):
|
||||
# on python 2, utf8 byte strings (and by extension ascii byte
|
||||
# strings) are passed through as-is.
|
||||
self.assertEqual(self.get_output(), utf8(u"\u00e9"))
|
||||
else:
|
||||
# on python 3, byte strings always get repr'd even if
|
||||
# they're ascii-only, so this degenerates into another
|
||||
# copy of test_bytes_logging.
|
||||
self.assertEqual(self.get_output(), utf8(repr(utf8(u"\u00e9"))))
|
||||
|
||||
def test_bytes_exception_logging(self):
|
||||
try:
|
||||
raise Exception(b"\xe9")
|
||||
except Exception:
|
||||
self.logger.exception("caught exception")
|
||||
# This will be "Exception: \xe9" on python 2 or
|
||||
# "Exception: b'\xe9'" on python 3.
|
||||
output = self.get_output()
|
||||
self.assertRegexpMatches(output, br"Exception.*\\xe9")
|
||||
# The traceback contains newlines, which should not have been escaped.
|
||||
self.assertNotIn(br"\n", output)
|
||||
|
||||
|
||||
class UnicodeLogFormatterTest(LogFormatterTest):
|
||||
def make_handler(self, filename):
|
||||
# Adding an explicit encoding configuration allows non-ascii unicode
|
||||
# strings in both python 2 and 3, without changing the behavior
|
||||
# for byte strings.
|
||||
return logging.FileHandler(filename, encoding="utf8")
|
||||
|
||||
def test_unicode_logging(self):
|
||||
self.logger.error(u"\u00e9")
|
||||
self.assertEqual(self.get_output(), utf8(u"\u00e9"))
|
||||
|
||||
|
||||
class EnablePrettyLoggingTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.options = OptionParser()
|
||||
define_logging_options(self.options)
|
||||
self.logger = logging.Logger("tornado.test.log_test.EnablePrettyLoggingTest")
|
||||
self.logger.propagate = False
|
||||
|
||||
def test_log_file(self):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
self.options.log_file_prefix = tmpdir + "/test_log"
|
||||
enable_pretty_logging(options=self.options, logger=self.logger)
|
||||
self.assertEqual(1, len(self.logger.handlers))
|
||||
self.logger.error("hello")
|
||||
self.logger.handlers[0].flush()
|
||||
filenames = glob.glob(tmpdir + "/test_log*")
|
||||
self.assertEqual(1, len(filenames))
|
||||
with open(filenames[0]) as f:
|
||||
self.assertRegexpMatches(f.read(), r"^\[E [^]]*\] hello$")
|
||||
finally:
|
||||
for handler in self.logger.handlers:
|
||||
handler.flush()
|
||||
handler.close()
|
||||
for filename in glob.glob(tmpdir + "/test_log*"):
|
||||
os.unlink(filename)
|
||||
os.rmdir(tmpdir)
|
||||
|
||||
def test_log_file_with_timed_rotating(self):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
self.options.log_file_prefix = tmpdir + "/test_log"
|
||||
self.options.log_rotate_mode = "time"
|
||||
enable_pretty_logging(options=self.options, logger=self.logger)
|
||||
self.logger.error("hello")
|
||||
self.logger.handlers[0].flush()
|
||||
filenames = glob.glob(tmpdir + "/test_log*")
|
||||
self.assertEqual(1, len(filenames))
|
||||
with open(filenames[0]) as f:
|
||||
self.assertRegexpMatches(f.read(), r"^\[E [^]]*\] hello$")
|
||||
finally:
|
||||
for handler in self.logger.handlers:
|
||||
handler.flush()
|
||||
handler.close()
|
||||
for filename in glob.glob(tmpdir + "/test_log*"):
|
||||
os.unlink(filename)
|
||||
os.rmdir(tmpdir)
|
||||
|
||||
def test_wrong_rotate_mode_value(self):
|
||||
try:
|
||||
self.options.log_file_prefix = "some_path"
|
||||
self.options.log_rotate_mode = "wrong_mode"
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
enable_pretty_logging,
|
||||
options=self.options,
|
||||
logger=self.logger,
|
||||
)
|
||||
finally:
|
||||
for handler in self.logger.handlers:
|
||||
handler.flush()
|
||||
handler.close()
|
||||
|
||||
|
||||
class LoggingOptionTest(unittest.TestCase):
|
||||
"""Test the ability to enable and disable Tornado's logging hooks."""
|
||||
|
||||
def logs_present(self, statement, args=None):
|
||||
# Each test may manipulate and/or parse the options and then logs
|
||||
# a line at the 'info' level. This level is ignored in the
|
||||
# logging module by default, but Tornado turns it on by default
|
||||
# so it is the easiest way to tell whether tornado's logging hooks
|
||||
# ran.
|
||||
IMPORT = "from tornado.options import options, parse_command_line"
|
||||
LOG_INFO = 'import logging; logging.info("hello")'
|
||||
program = ";".join([IMPORT, statement, LOG_INFO])
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "-c", program] + (args or []),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
stdout, stderr = proc.communicate()
|
||||
self.assertEqual(proc.returncode, 0, "process failed: %r" % stdout)
|
||||
return b"hello" in stdout
|
||||
|
||||
def test_default(self):
|
||||
self.assertFalse(self.logs_present("pass"))
|
||||
|
||||
def test_tornado_default(self):
|
||||
self.assertTrue(self.logs_present("parse_command_line()"))
|
||||
|
||||
def test_disable_command_line(self):
|
||||
self.assertFalse(self.logs_present("parse_command_line()", ["--logging=none"]))
|
||||
|
||||
def test_disable_command_line_case_insensitive(self):
|
||||
self.assertFalse(self.logs_present("parse_command_line()", ["--logging=None"]))
|
||||
|
||||
def test_disable_code_string(self):
|
||||
self.assertFalse(
|
||||
self.logs_present('options.logging = "none"; parse_command_line()')
|
||||
)
|
||||
|
||||
def test_disable_code_none(self):
|
||||
self.assertFalse(
|
||||
self.logs_present("options.logging = None; parse_command_line()")
|
||||
)
|
||||
|
||||
def test_disable_override(self):
|
||||
# command line trumps code defaults
|
||||
self.assertTrue(
|
||||
self.logs_present(
|
||||
"options.logging = None; parse_command_line()", ["--logging=info"]
|
||||
)
|
||||
)
|
233
venv/Lib/site-packages/tornado/test/netutil_test.py
Normal file
233
venv/Lib/site-packages/tornado/test/netutil_test.py
Normal file
|
@ -0,0 +1,233 @@
|
|||
import errno
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
from subprocess import Popen
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from tornado.netutil import (
|
||||
BlockingResolver,
|
||||
OverrideResolver,
|
||||
ThreadedResolver,
|
||||
is_valid_ip,
|
||||
bind_sockets,
|
||||
)
|
||||
from tornado.testing import AsyncTestCase, gen_test, bind_unused_port
|
||||
from tornado.test.util import skipIfNoNetwork
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import List # noqa: F401
|
||||
|
||||
try:
|
||||
import pycares # type: ignore
|
||||
except ImportError:
|
||||
pycares = None
|
||||
else:
|
||||
from tornado.platform.caresresolver import CaresResolver
|
||||
|
||||
try:
|
||||
import twisted # type: ignore
|
||||
import twisted.names # type: ignore
|
||||
except ImportError:
|
||||
twisted = None
|
||||
else:
|
||||
from tornado.platform.twisted import TwistedResolver
|
||||
|
||||
|
||||
class _ResolverTestMixin(object):
|
||||
resolver = None # type: typing.Any
|
||||
|
||||
@gen_test
|
||||
def test_localhost(self: typing.Any):
|
||||
addrinfo = yield self.resolver.resolve("localhost", 80, socket.AF_UNSPEC)
|
||||
self.assertIn((socket.AF_INET, ("127.0.0.1", 80)), addrinfo)
|
||||
|
||||
|
||||
# It is impossible to quickly and consistently generate an error in name
|
||||
# resolution, so test this case separately, using mocks as needed.
|
||||
class _ResolverErrorTestMixin(object):
|
||||
resolver = None # type: typing.Any
|
||||
|
||||
@gen_test
|
||||
def test_bad_host(self: typing.Any):
|
||||
with self.assertRaises(IOError):
|
||||
yield self.resolver.resolve("an invalid domain", 80, socket.AF_UNSPEC)
|
||||
|
||||
|
||||
def _failing_getaddrinfo(*args):
|
||||
"""Dummy implementation of getaddrinfo for use in mocks"""
|
||||
raise socket.gaierror(errno.EIO, "mock: lookup failed")
|
||||
|
||||
|
||||
@skipIfNoNetwork
|
||||
class BlockingResolverTest(AsyncTestCase, _ResolverTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resolver = BlockingResolver()
|
||||
|
||||
|
||||
# getaddrinfo-based tests need mocking to reliably generate errors;
|
||||
# some configurations are slow to produce errors and take longer than
|
||||
# our default timeout.
|
||||
class BlockingResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resolver = BlockingResolver()
|
||||
self.real_getaddrinfo = socket.getaddrinfo
|
||||
socket.getaddrinfo = _failing_getaddrinfo
|
||||
|
||||
def tearDown(self):
|
||||
socket.getaddrinfo = self.real_getaddrinfo
|
||||
super().tearDown()
|
||||
|
||||
|
||||
class OverrideResolverTest(AsyncTestCase, _ResolverTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
mapping = {
|
||||
("google.com", 80): ("1.2.3.4", 80),
|
||||
("google.com", 80, socket.AF_INET): ("1.2.3.4", 80),
|
||||
("google.com", 80, socket.AF_INET6): (
|
||||
"2a02:6b8:7c:40c:c51e:495f:e23a:3",
|
||||
80,
|
||||
),
|
||||
}
|
||||
self.resolver = OverrideResolver(BlockingResolver(), mapping)
|
||||
|
||||
@gen_test
|
||||
def test_resolve_multiaddr(self):
|
||||
result = yield self.resolver.resolve("google.com", 80, socket.AF_INET)
|
||||
self.assertIn((socket.AF_INET, ("1.2.3.4", 80)), result)
|
||||
|
||||
result = yield self.resolver.resolve("google.com", 80, socket.AF_INET6)
|
||||
self.assertIn(
|
||||
(socket.AF_INET6, ("2a02:6b8:7c:40c:c51e:495f:e23a:3", 80, 0, 0)), result
|
||||
)
|
||||
|
||||
|
||||
@skipIfNoNetwork
|
||||
class ThreadedResolverTest(AsyncTestCase, _ResolverTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resolver = ThreadedResolver()
|
||||
|
||||
def tearDown(self):
|
||||
self.resolver.close()
|
||||
super().tearDown()
|
||||
|
||||
|
||||
class ThreadedResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resolver = BlockingResolver()
|
||||
self.real_getaddrinfo = socket.getaddrinfo
|
||||
socket.getaddrinfo = _failing_getaddrinfo
|
||||
|
||||
def tearDown(self):
|
||||
socket.getaddrinfo = self.real_getaddrinfo
|
||||
super().tearDown()
|
||||
|
||||
|
||||
@skipIfNoNetwork
|
||||
@unittest.skipIf(sys.platform == "win32", "preexec_fn not available on win32")
|
||||
class ThreadedResolverImportTest(unittest.TestCase):
|
||||
def test_import(self):
|
||||
TIMEOUT = 5
|
||||
|
||||
# Test for a deadlock when importing a module that runs the
|
||||
# ThreadedResolver at import-time. See resolve_test.py for
|
||||
# full explanation.
|
||||
command = [sys.executable, "-c", "import tornado.test.resolve_test_helper"]
|
||||
|
||||
start = time.time()
|
||||
popen = Popen(command, preexec_fn=lambda: signal.alarm(TIMEOUT))
|
||||
while time.time() - start < TIMEOUT:
|
||||
return_code = popen.poll()
|
||||
if return_code is not None:
|
||||
self.assertEqual(0, return_code)
|
||||
return # Success.
|
||||
time.sleep(0.05)
|
||||
|
||||
self.fail("import timed out")
|
||||
|
||||
|
||||
# We do not test errors with CaresResolver:
|
||||
# Some DNS-hijacking ISPs (e.g. Time Warner) return non-empty results
|
||||
# with an NXDOMAIN status code. Most resolvers treat this as an error;
|
||||
# C-ares returns the results, making the "bad_host" tests unreliable.
|
||||
# C-ares will try to resolve even malformed names, such as the
|
||||
# name with spaces used in this test.
|
||||
@skipIfNoNetwork
|
||||
@unittest.skipIf(pycares is None, "pycares module not present")
|
||||
@unittest.skipIf(sys.platform == "win32", "pycares doesn't return loopback on windows")
|
||||
@unittest.skipIf(sys.platform == "darwin", "pycares doesn't return 127.0.0.1 on darwin")
|
||||
class CaresResolverTest(AsyncTestCase, _ResolverTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resolver = CaresResolver()
|
||||
|
||||
|
||||
# TwistedResolver produces consistent errors in our test cases so we
|
||||
# could test the regular and error cases in the same class. However,
|
||||
# in the error cases it appears that cleanup of socket objects is
|
||||
# handled asynchronously and occasionally results in "unclosed socket"
|
||||
# warnings if not given time to shut down (and there is no way to
|
||||
# explicitly shut it down). This makes the test flaky, so we do not
|
||||
# test error cases here.
|
||||
@skipIfNoNetwork
|
||||
@unittest.skipIf(twisted is None, "twisted module not present")
|
||||
@unittest.skipIf(
|
||||
getattr(twisted, "__version__", "0.0") < "12.1", "old version of twisted"
|
||||
)
|
||||
@unittest.skipIf(sys.platform == "win32", "twisted resolver hangs on windows")
|
||||
class TwistedResolverTest(AsyncTestCase, _ResolverTestMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resolver = TwistedResolver()
|
||||
|
||||
|
||||
class IsValidIPTest(unittest.TestCase):
|
||||
def test_is_valid_ip(self):
|
||||
self.assertTrue(is_valid_ip("127.0.0.1"))
|
||||
self.assertTrue(is_valid_ip("4.4.4.4"))
|
||||
self.assertTrue(is_valid_ip("::1"))
|
||||
self.assertTrue(is_valid_ip("2620:0:1cfe:face:b00c::3"))
|
||||
self.assertTrue(not is_valid_ip("www.google.com"))
|
||||
self.assertTrue(not is_valid_ip("localhost"))
|
||||
self.assertTrue(not is_valid_ip("4.4.4.4<"))
|
||||
self.assertTrue(not is_valid_ip(" 127.0.0.1"))
|
||||
self.assertTrue(not is_valid_ip(""))
|
||||
self.assertTrue(not is_valid_ip(" "))
|
||||
self.assertTrue(not is_valid_ip("\n"))
|
||||
self.assertTrue(not is_valid_ip("\x00"))
|
||||
|
||||
|
||||
class TestPortAllocation(unittest.TestCase):
|
||||
def test_same_port_allocation(self):
|
||||
if "TRAVIS" in os.environ:
|
||||
self.skipTest("dual-stack servers often have port conflicts on travis")
|
||||
sockets = bind_sockets(0, "localhost")
|
||||
try:
|
||||
port = sockets[0].getsockname()[1]
|
||||
self.assertTrue(all(s.getsockname()[1] == port for s in sockets[1:]))
|
||||
finally:
|
||||
for sock in sockets:
|
||||
sock.close()
|
||||
|
||||
@unittest.skipIf(
|
||||
not hasattr(socket, "SO_REUSEPORT"), "SO_REUSEPORT is not supported"
|
||||
)
|
||||
def test_reuse_port(self):
|
||||
sockets = [] # type: List[socket.socket]
|
||||
socket, port = bind_unused_port(reuse_port=True)
|
||||
try:
|
||||
sockets = bind_sockets(port, "127.0.0.1", reuse_port=True)
|
||||
self.assertTrue(all(s.getsockname()[1] == port for s in sockets))
|
||||
finally:
|
||||
socket.close()
|
||||
for sock in sockets:
|
||||
sock.close()
|
7
venv/Lib/site-packages/tornado/test/options_test.cfg
Normal file
7
venv/Lib/site-packages/tornado/test/options_test.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
port=443
|
||||
port=443
|
||||
username='李康'
|
||||
|
||||
foo_bar='a'
|
||||
|
||||
my_path = __file__
|
328
venv/Lib/site-packages/tornado/test/options_test.py
Normal file
328
venv/Lib/site-packages/tornado/test/options_test.py
Normal file
|
@ -0,0 +1,328 @@
|
|||
import datetime
|
||||
from io import StringIO
|
||||
import os
|
||||
import sys
|
||||
from unittest import mock
|
||||
import unittest
|
||||
|
||||
from tornado.options import OptionParser, Error
|
||||
from tornado.util import basestring_type
|
||||
from tornado.test.util import subTest
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import List # noqa: F401
|
||||
|
||||
|
||||
class Email(object):
|
||||
def __init__(self, value):
|
||||
if isinstance(value, str) and "@" in value:
|
||||
self._value = value
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
|
||||
class OptionsTest(unittest.TestCase):
|
||||
def test_parse_command_line(self):
|
||||
options = OptionParser()
|
||||
options.define("port", default=80)
|
||||
options.parse_command_line(["main.py", "--port=443"])
|
||||
self.assertEqual(options.port, 443)
|
||||
|
||||
def test_parse_config_file(self):
|
||||
options = OptionParser()
|
||||
options.define("port", default=80)
|
||||
options.define("username", default="foo")
|
||||
options.define("my_path")
|
||||
config_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "options_test.cfg"
|
||||
)
|
||||
options.parse_config_file(config_path)
|
||||
self.assertEqual(options.port, 443)
|
||||
self.assertEqual(options.username, "李康")
|
||||
self.assertEqual(options.my_path, config_path)
|
||||
|
||||
def test_parse_callbacks(self):
|
||||
options = OptionParser()
|
||||
self.called = False
|
||||
|
||||
def callback():
|
||||
self.called = True
|
||||
|
||||
options.add_parse_callback(callback)
|
||||
|
||||
# non-final parse doesn't run callbacks
|
||||
options.parse_command_line(["main.py"], final=False)
|
||||
self.assertFalse(self.called)
|
||||
|
||||
# final parse does
|
||||
options.parse_command_line(["main.py"])
|
||||
self.assertTrue(self.called)
|
||||
|
||||
# callbacks can be run more than once on the same options
|
||||
# object if there are multiple final parses
|
||||
self.called = False
|
||||
options.parse_command_line(["main.py"])
|
||||
self.assertTrue(self.called)
|
||||
|
||||
def test_help(self):
|
||||
options = OptionParser()
|
||||
try:
|
||||
orig_stderr = sys.stderr
|
||||
sys.stderr = StringIO()
|
||||
with self.assertRaises(SystemExit):
|
||||
options.parse_command_line(["main.py", "--help"])
|
||||
usage = sys.stderr.getvalue()
|
||||
finally:
|
||||
sys.stderr = orig_stderr
|
||||
self.assertIn("Usage:", usage)
|
||||
|
||||
def test_subcommand(self):
|
||||
base_options = OptionParser()
|
||||
base_options.define("verbose", default=False)
|
||||
sub_options = OptionParser()
|
||||
sub_options.define("foo", type=str)
|
||||
rest = base_options.parse_command_line(
|
||||
["main.py", "--verbose", "subcommand", "--foo=bar"]
|
||||
)
|
||||
self.assertEqual(rest, ["subcommand", "--foo=bar"])
|
||||
self.assertTrue(base_options.verbose)
|
||||
rest2 = sub_options.parse_command_line(rest)
|
||||
self.assertEqual(rest2, [])
|
||||
self.assertEqual(sub_options.foo, "bar")
|
||||
|
||||
# the two option sets are distinct
|
||||
try:
|
||||
orig_stderr = sys.stderr
|
||||
sys.stderr = StringIO()
|
||||
with self.assertRaises(Error):
|
||||
sub_options.parse_command_line(["subcommand", "--verbose"])
|
||||
finally:
|
||||
sys.stderr = orig_stderr
|
||||
|
||||
def test_setattr(self):
|
||||
options = OptionParser()
|
||||
options.define("foo", default=1, type=int)
|
||||
options.foo = 2
|
||||
self.assertEqual(options.foo, 2)
|
||||
|
||||
def test_setattr_type_check(self):
|
||||
# setattr requires that options be the right type and doesn't
|
||||
# parse from string formats.
|
||||
options = OptionParser()
|
||||
options.define("foo", default=1, type=int)
|
||||
with self.assertRaises(Error):
|
||||
options.foo = "2"
|
||||
|
||||
def test_setattr_with_callback(self):
|
||||
values = [] # type: List[int]
|
||||
options = OptionParser()
|
||||
options.define("foo", default=1, type=int, callback=values.append)
|
||||
options.foo = 2
|
||||
self.assertEqual(values, [2])
|
||||
|
||||
def _sample_options(self):
|
||||
options = OptionParser()
|
||||
options.define("a", default=1)
|
||||
options.define("b", default=2)
|
||||
return options
|
||||
|
||||
def test_iter(self):
|
||||
options = self._sample_options()
|
||||
# OptionParsers always define 'help'.
|
||||
self.assertEqual(set(["a", "b", "help"]), set(iter(options)))
|
||||
|
||||
def test_getitem(self):
|
||||
options = self._sample_options()
|
||||
self.assertEqual(1, options["a"])
|
||||
|
||||
def test_setitem(self):
|
||||
options = OptionParser()
|
||||
options.define("foo", default=1, type=int)
|
||||
options["foo"] = 2
|
||||
self.assertEqual(options["foo"], 2)
|
||||
|
||||
def test_items(self):
|
||||
options = self._sample_options()
|
||||
# OptionParsers always define 'help'.
|
||||
expected = [("a", 1), ("b", 2), ("help", options.help)]
|
||||
actual = sorted(options.items())
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_as_dict(self):
|
||||
options = self._sample_options()
|
||||
expected = {"a": 1, "b": 2, "help": options.help}
|
||||
self.assertEqual(expected, options.as_dict())
|
||||
|
||||
def test_group_dict(self):
|
||||
options = OptionParser()
|
||||
options.define("a", default=1)
|
||||
options.define("b", group="b_group", default=2)
|
||||
|
||||
frame = sys._getframe(0)
|
||||
this_file = frame.f_code.co_filename
|
||||
self.assertEqual(set(["b_group", "", this_file]), options.groups())
|
||||
|
||||
b_group_dict = options.group_dict("b_group")
|
||||
self.assertEqual({"b": 2}, b_group_dict)
|
||||
|
||||
self.assertEqual({}, options.group_dict("nonexistent"))
|
||||
|
||||
def test_mock_patch(self):
|
||||
# ensure that our setattr hooks don't interfere with mock.patch
|
||||
options = OptionParser()
|
||||
options.define("foo", default=1)
|
||||
options.parse_command_line(["main.py", "--foo=2"])
|
||||
self.assertEqual(options.foo, 2)
|
||||
|
||||
with mock.patch.object(options.mockable(), "foo", 3):
|
||||
self.assertEqual(options.foo, 3)
|
||||
self.assertEqual(options.foo, 2)
|
||||
|
||||
# Try nested patches mixed with explicit sets
|
||||
with mock.patch.object(options.mockable(), "foo", 4):
|
||||
self.assertEqual(options.foo, 4)
|
||||
options.foo = 5
|
||||
self.assertEqual(options.foo, 5)
|
||||
with mock.patch.object(options.mockable(), "foo", 6):
|
||||
self.assertEqual(options.foo, 6)
|
||||
self.assertEqual(options.foo, 5)
|
||||
self.assertEqual(options.foo, 2)
|
||||
|
||||
def _define_options(self):
|
||||
options = OptionParser()
|
||||
options.define("str", type=str)
|
||||
options.define("basestring", type=basestring_type)
|
||||
options.define("int", type=int)
|
||||
options.define("float", type=float)
|
||||
options.define("datetime", type=datetime.datetime)
|
||||
options.define("timedelta", type=datetime.timedelta)
|
||||
options.define("email", type=Email)
|
||||
options.define("list-of-int", type=int, multiple=True)
|
||||
return options
|
||||
|
||||
def _check_options_values(self, options):
|
||||
self.assertEqual(options.str, "asdf")
|
||||
self.assertEqual(options.basestring, "qwer")
|
||||
self.assertEqual(options.int, 42)
|
||||
self.assertEqual(options.float, 1.5)
|
||||
self.assertEqual(options.datetime, datetime.datetime(2013, 4, 28, 5, 16))
|
||||
self.assertEqual(options.timedelta, datetime.timedelta(seconds=45))
|
||||
self.assertEqual(options.email.value, "tornado@web.com")
|
||||
self.assertTrue(isinstance(options.email, Email))
|
||||
self.assertEqual(options.list_of_int, [1, 2, 3])
|
||||
|
||||
def test_types(self):
|
||||
options = self._define_options()
|
||||
options.parse_command_line(
|
||||
[
|
||||
"main.py",
|
||||
"--str=asdf",
|
||||
"--basestring=qwer",
|
||||
"--int=42",
|
||||
"--float=1.5",
|
||||
"--datetime=2013-04-28 05:16",
|
||||
"--timedelta=45s",
|
||||
"--email=tornado@web.com",
|
||||
"--list-of-int=1,2,3",
|
||||
]
|
||||
)
|
||||
self._check_options_values(options)
|
||||
|
||||
def test_types_with_conf_file(self):
|
||||
for config_file_name in (
|
||||
"options_test_types.cfg",
|
||||
"options_test_types_str.cfg",
|
||||
):
|
||||
options = self._define_options()
|
||||
options.parse_config_file(
|
||||
os.path.join(os.path.dirname(__file__), config_file_name)
|
||||
)
|
||||
self._check_options_values(options)
|
||||
|
||||
def test_multiple_string(self):
|
||||
options = OptionParser()
|
||||
options.define("foo", type=str, multiple=True)
|
||||
options.parse_command_line(["main.py", "--foo=a,b,c"])
|
||||
self.assertEqual(options.foo, ["a", "b", "c"])
|
||||
|
||||
def test_multiple_int(self):
|
||||
options = OptionParser()
|
||||
options.define("foo", type=int, multiple=True)
|
||||
options.parse_command_line(["main.py", "--foo=1,3,5:7"])
|
||||
self.assertEqual(options.foo, [1, 3, 5, 6, 7])
|
||||
|
||||
def test_error_redefine(self):
|
||||
options = OptionParser()
|
||||
options.define("foo")
|
||||
with self.assertRaises(Error) as cm:
|
||||
options.define("foo")
|
||||
self.assertRegexpMatches(str(cm.exception), "Option.*foo.*already defined")
|
||||
|
||||
def test_error_redefine_underscore(self):
|
||||
# Ensure that the dash/underscore normalization doesn't
|
||||
# interfere with the redefinition error.
|
||||
tests = [
|
||||
("foo-bar", "foo-bar"),
|
||||
("foo_bar", "foo_bar"),
|
||||
("foo-bar", "foo_bar"),
|
||||
("foo_bar", "foo-bar"),
|
||||
]
|
||||
for a, b in tests:
|
||||
with subTest(self, a=a, b=b):
|
||||
options = OptionParser()
|
||||
options.define(a)
|
||||
with self.assertRaises(Error) as cm:
|
||||
options.define(b)
|
||||
self.assertRegexpMatches(
|
||||
str(cm.exception), "Option.*foo.bar.*already defined"
|
||||
)
|
||||
|
||||
def test_dash_underscore_cli(self):
|
||||
# Dashes and underscores should be interchangeable.
|
||||
for defined_name in ["foo-bar", "foo_bar"]:
|
||||
for flag in ["--foo-bar=a", "--foo_bar=a"]:
|
||||
options = OptionParser()
|
||||
options.define(defined_name)
|
||||
options.parse_command_line(["main.py", flag])
|
||||
# Attr-style access always uses underscores.
|
||||
self.assertEqual(options.foo_bar, "a")
|
||||
# Dict-style access allows both.
|
||||
self.assertEqual(options["foo-bar"], "a")
|
||||
self.assertEqual(options["foo_bar"], "a")
|
||||
|
||||
def test_dash_underscore_file(self):
|
||||
# No matter how an option was defined, it can be set with underscores
|
||||
# in a config file.
|
||||
for defined_name in ["foo-bar", "foo_bar"]:
|
||||
options = OptionParser()
|
||||
options.define(defined_name)
|
||||
options.parse_config_file(
|
||||
os.path.join(os.path.dirname(__file__), "options_test.cfg")
|
||||
)
|
||||
self.assertEqual(options.foo_bar, "a")
|
||||
|
||||
def test_dash_underscore_introspection(self):
|
||||
# Original names are preserved in introspection APIs.
|
||||
options = OptionParser()
|
||||
options.define("with-dash", group="g")
|
||||
options.define("with_underscore", group="g")
|
||||
all_options = ["help", "with-dash", "with_underscore"]
|
||||
self.assertEqual(sorted(options), all_options)
|
||||
self.assertEqual(sorted(k for (k, v) in options.items()), all_options)
|
||||
self.assertEqual(sorted(options.as_dict().keys()), all_options)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(options.group_dict("g")), ["with-dash", "with_underscore"]
|
||||
)
|
||||
|
||||
# --help shows CLI-style names with dashes.
|
||||
buf = StringIO()
|
||||
options.print_help(buf)
|
||||
self.assertIn("--with-dash", buf.getvalue())
|
||||
self.assertIn("--with-underscore", buf.getvalue())
|
11
venv/Lib/site-packages/tornado/test/options_test_types.cfg
Normal file
11
venv/Lib/site-packages/tornado/test/options_test_types.cfg
Normal file
|
@ -0,0 +1,11 @@
|
|||
from datetime import datetime, timedelta
|
||||
from tornado.test.options_test import Email
|
||||
|
||||
str = 'asdf'
|
||||
basestring = 'qwer'
|
||||
int = 42
|
||||
float = 1.5
|
||||
datetime = datetime(2013, 4, 28, 5, 16)
|
||||
timedelta = timedelta(0, 45)
|
||||
email = Email('tornado@web.com')
|
||||
list_of_int = [1, 2, 3]
|
|
@ -0,0 +1,8 @@
|
|||
str = 'asdf'
|
||||
basestring = 'qwer'
|
||||
int = 42
|
||||
float = 1.5
|
||||
datetime = '2013-04-28 05:16'
|
||||
timedelta = '45s'
|
||||
email = 'tornado@web.com'
|
||||
list_of_int = '1,2,3'
|
274
venv/Lib/site-packages/tornado/test/process_test.py
Normal file
274
venv/Lib/site-packages/tornado/test/process_test.py
Normal file
|
@ -0,0 +1,274 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from tornado.httpclient import HTTPClient, HTTPError
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.log import gen_log
|
||||
from tornado.process import fork_processes, task_id, Subprocess
|
||||
from tornado.simple_httpclient import SimpleAsyncHTTPClient
|
||||
from tornado.testing import bind_unused_port, ExpectLog, AsyncTestCase, gen_test
|
||||
from tornado.test.util import skipIfNonUnix
|
||||
from tornado.web import RequestHandler, Application
|
||||
|
||||
|
||||
# Not using AsyncHTTPTestCase because we need control over the IOLoop.
|
||||
@skipIfNonUnix
|
||||
class ProcessTest(unittest.TestCase):
|
||||
def get_app(self):
|
||||
class ProcessHandler(RequestHandler):
|
||||
def get(self):
|
||||
if self.get_argument("exit", None):
|
||||
# must use os._exit instead of sys.exit so unittest's
|
||||
# exception handler doesn't catch it
|
||||
os._exit(int(self.get_argument("exit")))
|
||||
if self.get_argument("signal", None):
|
||||
os.kill(os.getpid(), int(self.get_argument("signal")))
|
||||
self.write(str(os.getpid()))
|
||||
|
||||
return Application([("/", ProcessHandler)])
|
||||
|
||||
def tearDown(self):
|
||||
if task_id() is not None:
|
||||
# We're in a child process, and probably got to this point
|
||||
# via an uncaught exception. If we return now, both
|
||||
# processes will continue with the rest of the test suite.
|
||||
# Exit now so the parent process will restart the child
|
||||
# (since we don't have a clean way to signal failure to
|
||||
# the parent that won't restart)
|
||||
logging.error("aborting child process from tearDown")
|
||||
logging.shutdown()
|
||||
os._exit(1)
|
||||
# In the surviving process, clear the alarm we set earlier
|
||||
signal.alarm(0)
|
||||
super().tearDown()
|
||||
|
||||
def test_multi_process(self):
|
||||
# This test doesn't work on twisted because we use the global
|
||||
# reactor and don't restore it to a sane state after the fork
|
||||
# (asyncio has the same issue, but we have a special case in
|
||||
# place for it).
|
||||
with ExpectLog(
|
||||
gen_log, "(Starting .* processes|child .* exited|uncaught exception)"
|
||||
):
|
||||
sock, port = bind_unused_port()
|
||||
|
||||
def get_url(path):
|
||||
return "http://127.0.0.1:%d%s" % (port, path)
|
||||
|
||||
# ensure that none of these processes live too long
|
||||
signal.alarm(5) # master process
|
||||
try:
|
||||
id = fork_processes(3, max_restarts=3)
|
||||
self.assertTrue(id is not None)
|
||||
signal.alarm(5) # child processes
|
||||
except SystemExit as e:
|
||||
# if we exit cleanly from fork_processes, all the child processes
|
||||
# finished with status 0
|
||||
self.assertEqual(e.code, 0)
|
||||
self.assertTrue(task_id() is None)
|
||||
sock.close()
|
||||
return
|
||||
try:
|
||||
if asyncio is not None:
|
||||
# Reset the global asyncio event loop, which was put into
|
||||
# a broken state by the fork.
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
if id in (0, 1):
|
||||
self.assertEqual(id, task_id())
|
||||
server = HTTPServer(self.get_app())
|
||||
server.add_sockets([sock])
|
||||
IOLoop.current().start()
|
||||
elif id == 2:
|
||||
self.assertEqual(id, task_id())
|
||||
sock.close()
|
||||
# Always use SimpleAsyncHTTPClient here; the curl
|
||||
# version appears to get confused sometimes if the
|
||||
# connection gets closed before it's had a chance to
|
||||
# switch from writing mode to reading mode.
|
||||
client = HTTPClient(SimpleAsyncHTTPClient)
|
||||
|
||||
def fetch(url, fail_ok=False):
|
||||
try:
|
||||
return client.fetch(get_url(url))
|
||||
except HTTPError as e:
|
||||
if not (fail_ok and e.code == 599):
|
||||
raise
|
||||
|
||||
# Make two processes exit abnormally
|
||||
fetch("/?exit=2", fail_ok=True)
|
||||
fetch("/?exit=3", fail_ok=True)
|
||||
|
||||
# They've been restarted, so a new fetch will work
|
||||
int(fetch("/").body)
|
||||
|
||||
# Now the same with signals
|
||||
# Disabled because on the mac a process dying with a signal
|
||||
# can trigger an "Application exited abnormally; send error
|
||||
# report to Apple?" prompt.
|
||||
# fetch("/?signal=%d" % signal.SIGTERM, fail_ok=True)
|
||||
# fetch("/?signal=%d" % signal.SIGABRT, fail_ok=True)
|
||||
# int(fetch("/").body)
|
||||
|
||||
# Now kill them normally so they won't be restarted
|
||||
fetch("/?exit=0", fail_ok=True)
|
||||
# One process left; watch it's pid change
|
||||
pid = int(fetch("/").body)
|
||||
fetch("/?exit=4", fail_ok=True)
|
||||
pid2 = int(fetch("/").body)
|
||||
self.assertNotEqual(pid, pid2)
|
||||
|
||||
# Kill the last one so we shut down cleanly
|
||||
fetch("/?exit=0", fail_ok=True)
|
||||
|
||||
os._exit(0)
|
||||
except Exception:
|
||||
logging.error("exception in child process %d", id, exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
@skipIfNonUnix
|
||||
class SubprocessTest(AsyncTestCase):
|
||||
def term_and_wait(self, subproc):
|
||||
subproc.proc.terminate()
|
||||
subproc.proc.wait()
|
||||
|
||||
@gen_test
|
||||
def test_subprocess(self):
|
||||
if IOLoop.configured_class().__name__.endswith("LayeredTwistedIOLoop"):
|
||||
# This test fails non-deterministically with LayeredTwistedIOLoop.
|
||||
# (the read_until('\n') returns '\n' instead of 'hello\n')
|
||||
# This probably indicates a problem with either TornadoReactor
|
||||
# or TwistedIOLoop, but I haven't been able to track it down
|
||||
# and for now this is just causing spurious travis-ci failures.
|
||||
raise unittest.SkipTest(
|
||||
"Subprocess tests not compatible with " "LayeredTwistedIOLoop"
|
||||
)
|
||||
subproc = Subprocess(
|
||||
[sys.executable, "-u", "-i"],
|
||||
stdin=Subprocess.STREAM,
|
||||
stdout=Subprocess.STREAM,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
self.addCleanup(lambda: self.term_and_wait(subproc))
|
||||
self.addCleanup(subproc.stdout.close)
|
||||
self.addCleanup(subproc.stdin.close)
|
||||
yield subproc.stdout.read_until(b">>> ")
|
||||
subproc.stdin.write(b"print('hello')\n")
|
||||
data = yield subproc.stdout.read_until(b"\n")
|
||||
self.assertEqual(data, b"hello\n")
|
||||
|
||||
yield subproc.stdout.read_until(b">>> ")
|
||||
subproc.stdin.write(b"raise SystemExit\n")
|
||||
data = yield subproc.stdout.read_until_close()
|
||||
self.assertEqual(data, b"")
|
||||
|
||||
@gen_test
|
||||
def test_close_stdin(self):
|
||||
# Close the parent's stdin handle and see that the child recognizes it.
|
||||
subproc = Subprocess(
|
||||
[sys.executable, "-u", "-i"],
|
||||
stdin=Subprocess.STREAM,
|
||||
stdout=Subprocess.STREAM,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
self.addCleanup(lambda: self.term_and_wait(subproc))
|
||||
yield subproc.stdout.read_until(b">>> ")
|
||||
subproc.stdin.close()
|
||||
data = yield subproc.stdout.read_until_close()
|
||||
self.assertEqual(data, b"\n")
|
||||
|
||||
@gen_test
|
||||
def test_stderr(self):
|
||||
# This test is mysteriously flaky on twisted: it succeeds, but logs
|
||||
# an error of EBADF on closing a file descriptor.
|
||||
subproc = Subprocess(
|
||||
[sys.executable, "-u", "-c", r"import sys; sys.stderr.write('hello\n')"],
|
||||
stderr=Subprocess.STREAM,
|
||||
)
|
||||
self.addCleanup(lambda: self.term_and_wait(subproc))
|
||||
data = yield subproc.stderr.read_until(b"\n")
|
||||
self.assertEqual(data, b"hello\n")
|
||||
# More mysterious EBADF: This fails if done with self.addCleanup instead of here.
|
||||
subproc.stderr.close()
|
||||
|
||||
def test_sigchild(self):
|
||||
Subprocess.initialize()
|
||||
self.addCleanup(Subprocess.uninitialize)
|
||||
subproc = Subprocess([sys.executable, "-c", "pass"])
|
||||
subproc.set_exit_callback(self.stop)
|
||||
ret = self.wait()
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertEqual(subproc.returncode, ret)
|
||||
|
||||
@gen_test
|
||||
def test_sigchild_future(self):
|
||||
Subprocess.initialize()
|
||||
self.addCleanup(Subprocess.uninitialize)
|
||||
subproc = Subprocess([sys.executable, "-c", "pass"])
|
||||
ret = yield subproc.wait_for_exit()
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertEqual(subproc.returncode, ret)
|
||||
|
||||
def test_sigchild_signal(self):
|
||||
Subprocess.initialize()
|
||||
self.addCleanup(Subprocess.uninitialize)
|
||||
subproc = Subprocess(
|
||||
[sys.executable, "-c", "import time; time.sleep(30)"],
|
||||
stdout=Subprocess.STREAM,
|
||||
)
|
||||
self.addCleanup(subproc.stdout.close)
|
||||
subproc.set_exit_callback(self.stop)
|
||||
|
||||
# For unclear reasons, killing a process too soon after
|
||||
# creating it can result in an exit status corresponding to
|
||||
# SIGKILL instead of the actual signal involved. This has been
|
||||
# observed on macOS 10.15 with Python 3.8 installed via brew,
|
||||
# but not with the system-installed Python 3.7.
|
||||
time.sleep(0.1)
|
||||
|
||||
os.kill(subproc.pid, signal.SIGTERM)
|
||||
try:
|
||||
ret = self.wait()
|
||||
except AssertionError:
|
||||
# We failed to get the termination signal. This test is
|
||||
# occasionally flaky on pypy, so try to get a little more
|
||||
# information: did the process close its stdout
|
||||
# (indicating that the problem is in the parent process's
|
||||
# signal handling) or did the child process somehow fail
|
||||
# to terminate?
|
||||
fut = subproc.stdout.read_until_close()
|
||||
fut.add_done_callback(lambda f: self.stop()) # type: ignore
|
||||
try:
|
||||
self.wait()
|
||||
except AssertionError:
|
||||
raise AssertionError("subprocess failed to terminate")
|
||||
else:
|
||||
raise AssertionError(
|
||||
"subprocess closed stdout but failed to " "get termination signal"
|
||||
)
|
||||
self.assertEqual(subproc.returncode, ret)
|
||||
self.assertEqual(ret, -signal.SIGTERM)
|
||||
|
||||
@gen_test
|
||||
def test_wait_for_exit_raise(self):
|
||||
Subprocess.initialize()
|
||||
self.addCleanup(Subprocess.uninitialize)
|
||||
subproc = Subprocess([sys.executable, "-c", "import sys; sys.exit(1)"])
|
||||
with self.assertRaises(subprocess.CalledProcessError) as cm:
|
||||
yield subproc.wait_for_exit()
|
||||
self.assertEqual(cm.exception.returncode, 1)
|
||||
|
||||
@gen_test
|
||||
def test_wait_for_exit_raise_disabled(self):
|
||||
Subprocess.initialize()
|
||||
self.addCleanup(Subprocess.uninitialize)
|
||||
subproc = Subprocess([sys.executable, "-c", "import sys; sys.exit(1)"])
|
||||
ret = yield subproc.wait_for_exit(raise_error=False)
|
||||
self.assertEqual(ret, 1)
|
431
venv/Lib/site-packages/tornado/test/queues_test.py
Normal file
431
venv/Lib/site-packages/tornado/test/queues_test.py
Normal file
|
@ -0,0 +1,431 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from random import random
|
||||
import unittest
|
||||
|
||||
from tornado import gen, queues
|
||||
from tornado.gen import TimeoutError
|
||||
from tornado.testing import gen_test, AsyncTestCase
|
||||
|
||||
|
||||
class QueueBasicTest(AsyncTestCase):
|
||||
def test_repr_and_str(self):
|
||||
q = queues.Queue(maxsize=1) # type: queues.Queue[None]
|
||||
self.assertIn(hex(id(q)), repr(q))
|
||||
self.assertNotIn(hex(id(q)), str(q))
|
||||
q.get()
|
||||
|
||||
for q_str in repr(q), str(q):
|
||||
self.assertTrue(q_str.startswith("<Queue"))
|
||||
self.assertIn("maxsize=1", q_str)
|
||||
self.assertIn("getters[1]", q_str)
|
||||
self.assertNotIn("putters", q_str)
|
||||
self.assertNotIn("tasks", q_str)
|
||||
|
||||
q.put(None)
|
||||
q.put(None)
|
||||
# Now the queue is full, this putter blocks.
|
||||
q.put(None)
|
||||
|
||||
for q_str in repr(q), str(q):
|
||||
self.assertNotIn("getters", q_str)
|
||||
self.assertIn("putters[1]", q_str)
|
||||
self.assertIn("tasks=2", q_str)
|
||||
|
||||
def test_order(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
for i in [1, 3, 2]:
|
||||
q.put_nowait(i)
|
||||
|
||||
items = [q.get_nowait() for _ in range(3)]
|
||||
self.assertEqual([1, 3, 2], items)
|
||||
|
||||
@gen_test
|
||||
def test_maxsize(self):
|
||||
self.assertRaises(TypeError, queues.Queue, maxsize=None)
|
||||
self.assertRaises(ValueError, queues.Queue, maxsize=-1)
|
||||
|
||||
q = queues.Queue(maxsize=2) # type: queues.Queue[int]
|
||||
self.assertTrue(q.empty())
|
||||
self.assertFalse(q.full())
|
||||
self.assertEqual(2, q.maxsize)
|
||||
self.assertTrue(q.put(0).done())
|
||||
self.assertTrue(q.put(1).done())
|
||||
self.assertFalse(q.empty())
|
||||
self.assertTrue(q.full())
|
||||
put2 = q.put(2)
|
||||
self.assertFalse(put2.done())
|
||||
self.assertEqual(0, (yield q.get())) # Make room.
|
||||
self.assertTrue(put2.done())
|
||||
self.assertFalse(q.empty())
|
||||
self.assertTrue(q.full())
|
||||
|
||||
|
||||
class QueueGetTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_blocking_get(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
q.put_nowait(0)
|
||||
self.assertEqual(0, (yield q.get()))
|
||||
|
||||
def test_nonblocking_get(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
q.put_nowait(0)
|
||||
self.assertEqual(0, q.get_nowait())
|
||||
|
||||
def test_nonblocking_get_exception(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
self.assertRaises(queues.QueueEmpty, q.get_nowait)
|
||||
|
||||
@gen_test
|
||||
def test_get_with_putters(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
q.put_nowait(0)
|
||||
put = q.put(1)
|
||||
self.assertEqual(0, (yield q.get()))
|
||||
self.assertIsNone((yield put))
|
||||
|
||||
@gen_test
|
||||
def test_blocking_get_wait(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
q.put(0)
|
||||
self.io_loop.call_later(0.01, q.put_nowait, 1)
|
||||
self.io_loop.call_later(0.02, q.put_nowait, 2)
|
||||
self.assertEqual(0, (yield q.get(timeout=timedelta(seconds=1))))
|
||||
self.assertEqual(1, (yield q.get(timeout=timedelta(seconds=1))))
|
||||
|
||||
@gen_test
|
||||
def test_get_timeout(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
get_timeout = q.get(timeout=timedelta(seconds=0.01))
|
||||
get = q.get()
|
||||
with self.assertRaises(TimeoutError):
|
||||
yield get_timeout
|
||||
|
||||
q.put_nowait(0)
|
||||
self.assertEqual(0, (yield get))
|
||||
|
||||
@gen_test
|
||||
def test_get_timeout_preempted(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
get = q.get(timeout=timedelta(seconds=0.01))
|
||||
q.put(0)
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(0, (yield get))
|
||||
|
||||
@gen_test
|
||||
def test_get_clears_timed_out_putters(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
# First putter succeeds, remainder block.
|
||||
putters = [q.put(i, timedelta(seconds=0.01)) for i in range(10)]
|
||||
put = q.put(10)
|
||||
self.assertEqual(10, len(q._putters))
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(10, len(q._putters))
|
||||
self.assertFalse(put.done()) # Final waiter is still active.
|
||||
q.put(11)
|
||||
self.assertEqual(0, (yield q.get())) # get() clears the waiters.
|
||||
self.assertEqual(1, len(q._putters))
|
||||
for putter in putters[1:]:
|
||||
self.assertRaises(TimeoutError, putter.result)
|
||||
|
||||
@gen_test
|
||||
def test_get_clears_timed_out_getters(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
getters = [
|
||||
asyncio.ensure_future(q.get(timedelta(seconds=0.01))) for _ in range(10)
|
||||
]
|
||||
get = asyncio.ensure_future(q.get())
|
||||
self.assertEqual(11, len(q._getters))
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(11, len(q._getters))
|
||||
self.assertFalse(get.done()) # Final waiter is still active.
|
||||
q.get() # get() clears the waiters.
|
||||
self.assertEqual(2, len(q._getters))
|
||||
for getter in getters:
|
||||
self.assertRaises(TimeoutError, getter.result)
|
||||
|
||||
@gen_test
|
||||
def test_async_for(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
for i in range(5):
|
||||
q.put(i)
|
||||
|
||||
async def f():
|
||||
results = []
|
||||
async for i in q:
|
||||
results.append(i)
|
||||
if i == 4:
|
||||
return results
|
||||
|
||||
results = yield f()
|
||||
self.assertEqual(results, list(range(5)))
|
||||
|
||||
|
||||
class QueuePutTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_blocking_put(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
q.put(0)
|
||||
self.assertEqual(0, q.get_nowait())
|
||||
|
||||
def test_nonblocking_put_exception(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
q.put(0)
|
||||
self.assertRaises(queues.QueueFull, q.put_nowait, 1)
|
||||
|
||||
@gen_test
|
||||
def test_put_with_getters(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
get0 = q.get()
|
||||
get1 = q.get()
|
||||
yield q.put(0)
|
||||
self.assertEqual(0, (yield get0))
|
||||
yield q.put(1)
|
||||
self.assertEqual(1, (yield get1))
|
||||
|
||||
@gen_test
|
||||
def test_nonblocking_put_with_getters(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
get0 = q.get()
|
||||
get1 = q.get()
|
||||
q.put_nowait(0)
|
||||
# put_nowait does *not* immediately unblock getters.
|
||||
yield gen.moment
|
||||
self.assertEqual(0, (yield get0))
|
||||
q.put_nowait(1)
|
||||
yield gen.moment
|
||||
self.assertEqual(1, (yield get1))
|
||||
|
||||
@gen_test
|
||||
def test_blocking_put_wait(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
q.put_nowait(0)
|
||||
|
||||
def get_and_discard():
|
||||
q.get()
|
||||
|
||||
self.io_loop.call_later(0.01, get_and_discard)
|
||||
self.io_loop.call_later(0.02, get_and_discard)
|
||||
futures = [q.put(0), q.put(1)]
|
||||
self.assertFalse(any(f.done() for f in futures))
|
||||
yield futures
|
||||
|
||||
@gen_test
|
||||
def test_put_timeout(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
q.put_nowait(0) # Now it's full.
|
||||
put_timeout = q.put(1, timeout=timedelta(seconds=0.01))
|
||||
put = q.put(2)
|
||||
with self.assertRaises(TimeoutError):
|
||||
yield put_timeout
|
||||
|
||||
self.assertEqual(0, q.get_nowait())
|
||||
# 1 was never put in the queue.
|
||||
self.assertEqual(2, (yield q.get()))
|
||||
|
||||
# Final get() unblocked this putter.
|
||||
yield put
|
||||
|
||||
@gen_test
|
||||
def test_put_timeout_preempted(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
q.put_nowait(0)
|
||||
put = q.put(1, timeout=timedelta(seconds=0.01))
|
||||
q.get()
|
||||
yield gen.sleep(0.02)
|
||||
yield put # No TimeoutError.
|
||||
|
||||
@gen_test
|
||||
def test_put_clears_timed_out_putters(self):
|
||||
q = queues.Queue(1) # type: queues.Queue[int]
|
||||
# First putter succeeds, remainder block.
|
||||
putters = [q.put(i, timedelta(seconds=0.01)) for i in range(10)]
|
||||
put = q.put(10)
|
||||
self.assertEqual(10, len(q._putters))
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(10, len(q._putters))
|
||||
self.assertFalse(put.done()) # Final waiter is still active.
|
||||
q.put(11) # put() clears the waiters.
|
||||
self.assertEqual(2, len(q._putters))
|
||||
for putter in putters[1:]:
|
||||
self.assertRaises(TimeoutError, putter.result)
|
||||
|
||||
@gen_test
|
||||
def test_put_clears_timed_out_getters(self):
|
||||
q = queues.Queue() # type: queues.Queue[int]
|
||||
getters = [
|
||||
asyncio.ensure_future(q.get(timedelta(seconds=0.01))) for _ in range(10)
|
||||
]
|
||||
get = asyncio.ensure_future(q.get())
|
||||
q.get()
|
||||
self.assertEqual(12, len(q._getters))
|
||||
yield gen.sleep(0.02)
|
||||
self.assertEqual(12, len(q._getters))
|
||||
self.assertFalse(get.done()) # Final waiters still active.
|
||||
q.put(0) # put() clears the waiters.
|
||||
self.assertEqual(1, len(q._getters))
|
||||
self.assertEqual(0, (yield get))
|
||||
for getter in getters:
|
||||
self.assertRaises(TimeoutError, getter.result)
|
||||
|
||||
@gen_test
|
||||
def test_float_maxsize(self):
|
||||
# If a float is passed for maxsize, a reasonable limit should
|
||||
# be enforced, instead of being treated as unlimited.
|
||||
# It happens to be rounded up.
|
||||
# http://bugs.python.org/issue21723
|
||||
q = queues.Queue(maxsize=1.3) # type: ignore
|
||||
self.assertTrue(q.empty())
|
||||
self.assertFalse(q.full())
|
||||
q.put_nowait(0)
|
||||
q.put_nowait(1)
|
||||
self.assertFalse(q.empty())
|
||||
self.assertTrue(q.full())
|
||||
self.assertRaises(queues.QueueFull, q.put_nowait, 2)
|
||||
self.assertEqual(0, q.get_nowait())
|
||||
self.assertFalse(q.empty())
|
||||
self.assertFalse(q.full())
|
||||
|
||||
yield q.put(2)
|
||||
put = q.put(3)
|
||||
self.assertFalse(put.done())
|
||||
self.assertEqual(1, (yield q.get()))
|
||||
yield put
|
||||
self.assertTrue(q.full())
|
||||
|
||||
|
||||
class QueueJoinTest(AsyncTestCase):
|
||||
queue_class = queues.Queue
|
||||
|
||||
def test_task_done_underflow(self):
|
||||
q = self.queue_class() # type: queues.Queue
|
||||
self.assertRaises(ValueError, q.task_done)
|
||||
|
||||
@gen_test
|
||||
def test_task_done(self):
|
||||
q = self.queue_class() # type: queues.Queue
|
||||
for i in range(100):
|
||||
q.put_nowait(i)
|
||||
|
||||
self.accumulator = 0
|
||||
|
||||
@gen.coroutine
|
||||
def worker():
|
||||
while True:
|
||||
item = yield q.get()
|
||||
self.accumulator += item
|
||||
q.task_done()
|
||||
yield gen.sleep(random() * 0.01)
|
||||
|
||||
# Two coroutines share work.
|
||||
worker()
|
||||
worker()
|
||||
yield q.join()
|
||||
self.assertEqual(sum(range(100)), self.accumulator)
|
||||
|
||||
@gen_test
|
||||
def test_task_done_delay(self):
|
||||
# Verify it is task_done(), not get(), that unblocks join().
|
||||
q = self.queue_class() # type: queues.Queue
|
||||
q.put_nowait(0)
|
||||
join = asyncio.ensure_future(q.join())
|
||||
self.assertFalse(join.done())
|
||||
yield q.get()
|
||||
self.assertFalse(join.done())
|
||||
yield gen.moment
|
||||
self.assertFalse(join.done())
|
||||
q.task_done()
|
||||
self.assertTrue(join.done())
|
||||
|
||||
@gen_test
|
||||
def test_join_empty_queue(self):
|
||||
q = self.queue_class() # type: queues.Queue
|
||||
yield q.join()
|
||||
yield q.join()
|
||||
|
||||
@gen_test
|
||||
def test_join_timeout(self):
|
||||
q = self.queue_class() # type: queues.Queue
|
||||
q.put(0)
|
||||
with self.assertRaises(TimeoutError):
|
||||
yield q.join(timeout=timedelta(seconds=0.01))
|
||||
|
||||
|
||||
class PriorityQueueJoinTest(QueueJoinTest):
|
||||
queue_class = queues.PriorityQueue
|
||||
|
||||
@gen_test
|
||||
def test_order(self):
|
||||
q = self.queue_class(maxsize=2)
|
||||
q.put_nowait((1, "a"))
|
||||
q.put_nowait((0, "b"))
|
||||
self.assertTrue(q.full())
|
||||
q.put((3, "c"))
|
||||
q.put((2, "d"))
|
||||
self.assertEqual((0, "b"), q.get_nowait())
|
||||
self.assertEqual((1, "a"), (yield q.get()))
|
||||
self.assertEqual((2, "d"), q.get_nowait())
|
||||
self.assertEqual((3, "c"), (yield q.get()))
|
||||
self.assertTrue(q.empty())
|
||||
|
||||
|
||||
class LifoQueueJoinTest(QueueJoinTest):
|
||||
queue_class = queues.LifoQueue
|
||||
|
||||
@gen_test
|
||||
def test_order(self):
|
||||
q = self.queue_class(maxsize=2)
|
||||
q.put_nowait(1)
|
||||
q.put_nowait(0)
|
||||
self.assertTrue(q.full())
|
||||
q.put(3)
|
||||
q.put(2)
|
||||
self.assertEqual(3, q.get_nowait())
|
||||
self.assertEqual(2, (yield q.get()))
|
||||
self.assertEqual(0, q.get_nowait())
|
||||
self.assertEqual(1, (yield q.get()))
|
||||
self.assertTrue(q.empty())
|
||||
|
||||
|
||||
class ProducerConsumerTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_producer_consumer(self):
|
||||
q = queues.Queue(maxsize=3) # type: queues.Queue[int]
|
||||
history = []
|
||||
|
||||
# We don't yield between get() and task_done(), so get() must wait for
|
||||
# the next tick. Otherwise we'd immediately call task_done and unblock
|
||||
# join() before q.put() resumes, and we'd only process the first four
|
||||
# items.
|
||||
@gen.coroutine
|
||||
def consumer():
|
||||
while True:
|
||||
history.append((yield q.get()))
|
||||
q.task_done()
|
||||
|
||||
@gen.coroutine
|
||||
def producer():
|
||||
for item in range(10):
|
||||
yield q.put(item)
|
||||
|
||||
consumer()
|
||||
yield producer()
|
||||
yield q.join()
|
||||
self.assertEqual(list(range(10)), history)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
10
venv/Lib/site-packages/tornado/test/resolve_test_helper.py
Normal file
10
venv/Lib/site-packages/tornado/test/resolve_test_helper.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from tornado.ioloop import IOLoop
|
||||
from tornado.netutil import ThreadedResolver
|
||||
|
||||
# When this module is imported, it runs getaddrinfo on a thread. Since
|
||||
# the hostname is unicode, getaddrinfo attempts to import encodings.idna
|
||||
# but blocks on the import lock. Verify that ThreadedResolver avoids
|
||||
# this deadlock.
|
||||
|
||||
resolver = ThreadedResolver()
|
||||
IOLoop.current().run_sync(lambda: resolver.resolve(u"localhost", 80))
|
276
venv/Lib/site-packages/tornado/test/routing_test.py
Normal file
276
venv/Lib/site-packages/tornado/test/routing_test.py
Normal file
|
@ -0,0 +1,276 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tornado.httputil import (
|
||||
HTTPHeaders,
|
||||
HTTPMessageDelegate,
|
||||
HTTPServerConnectionDelegate,
|
||||
ResponseStartLine,
|
||||
)
|
||||
from tornado.routing import (
|
||||
HostMatches,
|
||||
PathMatches,
|
||||
ReversibleRouter,
|
||||
Router,
|
||||
Rule,
|
||||
RuleRouter,
|
||||
)
|
||||
from tornado.testing import AsyncHTTPTestCase
|
||||
from tornado.web import Application, HTTPError, RequestHandler
|
||||
from tornado.wsgi import WSGIContainer
|
||||
|
||||
import typing # noqa: F401
|
||||
|
||||
|
||||
class BasicRouter(Router):
|
||||
def find_handler(self, request, **kwargs):
|
||||
class MessageDelegate(HTTPMessageDelegate):
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
def finish(self):
|
||||
self.connection.write_headers(
|
||||
ResponseStartLine("HTTP/1.1", 200, "OK"),
|
||||
HTTPHeaders({"Content-Length": "2"}),
|
||||
b"OK",
|
||||
)
|
||||
self.connection.finish()
|
||||
|
||||
return MessageDelegate(request.connection)
|
||||
|
||||
|
||||
class BasicRouterTestCase(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return BasicRouter()
|
||||
|
||||
def test_basic_router(self):
|
||||
response = self.fetch("/any_request")
|
||||
self.assertEqual(response.body, b"OK")
|
||||
|
||||
|
||||
resources = {} # type: typing.Dict[str, bytes]
|
||||
|
||||
|
||||
class GetResource(RequestHandler):
|
||||
def get(self, path):
|
||||
if path not in resources:
|
||||
raise HTTPError(404)
|
||||
|
||||
self.finish(resources[path])
|
||||
|
||||
|
||||
class PostResource(RequestHandler):
|
||||
def post(self, path):
|
||||
resources[path] = self.request.body
|
||||
|
||||
|
||||
class HTTPMethodRouter(Router):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def find_handler(self, request, **kwargs):
|
||||
handler = GetResource if request.method == "GET" else PostResource
|
||||
return self.app.get_handler_delegate(request, handler, path_args=[request.path])
|
||||
|
||||
|
||||
class HTTPMethodRouterTestCase(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return HTTPMethodRouter(Application())
|
||||
|
||||
def test_http_method_router(self):
|
||||
response = self.fetch("/post_resource", method="POST", body="data")
|
||||
self.assertEqual(response.code, 200)
|
||||
|
||||
response = self.fetch("/get_resource")
|
||||
self.assertEqual(response.code, 404)
|
||||
|
||||
response = self.fetch("/post_resource")
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.body, b"data")
|
||||
|
||||
|
||||
def _get_named_handler(handler_name):
|
||||
class Handler(RequestHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
if self.application.settings.get("app_name") is not None:
|
||||
self.write(self.application.settings["app_name"] + ": ")
|
||||
|
||||
self.finish(handler_name + ": " + self.reverse_url(handler_name))
|
||||
|
||||
return Handler
|
||||
|
||||
|
||||
FirstHandler = _get_named_handler("first_handler")
|
||||
SecondHandler = _get_named_handler("second_handler")
|
||||
|
||||
|
||||
class CustomRouter(ReversibleRouter):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.routes = {} # type: typing.Dict[str, typing.Any]
|
||||
|
||||
def add_routes(self, routes):
|
||||
self.routes.update(routes)
|
||||
|
||||
def find_handler(self, request, **kwargs):
|
||||
if request.path in self.routes:
|
||||
app, handler = self.routes[request.path]
|
||||
return app.get_handler_delegate(request, handler)
|
||||
|
||||
def reverse_url(self, name, *args):
|
||||
handler_path = "/" + name
|
||||
return handler_path if handler_path in self.routes else None
|
||||
|
||||
|
||||
class CustomRouterTestCase(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
router = CustomRouter()
|
||||
|
||||
class CustomApplication(Application):
|
||||
def reverse_url(self, name, *args):
|
||||
return router.reverse_url(name, *args)
|
||||
|
||||
app1 = CustomApplication(app_name="app1")
|
||||
app2 = CustomApplication(app_name="app2")
|
||||
|
||||
router.add_routes(
|
||||
{
|
||||
"/first_handler": (app1, FirstHandler),
|
||||
"/second_handler": (app2, SecondHandler),
|
||||
"/first_handler_second_app": (app2, FirstHandler),
|
||||
}
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
def test_custom_router(self):
|
||||
response = self.fetch("/first_handler")
|
||||
self.assertEqual(response.body, b"app1: first_handler: /first_handler")
|
||||
response = self.fetch("/second_handler")
|
||||
self.assertEqual(response.body, b"app2: second_handler: /second_handler")
|
||||
response = self.fetch("/first_handler_second_app")
|
||||
self.assertEqual(response.body, b"app2: first_handler: /first_handler")
|
||||
|
||||
|
||||
class ConnectionDelegate(HTTPServerConnectionDelegate):
|
||||
def start_request(self, server_conn, request_conn):
|
||||
class MessageDelegate(HTTPMessageDelegate):
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
def finish(self):
|
||||
response_body = b"OK"
|
||||
self.connection.write_headers(
|
||||
ResponseStartLine("HTTP/1.1", 200, "OK"),
|
||||
HTTPHeaders({"Content-Length": str(len(response_body))}),
|
||||
)
|
||||
self.connection.write(response_body)
|
||||
self.connection.finish()
|
||||
|
||||
return MessageDelegate(request_conn)
|
||||
|
||||
|
||||
class RuleRouterTest(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
app = Application()
|
||||
|
||||
def request_callable(request):
|
||||
request.connection.write_headers(
|
||||
ResponseStartLine("HTTP/1.1", 200, "OK"),
|
||||
HTTPHeaders({"Content-Length": "2"}),
|
||||
)
|
||||
request.connection.write(b"OK")
|
||||
request.connection.finish()
|
||||
|
||||
router = CustomRouter()
|
||||
router.add_routes(
|
||||
{"/nested_handler": (app, _get_named_handler("nested_handler"))}
|
||||
)
|
||||
|
||||
app.add_handlers(
|
||||
".*",
|
||||
[
|
||||
(
|
||||
HostMatches("www.example.com"),
|
||||
[
|
||||
(
|
||||
PathMatches("/first_handler"),
|
||||
"tornado.test.routing_test.SecondHandler",
|
||||
{},
|
||||
"second_handler",
|
||||
)
|
||||
],
|
||||
),
|
||||
Rule(PathMatches("/.*handler"), router),
|
||||
Rule(PathMatches("/first_handler"), FirstHandler, name="first_handler"),
|
||||
Rule(PathMatches("/request_callable"), request_callable),
|
||||
("/connection_delegate", ConnectionDelegate()),
|
||||
],
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
def test_rule_based_router(self):
|
||||
response = self.fetch("/first_handler")
|
||||
self.assertEqual(response.body, b"first_handler: /first_handler")
|
||||
|
||||
response = self.fetch("/first_handler", headers={"Host": "www.example.com"})
|
||||
self.assertEqual(response.body, b"second_handler: /first_handler")
|
||||
|
||||
response = self.fetch("/nested_handler")
|
||||
self.assertEqual(response.body, b"nested_handler: /nested_handler")
|
||||
|
||||
response = self.fetch("/nested_not_found_handler")
|
||||
self.assertEqual(response.code, 404)
|
||||
|
||||
response = self.fetch("/connection_delegate")
|
||||
self.assertEqual(response.body, b"OK")
|
||||
|
||||
response = self.fetch("/request_callable")
|
||||
self.assertEqual(response.body, b"OK")
|
||||
|
||||
response = self.fetch("/404")
|
||||
self.assertEqual(response.code, 404)
|
||||
|
||||
|
||||
class WSGIContainerTestCase(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
wsgi_app = WSGIContainer(self.wsgi_app)
|
||||
|
||||
class Handler(RequestHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
self.finish(self.reverse_url("tornado"))
|
||||
|
||||
return RuleRouter(
|
||||
[
|
||||
(
|
||||
PathMatches("/tornado.*"),
|
||||
Application([(r"/tornado/test", Handler, {}, "tornado")]),
|
||||
),
|
||||
(PathMatches("/wsgi"), wsgi_app),
|
||||
]
|
||||
)
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
start_response("200 OK", [])
|
||||
return [b"WSGI"]
|
||||
|
||||
def test_wsgi_container(self):
|
||||
response = self.fetch("/tornado/test")
|
||||
self.assertEqual(response.body, b"/tornado/test")
|
||||
|
||||
response = self.fetch("/wsgi")
|
||||
self.assertEqual(response.body, b"WSGI")
|
||||
|
||||
def test_delegate_not_found(self):
|
||||
response = self.fetch("/404")
|
||||
self.assertEqual(response.code, 404)
|
241
venv/Lib/site-packages/tornado/test/runtests.py
Normal file
241
venv/Lib/site-packages/tornado/test/runtests.py
Normal file
|
@ -0,0 +1,241 @@
|
|||
from functools import reduce
|
||||
import gc
|
||||
import io
|
||||
import locale # system locale module, not tornado.locale
|
||||
import logging
|
||||
import operator
|
||||
import textwrap
|
||||
import sys
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from tornado.httpclient import AsyncHTTPClient
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.netutil import Resolver
|
||||
from tornado.options import define, add_parse_callback, options
|
||||
|
||||
|
||||
TEST_MODULES = [
|
||||
"tornado.httputil.doctests",
|
||||
"tornado.iostream.doctests",
|
||||
"tornado.util.doctests",
|
||||
"tornado.test.asyncio_test",
|
||||
"tornado.test.auth_test",
|
||||
"tornado.test.autoreload_test",
|
||||
"tornado.test.concurrent_test",
|
||||
"tornado.test.curl_httpclient_test",
|
||||
"tornado.test.escape_test",
|
||||
"tornado.test.gen_test",
|
||||
"tornado.test.http1connection_test",
|
||||
"tornado.test.httpclient_test",
|
||||
"tornado.test.httpserver_test",
|
||||
"tornado.test.httputil_test",
|
||||
"tornado.test.import_test",
|
||||
"tornado.test.ioloop_test",
|
||||
"tornado.test.iostream_test",
|
||||
"tornado.test.locale_test",
|
||||
"tornado.test.locks_test",
|
||||
"tornado.test.netutil_test",
|
||||
"tornado.test.log_test",
|
||||
"tornado.test.options_test",
|
||||
"tornado.test.process_test",
|
||||
"tornado.test.queues_test",
|
||||
"tornado.test.routing_test",
|
||||
"tornado.test.simple_httpclient_test",
|
||||
"tornado.test.tcpclient_test",
|
||||
"tornado.test.tcpserver_test",
|
||||
"tornado.test.template_test",
|
||||
"tornado.test.testing_test",
|
||||
"tornado.test.twisted_test",
|
||||
"tornado.test.util_test",
|
||||
"tornado.test.web_test",
|
||||
"tornado.test.websocket_test",
|
||||
"tornado.test.wsgi_test",
|
||||
]
|
||||
|
||||
|
||||
def all():
|
||||
return unittest.defaultTestLoader.loadTestsFromNames(TEST_MODULES)
|
||||
|
||||
|
||||
def test_runner_factory(stderr):
|
||||
class TornadoTextTestRunner(unittest.TextTestRunner):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["stream"] = stderr
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def run(self, test):
|
||||
result = super().run(test)
|
||||
if result.skipped:
|
||||
skip_reasons = set(reason for (test, reason) in result.skipped)
|
||||
self.stream.write( # type: ignore
|
||||
textwrap.fill(
|
||||
"Some tests were skipped because: %s"
|
||||
% ", ".join(sorted(skip_reasons))
|
||||
)
|
||||
)
|
||||
self.stream.write("\n") # type: ignore
|
||||
return result
|
||||
|
||||
return TornadoTextTestRunner
|
||||
|
||||
|
||||
class LogCounter(logging.Filter):
|
||||
"""Counts the number of WARNING or higher log records."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.info_count = self.warning_count = self.error_count = 0
|
||||
|
||||
def filter(self, record):
|
||||
if record.levelno >= logging.ERROR:
|
||||
self.error_count += 1
|
||||
elif record.levelno >= logging.WARNING:
|
||||
self.warning_count += 1
|
||||
elif record.levelno >= logging.INFO:
|
||||
self.info_count += 1
|
||||
return True
|
||||
|
||||
|
||||
class CountingStderr(io.IOBase):
|
||||
def __init__(self, real):
|
||||
self.real = real
|
||||
self.byte_count = 0
|
||||
|
||||
def write(self, data):
|
||||
self.byte_count += len(data)
|
||||
return self.real.write(data)
|
||||
|
||||
def flush(self):
|
||||
return self.real.flush()
|
||||
|
||||
|
||||
def main():
|
||||
# Be strict about most warnings (This is set in our test running
|
||||
# scripts to catch import-time warnings, but set it again here to
|
||||
# be sure). This also turns on warnings that are ignored by
|
||||
# default, including DeprecationWarnings and python 3.2's
|
||||
# ResourceWarnings.
|
||||
warnings.filterwarnings("error")
|
||||
# setuptools sometimes gives ImportWarnings about things that are on
|
||||
# sys.path even if they're not being used.
|
||||
warnings.filterwarnings("ignore", category=ImportWarning)
|
||||
# Tornado generally shouldn't use anything deprecated, but some of
|
||||
# our dependencies do (last match wins).
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
warnings.filterwarnings("error", category=DeprecationWarning, module=r"tornado\..*")
|
||||
warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
|
||||
warnings.filterwarnings(
|
||||
"error", category=PendingDeprecationWarning, module=r"tornado\..*"
|
||||
)
|
||||
# The unittest module is aggressive about deprecating redundant methods,
|
||||
# leaving some without non-deprecated spellings that work on both
|
||||
# 2.7 and 3.2
|
||||
warnings.filterwarnings(
|
||||
"ignore", category=DeprecationWarning, message="Please use assert.* instead"
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
category=PendingDeprecationWarning,
|
||||
message="Please use assert.* instead",
|
||||
)
|
||||
# Twisted 15.0.0 triggers some warnings on py3 with -bb.
|
||||
warnings.filterwarnings("ignore", category=BytesWarning, module=r"twisted\..*")
|
||||
if (3,) < sys.version_info < (3, 6):
|
||||
# Prior to 3.6, async ResourceWarnings were rather noisy
|
||||
# and even
|
||||
# `python3.4 -W error -c 'import asyncio; asyncio.get_event_loop()'`
|
||||
# would generate a warning.
|
||||
warnings.filterwarnings(
|
||||
"ignore", category=ResourceWarning, module=r"asyncio\..*"
|
||||
)
|
||||
# This deprecation warning is introduced in Python 3.8 and is
|
||||
# triggered by pycurl. Unforunately, because it is raised in the C
|
||||
# layer it can't be filtered by module and we must match the
|
||||
# message text instead (Tornado's C module uses PY_SSIZE_T_CLEAN
|
||||
# so it's not at risk of running into this issue).
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
category=DeprecationWarning,
|
||||
message="PY_SSIZE_T_CLEAN will be required",
|
||||
)
|
||||
|
||||
logging.getLogger("tornado.access").setLevel(logging.CRITICAL)
|
||||
|
||||
define(
|
||||
"httpclient",
|
||||
type=str,
|
||||
default=None,
|
||||
callback=lambda s: AsyncHTTPClient.configure(
|
||||
s, defaults=dict(allow_ipv6=False)
|
||||
),
|
||||
)
|
||||
define("httpserver", type=str, default=None, callback=HTTPServer.configure)
|
||||
define("resolver", type=str, default=None, callback=Resolver.configure)
|
||||
define(
|
||||
"debug_gc",
|
||||
type=str,
|
||||
multiple=True,
|
||||
help="A comma-separated list of gc module debug constants, "
|
||||
"e.g. DEBUG_STATS or DEBUG_COLLECTABLE,DEBUG_OBJECTS",
|
||||
callback=lambda values: gc.set_debug(
|
||||
reduce(operator.or_, (getattr(gc, v) for v in values))
|
||||
),
|
||||
)
|
||||
define(
|
||||
"fail-if-logs",
|
||||
default=True,
|
||||
help="If true, fail the tests if any log output is produced (unless captured by ExpectLog)",
|
||||
)
|
||||
|
||||
def set_locale(x):
|
||||
locale.setlocale(locale.LC_ALL, x)
|
||||
|
||||
define("locale", type=str, default=None, callback=set_locale)
|
||||
|
||||
log_counter = LogCounter()
|
||||
add_parse_callback(lambda: logging.getLogger().handlers[0].addFilter(log_counter))
|
||||
|
||||
# Certain errors (especially "unclosed resource" errors raised in
|
||||
# destructors) go directly to stderr instead of logging. Count
|
||||
# anything written by anything but the test runner as an error.
|
||||
orig_stderr = sys.stderr
|
||||
counting_stderr = CountingStderr(orig_stderr)
|
||||
sys.stderr = counting_stderr # type: ignore
|
||||
|
||||
import tornado.testing
|
||||
|
||||
kwargs = {}
|
||||
|
||||
# HACK: unittest.main will make its own changes to the warning
|
||||
# configuration, which may conflict with the settings above
|
||||
# or command-line flags like -bb. Passing warnings=False
|
||||
# suppresses this behavior, although this looks like an implementation
|
||||
# detail. http://bugs.python.org/issue15626
|
||||
kwargs["warnings"] = False
|
||||
|
||||
kwargs["testRunner"] = test_runner_factory(orig_stderr)
|
||||
try:
|
||||
tornado.testing.main(**kwargs)
|
||||
finally:
|
||||
# The tests should run clean; consider it a failure if they
|
||||
# logged anything at info level or above.
|
||||
if (
|
||||
log_counter.info_count > 0
|
||||
or log_counter.warning_count > 0
|
||||
or log_counter.error_count > 0
|
||||
or counting_stderr.byte_count > 0
|
||||
):
|
||||
logging.error(
|
||||
"logged %d infos, %d warnings, %d errors, and %d bytes to stderr",
|
||||
log_counter.info_count,
|
||||
log_counter.warning_count,
|
||||
log_counter.error_count,
|
||||
counting_stderr.byte_count,
|
||||
)
|
||||
if options.fail_if_logs:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
834
venv/Lib/site-packages/tornado/test/simple_httpclient_test.py
Normal file
834
venv/Lib/site-packages/tornado/test/simple_httpclient_test.py
Normal file
|
@ -0,0 +1,834 @@
|
|||
import collections
|
||||
from contextlib import closing
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
import typing # noqa: F401
|
||||
|
||||
from tornado.escape import to_unicode, utf8
|
||||
from tornado import gen, version
|
||||
from tornado.httpclient import AsyncHTTPClient
|
||||
from tornado.httputil import HTTPHeaders, ResponseStartLine
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.iostream import UnsatisfiableReadError
|
||||
from tornado.locks import Event
|
||||
from tornado.log import gen_log
|
||||
from tornado.netutil import Resolver, bind_sockets
|
||||
from tornado.simple_httpclient import (
|
||||
SimpleAsyncHTTPClient,
|
||||
HTTPStreamClosedError,
|
||||
HTTPTimeoutError,
|
||||
)
|
||||
from tornado.test.httpclient_test import (
|
||||
ChunkHandler,
|
||||
CountdownHandler,
|
||||
HelloWorldHandler,
|
||||
RedirectHandler,
|
||||
UserAgentHandler,
|
||||
)
|
||||
from tornado.test import httpclient_test
|
||||
from tornado.testing import (
|
||||
AsyncHTTPTestCase,
|
||||
AsyncHTTPSTestCase,
|
||||
AsyncTestCase,
|
||||
ExpectLog,
|
||||
gen_test,
|
||||
)
|
||||
from tornado.test.util import skipOnTravis, skipIfNoIPv6, refusing_port
|
||||
from tornado.web import RequestHandler, Application, url, stream_request_body
|
||||
|
||||
|
||||
class SimpleHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):
|
||||
def get_http_client(self):
|
||||
client = SimpleAsyncHTTPClient(force_instance=True)
|
||||
self.assertTrue(isinstance(client, SimpleAsyncHTTPClient))
|
||||
return client
|
||||
|
||||
|
||||
class TriggerHandler(RequestHandler):
|
||||
def initialize(self, queue, wake_callback):
|
||||
self.queue = queue
|
||||
self.wake_callback = wake_callback
|
||||
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
logging.debug("queuing trigger")
|
||||
event = Event()
|
||||
self.queue.append(event.set)
|
||||
if self.get_argument("wake", "true") == "true":
|
||||
self.wake_callback()
|
||||
yield event.wait()
|
||||
|
||||
|
||||
class ContentLengthHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.stream = self.detach()
|
||||
IOLoop.current().spawn_callback(self.write_response)
|
||||
|
||||
@gen.coroutine
|
||||
def write_response(self):
|
||||
yield self.stream.write(
|
||||
utf8(
|
||||
"HTTP/1.0 200 OK\r\nContent-Length: %s\r\n\r\nok"
|
||||
% self.get_argument("value")
|
||||
)
|
||||
)
|
||||
self.stream.close()
|
||||
|
||||
|
||||
class HeadHandler(RequestHandler):
|
||||
def head(self):
|
||||
self.set_header("Content-Length", "7")
|
||||
|
||||
|
||||
class OptionsHandler(RequestHandler):
|
||||
def options(self):
|
||||
self.set_header("Access-Control-Allow-Origin", "*")
|
||||
self.write("ok")
|
||||
|
||||
|
||||
class NoContentHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.set_status(204)
|
||||
self.finish()
|
||||
|
||||
|
||||
class SeeOtherPostHandler(RequestHandler):
|
||||
def post(self):
|
||||
redirect_code = int(self.request.body)
|
||||
assert redirect_code in (302, 303), "unexpected body %r" % self.request.body
|
||||
self.set_header("Location", "/see_other_get")
|
||||
self.set_status(redirect_code)
|
||||
|
||||
|
||||
class SeeOtherGetHandler(RequestHandler):
|
||||
def get(self):
|
||||
if self.request.body:
|
||||
raise Exception("unexpected body %r" % self.request.body)
|
||||
self.write("ok")
|
||||
|
||||
|
||||
class HostEchoHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write(self.request.headers["Host"])
|
||||
|
||||
|
||||
class NoContentLengthHandler(RequestHandler):
|
||||
def get(self):
|
||||
if self.request.version.startswith("HTTP/1"):
|
||||
# Emulate the old HTTP/1.0 behavior of returning a body with no
|
||||
# content-length. Tornado handles content-length at the framework
|
||||
# level so we have to go around it.
|
||||
stream = self.detach()
|
||||
stream.write(b"HTTP/1.0 200 OK\r\n\r\n" b"hello")
|
||||
stream.close()
|
||||
else:
|
||||
self.finish("HTTP/1 required")
|
||||
|
||||
|
||||
class EchoPostHandler(RequestHandler):
|
||||
def post(self):
|
||||
self.write(self.request.body)
|
||||
|
||||
|
||||
@stream_request_body
|
||||
class RespondInPrepareHandler(RequestHandler):
|
||||
def prepare(self):
|
||||
self.set_status(403)
|
||||
self.finish("forbidden")
|
||||
|
||||
|
||||
class SimpleHTTPClientTestMixin(object):
|
||||
def create_client(self, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_app(self: typing.Any):
|
||||
# callable objects to finish pending /trigger requests
|
||||
self.triggers = (
|
||||
collections.deque()
|
||||
) # type: typing.Deque[typing.Callable[[], None]]
|
||||
return Application(
|
||||
[
|
||||
url(
|
||||
"/trigger",
|
||||
TriggerHandler,
|
||||
dict(queue=self.triggers, wake_callback=self.stop),
|
||||
),
|
||||
url("/chunk", ChunkHandler),
|
||||
url("/countdown/([0-9]+)", CountdownHandler, name="countdown"),
|
||||
url("/hello", HelloWorldHandler),
|
||||
url("/content_length", ContentLengthHandler),
|
||||
url("/head", HeadHandler),
|
||||
url("/options", OptionsHandler),
|
||||
url("/no_content", NoContentHandler),
|
||||
url("/see_other_post", SeeOtherPostHandler),
|
||||
url("/see_other_get", SeeOtherGetHandler),
|
||||
url("/host_echo", HostEchoHandler),
|
||||
url("/no_content_length", NoContentLengthHandler),
|
||||
url("/echo_post", EchoPostHandler),
|
||||
url("/respond_in_prepare", RespondInPrepareHandler),
|
||||
url("/redirect", RedirectHandler),
|
||||
url("/user_agent", UserAgentHandler),
|
||||
],
|
||||
gzip=True,
|
||||
)
|
||||
|
||||
def test_singleton(self: typing.Any):
|
||||
# Class "constructor" reuses objects on the same IOLoop
|
||||
self.assertTrue(SimpleAsyncHTTPClient() is SimpleAsyncHTTPClient())
|
||||
# unless force_instance is used
|
||||
self.assertTrue(
|
||||
SimpleAsyncHTTPClient() is not SimpleAsyncHTTPClient(force_instance=True)
|
||||
)
|
||||
# different IOLoops use different objects
|
||||
with closing(IOLoop()) as io_loop2:
|
||||
|
||||
async def make_client():
|
||||
await gen.sleep(0)
|
||||
return SimpleAsyncHTTPClient()
|
||||
|
||||
client1 = self.io_loop.run_sync(make_client)
|
||||
client2 = io_loop2.run_sync(make_client)
|
||||
self.assertTrue(client1 is not client2)
|
||||
|
||||
def test_connection_limit(self: typing.Any):
|
||||
with closing(self.create_client(max_clients=2)) as client:
|
||||
self.assertEqual(client.max_clients, 2)
|
||||
seen = []
|
||||
# Send 4 requests. Two can be sent immediately, while the others
|
||||
# will be queued
|
||||
for i in range(4):
|
||||
|
||||
def cb(fut, i=i):
|
||||
seen.append(i)
|
||||
self.stop()
|
||||
|
||||
client.fetch(self.get_url("/trigger")).add_done_callback(cb)
|
||||
self.wait(condition=lambda: len(self.triggers) == 2)
|
||||
self.assertEqual(len(client.queue), 2)
|
||||
|
||||
# Finish the first two requests and let the next two through
|
||||
self.triggers.popleft()()
|
||||
self.triggers.popleft()()
|
||||
self.wait(condition=lambda: (len(self.triggers) == 2 and len(seen) == 2))
|
||||
self.assertEqual(set(seen), set([0, 1]))
|
||||
self.assertEqual(len(client.queue), 0)
|
||||
|
||||
# Finish all the pending requests
|
||||
self.triggers.popleft()()
|
||||
self.triggers.popleft()()
|
||||
self.wait(condition=lambda: len(seen) == 4)
|
||||
self.assertEqual(set(seen), set([0, 1, 2, 3]))
|
||||
self.assertEqual(len(self.triggers), 0)
|
||||
|
||||
@gen_test
|
||||
def test_redirect_connection_limit(self: typing.Any):
|
||||
# following redirects should not consume additional connections
|
||||
with closing(self.create_client(max_clients=1)) as client:
|
||||
response = yield client.fetch(self.get_url("/countdown/3"), max_redirects=3)
|
||||
response.rethrow()
|
||||
|
||||
def test_max_redirects(self: typing.Any):
|
||||
response = self.fetch("/countdown/5", max_redirects=3)
|
||||
self.assertEqual(302, response.code)
|
||||
# We requested 5, followed three redirects for 4, 3, 2, then the last
|
||||
# unfollowed redirect is to 1.
|
||||
self.assertTrue(response.request.url.endswith("/countdown/5"))
|
||||
self.assertTrue(response.effective_url.endswith("/countdown/2"))
|
||||
self.assertTrue(response.headers["Location"].endswith("/countdown/1"))
|
||||
|
||||
def test_header_reuse(self: typing.Any):
|
||||
# Apps may reuse a headers object if they are only passing in constant
|
||||
# headers like user-agent. The header object should not be modified.
|
||||
headers = HTTPHeaders({"User-Agent": "Foo"})
|
||||
self.fetch("/hello", headers=headers)
|
||||
self.assertEqual(list(headers.get_all()), [("User-Agent", "Foo")])
|
||||
|
||||
def test_default_user_agent(self: typing.Any):
|
||||
response = self.fetch("/user_agent", method="GET")
|
||||
self.assertEqual(200, response.code)
|
||||
self.assertEqual(response.body.decode(), "Tornado/{}".format(version))
|
||||
|
||||
def test_see_other_redirect(self: typing.Any):
|
||||
for code in (302, 303):
|
||||
response = self.fetch("/see_other_post", method="POST", body="%d" % code)
|
||||
self.assertEqual(200, response.code)
|
||||
self.assertTrue(response.request.url.endswith("/see_other_post"))
|
||||
self.assertTrue(response.effective_url.endswith("/see_other_get"))
|
||||
# request is the original request, is a POST still
|
||||
self.assertEqual("POST", response.request.method)
|
||||
|
||||
@skipOnTravis
|
||||
@gen_test
|
||||
def test_connect_timeout(self: typing.Any):
|
||||
timeout = 0.1
|
||||
|
||||
cleanup_event = Event()
|
||||
test = self
|
||||
|
||||
class TimeoutResolver(Resolver):
|
||||
async def resolve(self, *args, **kwargs):
|
||||
await cleanup_event.wait()
|
||||
# Return something valid so the test doesn't raise during shutdown.
|
||||
return [(socket.AF_INET, ("127.0.0.1", test.get_http_port()))]
|
||||
|
||||
with closing(self.create_client(resolver=TimeoutResolver())) as client:
|
||||
with self.assertRaises(HTTPTimeoutError):
|
||||
yield client.fetch(
|
||||
self.get_url("/hello"),
|
||||
connect_timeout=timeout,
|
||||
request_timeout=3600,
|
||||
raise_error=True,
|
||||
)
|
||||
|
||||
# Let the hanging coroutine clean up after itself. We need to
|
||||
# wait more than a single IOLoop iteration for the SSL case,
|
||||
# which logs errors on unexpected EOF.
|
||||
cleanup_event.set()
|
||||
yield gen.sleep(0.2)
|
||||
|
||||
@skipOnTravis
|
||||
def test_request_timeout(self: typing.Any):
|
||||
timeout = 0.1
|
||||
if os.name == "nt":
|
||||
timeout = 0.5
|
||||
|
||||
with self.assertRaises(HTTPTimeoutError):
|
||||
self.fetch("/trigger?wake=false", request_timeout=timeout, raise_error=True)
|
||||
# trigger the hanging request to let it clean up after itself
|
||||
self.triggers.popleft()()
|
||||
self.io_loop.run_sync(lambda: gen.sleep(0))
|
||||
|
||||
@skipIfNoIPv6
|
||||
def test_ipv6(self: typing.Any):
|
||||
[sock] = bind_sockets(0, "::1", family=socket.AF_INET6)
|
||||
port = sock.getsockname()[1]
|
||||
self.http_server.add_socket(sock)
|
||||
url = "%s://[::1]:%d/hello" % (self.get_protocol(), port)
|
||||
|
||||
# ipv6 is currently enabled by default but can be disabled
|
||||
with self.assertRaises(Exception):
|
||||
self.fetch(url, allow_ipv6=False, raise_error=True)
|
||||
|
||||
response = self.fetch(url)
|
||||
self.assertEqual(response.body, b"Hello world!")
|
||||
|
||||
def test_multiple_content_length_accepted(self: typing.Any):
|
||||
response = self.fetch("/content_length?value=2,2")
|
||||
self.assertEqual(response.body, b"ok")
|
||||
response = self.fetch("/content_length?value=2,%202,2")
|
||||
self.assertEqual(response.body, b"ok")
|
||||
|
||||
with ExpectLog(
|
||||
gen_log, ".*Multiple unequal Content-Lengths", level=logging.INFO
|
||||
):
|
||||
with self.assertRaises(HTTPStreamClosedError):
|
||||
self.fetch("/content_length?value=2,4", raise_error=True)
|
||||
with self.assertRaises(HTTPStreamClosedError):
|
||||
self.fetch("/content_length?value=2,%202,3", raise_error=True)
|
||||
|
||||
def test_head_request(self: typing.Any):
|
||||
response = self.fetch("/head", method="HEAD")
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.headers["content-length"], "7")
|
||||
self.assertFalse(response.body)
|
||||
|
||||
def test_options_request(self: typing.Any):
|
||||
response = self.fetch("/options", method="OPTIONS")
|
||||
self.assertEqual(response.code, 200)
|
||||
self.assertEqual(response.headers["content-length"], "2")
|
||||
self.assertEqual(response.headers["access-control-allow-origin"], "*")
|
||||
self.assertEqual(response.body, b"ok")
|
||||
|
||||
def test_no_content(self: typing.Any):
|
||||
response = self.fetch("/no_content")
|
||||
self.assertEqual(response.code, 204)
|
||||
# 204 status shouldn't have a content-length
|
||||
#
|
||||
# Tests with a content-length header are included below
|
||||
# in HTTP204NoContentTestCase.
|
||||
self.assertNotIn("Content-Length", response.headers)
|
||||
|
||||
def test_host_header(self: typing.Any):
|
||||
host_re = re.compile(b"^127.0.0.1:[0-9]+$")
|
||||
response = self.fetch("/host_echo")
|
||||
self.assertTrue(host_re.match(response.body))
|
||||
|
||||
url = self.get_url("/host_echo").replace("http://", "http://me:secret@")
|
||||
response = self.fetch(url)
|
||||
self.assertTrue(host_re.match(response.body), response.body)
|
||||
|
||||
def test_connection_refused(self: typing.Any):
|
||||
cleanup_func, port = refusing_port()
|
||||
self.addCleanup(cleanup_func)
|
||||
with ExpectLog(gen_log, ".*", required=False):
|
||||
with self.assertRaises(socket.error) as cm:
|
||||
self.fetch("http://127.0.0.1:%d/" % port, raise_error=True)
|
||||
|
||||
if sys.platform != "cygwin":
|
||||
# cygwin returns EPERM instead of ECONNREFUSED here
|
||||
contains_errno = str(errno.ECONNREFUSED) in str(cm.exception)
|
||||
if not contains_errno and hasattr(errno, "WSAECONNREFUSED"):
|
||||
contains_errno = str(errno.WSAECONNREFUSED) in str( # type: ignore
|
||||
cm.exception
|
||||
)
|
||||
self.assertTrue(contains_errno, cm.exception)
|
||||
# This is usually "Connection refused".
|
||||
# On windows, strerror is broken and returns "Unknown error".
|
||||
expected_message = os.strerror(errno.ECONNREFUSED)
|
||||
self.assertTrue(expected_message in str(cm.exception), cm.exception)
|
||||
|
||||
def test_queue_timeout(self: typing.Any):
|
||||
with closing(self.create_client(max_clients=1)) as client:
|
||||
# Wait for the trigger request to block, not complete.
|
||||
fut1 = client.fetch(self.get_url("/trigger"), request_timeout=10)
|
||||
self.wait()
|
||||
with self.assertRaises(HTTPTimeoutError) as cm:
|
||||
self.io_loop.run_sync(
|
||||
lambda: client.fetch(
|
||||
self.get_url("/hello"), connect_timeout=0.1, raise_error=True
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(str(cm.exception), "Timeout in request queue")
|
||||
self.triggers.popleft()()
|
||||
self.io_loop.run_sync(lambda: fut1)
|
||||
|
||||
def test_no_content_length(self: typing.Any):
|
||||
response = self.fetch("/no_content_length")
|
||||
if response.body == b"HTTP/1 required":
|
||||
self.skipTest("requires HTTP/1.x")
|
||||
else:
|
||||
self.assertEqual(b"hello", response.body)
|
||||
|
||||
def sync_body_producer(self, write):
|
||||
write(b"1234")
|
||||
write(b"5678")
|
||||
|
||||
@gen.coroutine
|
||||
def async_body_producer(self, write):
|
||||
yield write(b"1234")
|
||||
yield gen.moment
|
||||
yield write(b"5678")
|
||||
|
||||
def test_sync_body_producer_chunked(self: typing.Any):
|
||||
response = self.fetch(
|
||||
"/echo_post", method="POST", body_producer=self.sync_body_producer
|
||||
)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"12345678")
|
||||
|
||||
def test_sync_body_producer_content_length(self: typing.Any):
|
||||
response = self.fetch(
|
||||
"/echo_post",
|
||||
method="POST",
|
||||
body_producer=self.sync_body_producer,
|
||||
headers={"Content-Length": "8"},
|
||||
)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"12345678")
|
||||
|
||||
def test_async_body_producer_chunked(self: typing.Any):
|
||||
response = self.fetch(
|
||||
"/echo_post", method="POST", body_producer=self.async_body_producer
|
||||
)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"12345678")
|
||||
|
||||
def test_async_body_producer_content_length(self: typing.Any):
|
||||
response = self.fetch(
|
||||
"/echo_post",
|
||||
method="POST",
|
||||
body_producer=self.async_body_producer,
|
||||
headers={"Content-Length": "8"},
|
||||
)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"12345678")
|
||||
|
||||
def test_native_body_producer_chunked(self: typing.Any):
|
||||
async def body_producer(write):
|
||||
await write(b"1234")
|
||||
import asyncio
|
||||
|
||||
await asyncio.sleep(0)
|
||||
await write(b"5678")
|
||||
|
||||
response = self.fetch("/echo_post", method="POST", body_producer=body_producer)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"12345678")
|
||||
|
||||
def test_native_body_producer_content_length(self: typing.Any):
|
||||
async def body_producer(write):
|
||||
await write(b"1234")
|
||||
import asyncio
|
||||
|
||||
await asyncio.sleep(0)
|
||||
await write(b"5678")
|
||||
|
||||
response = self.fetch(
|
||||
"/echo_post",
|
||||
method="POST",
|
||||
body_producer=body_producer,
|
||||
headers={"Content-Length": "8"},
|
||||
)
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"12345678")
|
||||
|
||||
def test_100_continue(self: typing.Any):
|
||||
response = self.fetch(
|
||||
"/echo_post", method="POST", body=b"1234", expect_100_continue=True
|
||||
)
|
||||
self.assertEqual(response.body, b"1234")
|
||||
|
||||
def test_100_continue_early_response(self: typing.Any):
|
||||
def body_producer(write):
|
||||
raise Exception("should not be called")
|
||||
|
||||
response = self.fetch(
|
||||
"/respond_in_prepare",
|
||||
method="POST",
|
||||
body_producer=body_producer,
|
||||
expect_100_continue=True,
|
||||
)
|
||||
self.assertEqual(response.code, 403)
|
||||
|
||||
def test_streaming_follow_redirects(self: typing.Any):
|
||||
# When following redirects, header and streaming callbacks
|
||||
# should only be called for the final result.
|
||||
# TODO(bdarnell): this test belongs in httpclient_test instead of
|
||||
# simple_httpclient_test, but it fails with the version of libcurl
|
||||
# available on travis-ci. Move it when that has been upgraded
|
||||
# or we have a better framework to skip tests based on curl version.
|
||||
headers = [] # type: typing.List[str]
|
||||
chunk_bytes = [] # type: typing.List[bytes]
|
||||
self.fetch(
|
||||
"/redirect?url=/hello",
|
||||
header_callback=headers.append,
|
||||
streaming_callback=chunk_bytes.append,
|
||||
)
|
||||
chunks = list(map(to_unicode, chunk_bytes))
|
||||
self.assertEqual(chunks, ["Hello world!"])
|
||||
# Make sure we only got one set of headers.
|
||||
num_start_lines = len([h for h in headers if h.startswith("HTTP/")])
|
||||
self.assertEqual(num_start_lines, 1)
|
||||
|
||||
|
||||
class SimpleHTTPClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.http_client = self.create_client()
|
||||
|
||||
def create_client(self, **kwargs):
|
||||
return SimpleAsyncHTTPClient(force_instance=True, **kwargs)
|
||||
|
||||
|
||||
class SimpleHTTPSClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPSTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.http_client = self.create_client()
|
||||
|
||||
def create_client(self, **kwargs):
|
||||
return SimpleAsyncHTTPClient(
|
||||
force_instance=True, defaults=dict(validate_cert=False), **kwargs
|
||||
)
|
||||
|
||||
def test_ssl_options(self):
|
||||
resp = self.fetch("/hello", ssl_options={})
|
||||
self.assertEqual(resp.body, b"Hello world!")
|
||||
|
||||
def test_ssl_context(self):
|
||||
resp = self.fetch("/hello", ssl_options=ssl.SSLContext(ssl.PROTOCOL_SSLv23))
|
||||
self.assertEqual(resp.body, b"Hello world!")
|
||||
|
||||
def test_ssl_options_handshake_fail(self):
|
||||
with ExpectLog(gen_log, "SSL Error|Uncaught exception", required=False):
|
||||
with self.assertRaises(ssl.SSLError):
|
||||
self.fetch(
|
||||
"/hello",
|
||||
ssl_options=dict(cert_reqs=ssl.CERT_REQUIRED),
|
||||
raise_error=True,
|
||||
)
|
||||
|
||||
def test_ssl_context_handshake_fail(self):
|
||||
with ExpectLog(gen_log, "SSL Error|Uncaught exception"):
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||
with self.assertRaises(ssl.SSLError):
|
||||
self.fetch("/hello", ssl_options=ctx, raise_error=True)
|
||||
|
||||
def test_error_logging(self):
|
||||
# No stack traces are logged for SSL errors (in this case,
|
||||
# failure to validate the testing self-signed cert).
|
||||
# The SSLError is exposed through ssl.SSLError.
|
||||
with ExpectLog(gen_log, ".*") as expect_log:
|
||||
with self.assertRaises(ssl.SSLError):
|
||||
self.fetch("/", validate_cert=True, raise_error=True)
|
||||
self.assertFalse(expect_log.logged_stack)
|
||||
|
||||
|
||||
class CreateAsyncHTTPClientTestCase(AsyncTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.saved = AsyncHTTPClient._save_configuration()
|
||||
|
||||
def tearDown(self):
|
||||
AsyncHTTPClient._restore_configuration(self.saved)
|
||||
super().tearDown()
|
||||
|
||||
def test_max_clients(self):
|
||||
AsyncHTTPClient.configure(SimpleAsyncHTTPClient)
|
||||
with closing(AsyncHTTPClient(force_instance=True)) as client:
|
||||
self.assertEqual(client.max_clients, 10) # type: ignore
|
||||
with closing(AsyncHTTPClient(max_clients=11, force_instance=True)) as client:
|
||||
self.assertEqual(client.max_clients, 11) # type: ignore
|
||||
|
||||
# Now configure max_clients statically and try overriding it
|
||||
# with each way max_clients can be passed
|
||||
AsyncHTTPClient.configure(SimpleAsyncHTTPClient, max_clients=12)
|
||||
with closing(AsyncHTTPClient(force_instance=True)) as client:
|
||||
self.assertEqual(client.max_clients, 12) # type: ignore
|
||||
with closing(AsyncHTTPClient(max_clients=13, force_instance=True)) as client:
|
||||
self.assertEqual(client.max_clients, 13) # type: ignore
|
||||
with closing(AsyncHTTPClient(max_clients=14, force_instance=True)) as client:
|
||||
self.assertEqual(client.max_clients, 14) # type: ignore
|
||||
|
||||
|
||||
class HTTP100ContinueTestCase(AsyncHTTPTestCase):
|
||||
def respond_100(self, request):
|
||||
self.http1 = request.version.startswith("HTTP/1.")
|
||||
if not self.http1:
|
||||
request.connection.write_headers(
|
||||
ResponseStartLine("", 200, "OK"), HTTPHeaders()
|
||||
)
|
||||
request.connection.finish()
|
||||
return
|
||||
self.request = request
|
||||
fut = self.request.connection.stream.write(b"HTTP/1.1 100 CONTINUE\r\n\r\n")
|
||||
fut.add_done_callback(self.respond_200)
|
||||
|
||||
def respond_200(self, fut):
|
||||
fut.result()
|
||||
fut = self.request.connection.stream.write(
|
||||
b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nA"
|
||||
)
|
||||
fut.add_done_callback(lambda f: self.request.connection.stream.close())
|
||||
|
||||
def get_app(self):
|
||||
# Not a full Application, but works as an HTTPServer callback
|
||||
return self.respond_100
|
||||
|
||||
def test_100_continue(self):
|
||||
res = self.fetch("/")
|
||||
if not self.http1:
|
||||
self.skipTest("requires HTTP/1.x")
|
||||
self.assertEqual(res.body, b"A")
|
||||
|
||||
|
||||
class HTTP204NoContentTestCase(AsyncHTTPTestCase):
|
||||
def respond_204(self, request):
|
||||
self.http1 = request.version.startswith("HTTP/1.")
|
||||
if not self.http1:
|
||||
# Close the request cleanly in HTTP/2; it will be skipped anyway.
|
||||
request.connection.write_headers(
|
||||
ResponseStartLine("", 200, "OK"), HTTPHeaders()
|
||||
)
|
||||
request.connection.finish()
|
||||
return
|
||||
|
||||
# A 204 response never has a body, even if doesn't have a content-length
|
||||
# (which would otherwise mean read-until-close). We simulate here a
|
||||
# server that sends no content length and does not close the connection.
|
||||
#
|
||||
# Tests of a 204 response with no Content-Length header are included
|
||||
# in SimpleHTTPClientTestMixin.
|
||||
stream = request.connection.detach()
|
||||
stream.write(b"HTTP/1.1 204 No content\r\n")
|
||||
if request.arguments.get("error", [False])[-1]:
|
||||
stream.write(b"Content-Length: 5\r\n")
|
||||
else:
|
||||
stream.write(b"Content-Length: 0\r\n")
|
||||
stream.write(b"\r\n")
|
||||
stream.close()
|
||||
|
||||
def get_app(self):
|
||||
return self.respond_204
|
||||
|
||||
def test_204_no_content(self):
|
||||
resp = self.fetch("/")
|
||||
if not self.http1:
|
||||
self.skipTest("requires HTTP/1.x")
|
||||
self.assertEqual(resp.code, 204)
|
||||
self.assertEqual(resp.body, b"")
|
||||
|
||||
def test_204_invalid_content_length(self):
|
||||
# 204 status with non-zero content length is malformed
|
||||
with ExpectLog(
|
||||
gen_log, ".*Response with code 204 should not have body", level=logging.INFO
|
||||
):
|
||||
with self.assertRaises(HTTPStreamClosedError):
|
||||
self.fetch("/?error=1", raise_error=True)
|
||||
if not self.http1:
|
||||
self.skipTest("requires HTTP/1.x")
|
||||
if self.http_client.configured_class != SimpleAsyncHTTPClient:
|
||||
self.skipTest("curl client accepts invalid headers")
|
||||
|
||||
|
||||
class HostnameMappingTestCase(AsyncHTTPTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.http_client = SimpleAsyncHTTPClient(
|
||||
hostname_mapping={
|
||||
"www.example.com": "127.0.0.1",
|
||||
("foo.example.com", 8000): ("127.0.0.1", self.get_http_port()),
|
||||
}
|
||||
)
|
||||
|
||||
def get_app(self):
|
||||
return Application([url("/hello", HelloWorldHandler)])
|
||||
|
||||
def test_hostname_mapping(self):
|
||||
response = self.fetch("http://www.example.com:%d/hello" % self.get_http_port())
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"Hello world!")
|
||||
|
||||
def test_port_mapping(self):
|
||||
response = self.fetch("http://foo.example.com:8000/hello")
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"Hello world!")
|
||||
|
||||
|
||||
class ResolveTimeoutTestCase(AsyncHTTPTestCase):
|
||||
def setUp(self):
|
||||
self.cleanup_event = Event()
|
||||
test = self
|
||||
|
||||
# Dummy Resolver subclass that never finishes.
|
||||
class BadResolver(Resolver):
|
||||
@gen.coroutine
|
||||
def resolve(self, *args, **kwargs):
|
||||
yield test.cleanup_event.wait()
|
||||
# Return something valid so the test doesn't raise during cleanup.
|
||||
return [(socket.AF_INET, ("127.0.0.1", test.get_http_port()))]
|
||||
|
||||
super().setUp()
|
||||
self.http_client = SimpleAsyncHTTPClient(resolver=BadResolver())
|
||||
|
||||
def get_app(self):
|
||||
return Application([url("/hello", HelloWorldHandler)])
|
||||
|
||||
def test_resolve_timeout(self):
|
||||
with self.assertRaises(HTTPTimeoutError):
|
||||
self.fetch("/hello", connect_timeout=0.1, raise_error=True)
|
||||
|
||||
# Let the hanging coroutine clean up after itself
|
||||
self.cleanup_event.set()
|
||||
self.io_loop.run_sync(lambda: gen.sleep(0))
|
||||
|
||||
|
||||
class MaxHeaderSizeTest(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
class SmallHeaders(RequestHandler):
|
||||
def get(self):
|
||||
self.set_header("X-Filler", "a" * 100)
|
||||
self.write("ok")
|
||||
|
||||
class LargeHeaders(RequestHandler):
|
||||
def get(self):
|
||||
self.set_header("X-Filler", "a" * 1000)
|
||||
self.write("ok")
|
||||
|
||||
return Application([("/small", SmallHeaders), ("/large", LargeHeaders)])
|
||||
|
||||
def get_http_client(self):
|
||||
return SimpleAsyncHTTPClient(max_header_size=1024)
|
||||
|
||||
def test_small_headers(self):
|
||||
response = self.fetch("/small")
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"ok")
|
||||
|
||||
def test_large_headers(self):
|
||||
with ExpectLog(gen_log, "Unsatisfiable read", level=logging.INFO):
|
||||
with self.assertRaises(UnsatisfiableReadError):
|
||||
self.fetch("/large", raise_error=True)
|
||||
|
||||
|
||||
class MaxBodySizeTest(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
class SmallBody(RequestHandler):
|
||||
def get(self):
|
||||
self.write("a" * 1024 * 64)
|
||||
|
||||
class LargeBody(RequestHandler):
|
||||
def get(self):
|
||||
self.write("a" * 1024 * 100)
|
||||
|
||||
return Application([("/small", SmallBody), ("/large", LargeBody)])
|
||||
|
||||
def get_http_client(self):
|
||||
return SimpleAsyncHTTPClient(max_body_size=1024 * 64)
|
||||
|
||||
def test_small_body(self):
|
||||
response = self.fetch("/small")
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"a" * 1024 * 64)
|
||||
|
||||
def test_large_body(self):
|
||||
with ExpectLog(
|
||||
gen_log,
|
||||
"Malformed HTTP message from None: Content-Length too long",
|
||||
level=logging.INFO,
|
||||
):
|
||||
with self.assertRaises(HTTPStreamClosedError):
|
||||
self.fetch("/large", raise_error=True)
|
||||
|
||||
|
||||
class MaxBufferSizeTest(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
class LargeBody(RequestHandler):
|
||||
def get(self):
|
||||
self.write("a" * 1024 * 100)
|
||||
|
||||
return Application([("/large", LargeBody)])
|
||||
|
||||
def get_http_client(self):
|
||||
# 100KB body with 64KB buffer
|
||||
return SimpleAsyncHTTPClient(
|
||||
max_body_size=1024 * 100, max_buffer_size=1024 * 64
|
||||
)
|
||||
|
||||
def test_large_body(self):
|
||||
response = self.fetch("/large")
|
||||
response.rethrow()
|
||||
self.assertEqual(response.body, b"a" * 1024 * 100)
|
||||
|
||||
|
||||
class ChunkedWithContentLengthTest(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
class ChunkedWithContentLength(RequestHandler):
|
||||
def get(self):
|
||||
# Add an invalid Transfer-Encoding to the response
|
||||
self.set_header("Transfer-Encoding", "chunked")
|
||||
self.write("Hello world")
|
||||
|
||||
return Application([("/chunkwithcl", ChunkedWithContentLength)])
|
||||
|
||||
def get_http_client(self):
|
||||
return SimpleAsyncHTTPClient()
|
||||
|
||||
def test_chunked_with_content_length(self):
|
||||
# Make sure the invalid headers are detected
|
||||
with ExpectLog(
|
||||
gen_log,
|
||||
(
|
||||
"Malformed HTTP message from None: Response "
|
||||
"with both Transfer-Encoding and Content-Length"
|
||||
),
|
||||
level=logging.INFO,
|
||||
):
|
||||
with self.assertRaises(HTTPStreamClosedError):
|
||||
self.fetch("/chunkwithcl", raise_error=True)
|
|
@ -0,0 +1 @@
|
|||
this is the index
|
2
venv/Lib/site-packages/tornado/test/static/robots.txt
Normal file
2
venv/Lib/site-packages/tornado/test/static/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow: /
|
23
venv/Lib/site-packages/tornado/test/static/sample.xml
Normal file
23
venv/Lib/site-packages/tornado/test/static/sample.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<country name="Liechtenstein">
|
||||
<rank>1</rank>
|
||||
<year>2008</year>
|
||||
<gdppc>141100</gdppc>
|
||||
<neighbor name="Austria" direction="E"/>
|
||||
<neighbor name="Switzerland" direction="W"/>
|
||||
</country>
|
||||
<country name="Singapore">
|
||||
<rank>4</rank>
|
||||
<year>2011</year>
|
||||
<gdppc>59900</gdppc>
|
||||
<neighbor name="Malaysia" direction="N"/>
|
||||
</country>
|
||||
<country name="Panama">
|
||||
<rank>68</rank>
|
||||
<year>2011</year>
|
||||
<gdppc>13600</gdppc>
|
||||
<neighbor name="Costa Rica" direction="W"/>
|
||||
<neighbor name="Colombia" direction="E"/>
|
||||
</country>
|
||||
</data>
|
BIN
venv/Lib/site-packages/tornado/test/static/sample.xml.bz2
Normal file
BIN
venv/Lib/site-packages/tornado/test/static/sample.xml.bz2
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/tornado/test/static/sample.xml.gz
Normal file
BIN
venv/Lib/site-packages/tornado/test/static/sample.xml.gz
Normal file
Binary file not shown.
2
venv/Lib/site-packages/tornado/test/static_foo.txt
Normal file
2
venv/Lib/site-packages/tornado/test/static_foo.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
This file should not be served by StaticFileHandler even though
|
||||
its name starts with "static".
|
438
venv/Lib/site-packages/tornado/test/tcpclient_test.py
Normal file
438
venv/Lib/site-packages/tornado/test/tcpclient_test.py
Normal file
|
@ -0,0 +1,438 @@
|
|||
#
|
||||
# Copyright 2014 Facebook
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from contextlib import closing
|
||||
import getpass
|
||||
import os
|
||||
import socket
|
||||
import unittest
|
||||
|
||||
from tornado.concurrent import Future
|
||||
from tornado.netutil import bind_sockets, Resolver
|
||||
from tornado.queues import Queue
|
||||
from tornado.tcpclient import TCPClient, _Connector
|
||||
from tornado.tcpserver import TCPServer
|
||||
from tornado.testing import AsyncTestCase, gen_test
|
||||
from tornado.test.util import skipIfNoIPv6, refusing_port, skipIfNonUnix
|
||||
from tornado.gen import TimeoutError
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from tornado.iostream import IOStream # noqa: F401
|
||||
from typing import List, Dict, Tuple # noqa: F401
|
||||
|
||||
# Fake address families for testing. Used in place of AF_INET
|
||||
# and AF_INET6 because some installations do not have AF_INET6.
|
||||
AF1, AF2 = 1, 2
|
||||
|
||||
|
||||
class TestTCPServer(TCPServer):
|
||||
def __init__(self, family):
|
||||
super().__init__()
|
||||
self.streams = [] # type: List[IOStream]
|
||||
self.queue = Queue() # type: Queue[IOStream]
|
||||
sockets = bind_sockets(0, "localhost", family)
|
||||
self.add_sockets(sockets)
|
||||
self.port = sockets[0].getsockname()[1]
|
||||
|
||||
def handle_stream(self, stream, address):
|
||||
self.streams.append(stream)
|
||||
self.queue.put(stream)
|
||||
|
||||
def stop(self):
|
||||
super().stop()
|
||||
for stream in self.streams:
|
||||
stream.close()
|
||||
|
||||
|
||||
class TCPClientTest(AsyncTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.server = None
|
||||
self.client = TCPClient()
|
||||
|
||||
def start_server(self, family):
|
||||
if family == socket.AF_UNSPEC and "TRAVIS" in os.environ:
|
||||
self.skipTest("dual-stack servers often have port conflicts on travis")
|
||||
self.server = TestTCPServer(family)
|
||||
return self.server.port
|
||||
|
||||
def stop_server(self):
|
||||
if self.server is not None:
|
||||
self.server.stop()
|
||||
self.server = None
|
||||
|
||||
def tearDown(self):
|
||||
self.client.close()
|
||||
self.stop_server()
|
||||
super().tearDown()
|
||||
|
||||
def skipIfLocalhostV4(self):
|
||||
# The port used here doesn't matter, but some systems require it
|
||||
# to be non-zero if we do not also pass AI_PASSIVE.
|
||||
addrinfo = self.io_loop.run_sync(lambda: Resolver().resolve("localhost", 80))
|
||||
families = set(addr[0] for addr in addrinfo)
|
||||
if socket.AF_INET6 not in families:
|
||||
self.skipTest("localhost does not resolve to ipv6")
|
||||
|
||||
@gen_test
|
||||
def do_test_connect(self, family, host, source_ip=None, source_port=None):
|
||||
port = self.start_server(family)
|
||||
stream = yield self.client.connect(
|
||||
host, port, source_ip=source_ip, source_port=source_port
|
||||
)
|
||||
assert self.server is not None
|
||||
server_stream = yield self.server.queue.get()
|
||||
with closing(stream):
|
||||
stream.write(b"hello")
|
||||
data = yield server_stream.read_bytes(5)
|
||||
self.assertEqual(data, b"hello")
|
||||
|
||||
def test_connect_ipv4_ipv4(self):
|
||||
self.do_test_connect(socket.AF_INET, "127.0.0.1")
|
||||
|
||||
def test_connect_ipv4_dual(self):
|
||||
self.do_test_connect(socket.AF_INET, "localhost")
|
||||
|
||||
@skipIfNoIPv6
|
||||
def test_connect_ipv6_ipv6(self):
|
||||
self.skipIfLocalhostV4()
|
||||
self.do_test_connect(socket.AF_INET6, "::1")
|
||||
|
||||
@skipIfNoIPv6
|
||||
def test_connect_ipv6_dual(self):
|
||||
self.skipIfLocalhostV4()
|
||||
if Resolver.configured_class().__name__.endswith("TwistedResolver"):
|
||||
self.skipTest("TwistedResolver does not support multiple addresses")
|
||||
self.do_test_connect(socket.AF_INET6, "localhost")
|
||||
|
||||
def test_connect_unspec_ipv4(self):
|
||||
self.do_test_connect(socket.AF_UNSPEC, "127.0.0.1")
|
||||
|
||||
@skipIfNoIPv6
|
||||
def test_connect_unspec_ipv6(self):
|
||||
self.skipIfLocalhostV4()
|
||||
self.do_test_connect(socket.AF_UNSPEC, "::1")
|
||||
|
||||
def test_connect_unspec_dual(self):
|
||||
self.do_test_connect(socket.AF_UNSPEC, "localhost")
|
||||
|
||||
@gen_test
|
||||
def test_refused_ipv4(self):
|
||||
cleanup_func, port = refusing_port()
|
||||
self.addCleanup(cleanup_func)
|
||||
with self.assertRaises(IOError):
|
||||
yield self.client.connect("127.0.0.1", port)
|
||||
|
||||
def test_source_ip_fail(self):
|
||||
"""Fail when trying to use the source IP Address '8.8.8.8'.
|
||||
"""
|
||||
self.assertRaises(
|
||||
socket.error,
|
||||
self.do_test_connect,
|
||||
socket.AF_INET,
|
||||
"127.0.0.1",
|
||||
source_ip="8.8.8.8",
|
||||
)
|
||||
|
||||
def test_source_ip_success(self):
|
||||
"""Success when trying to use the source IP Address '127.0.0.1'.
|
||||
"""
|
||||
self.do_test_connect(socket.AF_INET, "127.0.0.1", source_ip="127.0.0.1")
|
||||
|
||||
@skipIfNonUnix
|
||||
def test_source_port_fail(self):
|
||||
"""Fail when trying to use source port 1.
|
||||
"""
|
||||
if getpass.getuser() == "root":
|
||||
# Root can use any port so we can't easily force this to fail.
|
||||
# This is mainly relevant for docker.
|
||||
self.skipTest("running as root")
|
||||
self.assertRaises(
|
||||
socket.error,
|
||||
self.do_test_connect,
|
||||
socket.AF_INET,
|
||||
"127.0.0.1",
|
||||
source_port=1,
|
||||
)
|
||||
|
||||
@gen_test
|
||||
def test_connect_timeout(self):
|
||||
timeout = 0.05
|
||||
|
||||
class TimeoutResolver(Resolver):
|
||||
def resolve(self, *args, **kwargs):
|
||||
return Future() # never completes
|
||||
|
||||
with self.assertRaises(TimeoutError):
|
||||
yield TCPClient(resolver=TimeoutResolver()).connect(
|
||||
"1.2.3.4", 12345, timeout=timeout
|
||||
)
|
||||
|
||||
|
||||
class TestConnectorSplit(unittest.TestCase):
|
||||
def test_one_family(self):
|
||||
# These addresses aren't in the right format, but split doesn't care.
|
||||
primary, secondary = _Connector.split([(AF1, "a"), (AF1, "b")])
|
||||
self.assertEqual(primary, [(AF1, "a"), (AF1, "b")])
|
||||
self.assertEqual(secondary, [])
|
||||
|
||||
def test_mixed(self):
|
||||
primary, secondary = _Connector.split(
|
||||
[(AF1, "a"), (AF2, "b"), (AF1, "c"), (AF2, "d")]
|
||||
)
|
||||
self.assertEqual(primary, [(AF1, "a"), (AF1, "c")])
|
||||
self.assertEqual(secondary, [(AF2, "b"), (AF2, "d")])
|
||||
|
||||
|
||||
class ConnectorTest(AsyncTestCase):
|
||||
class FakeStream(object):
|
||||
def __init__(self):
|
||||
self.closed = False
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.connect_futures = (
|
||||
{}
|
||||
) # type: Dict[Tuple[int, typing.Any], Future[ConnectorTest.FakeStream]]
|
||||
self.streams = {} # type: Dict[typing.Any, ConnectorTest.FakeStream]
|
||||
self.addrinfo = [(AF1, "a"), (AF1, "b"), (AF2, "c"), (AF2, "d")]
|
||||
|
||||
def tearDown(self):
|
||||
# Unless explicitly checked (and popped) in the test, we shouldn't
|
||||
# be closing any streams
|
||||
for stream in self.streams.values():
|
||||
self.assertFalse(stream.closed)
|
||||
super().tearDown()
|
||||
|
||||
def create_stream(self, af, addr):
|
||||
stream = ConnectorTest.FakeStream()
|
||||
self.streams[addr] = stream
|
||||
future = Future() # type: Future[ConnectorTest.FakeStream]
|
||||
self.connect_futures[(af, addr)] = future
|
||||
return stream, future
|
||||
|
||||
def assert_pending(self, *keys):
|
||||
self.assertEqual(sorted(self.connect_futures.keys()), sorted(keys))
|
||||
|
||||
def resolve_connect(self, af, addr, success):
|
||||
future = self.connect_futures.pop((af, addr))
|
||||
if success:
|
||||
future.set_result(self.streams[addr])
|
||||
else:
|
||||
self.streams.pop(addr)
|
||||
future.set_exception(IOError())
|
||||
# Run the loop to allow callbacks to be run.
|
||||
self.io_loop.add_callback(self.stop)
|
||||
self.wait()
|
||||
|
||||
def assert_connector_streams_closed(self, conn):
|
||||
for stream in conn.streams:
|
||||
self.assertTrue(stream.closed)
|
||||
|
||||
def start_connect(self, addrinfo):
|
||||
conn = _Connector(addrinfo, self.create_stream)
|
||||
# Give it a huge timeout; we'll trigger timeouts manually.
|
||||
future = conn.start(3600, connect_timeout=self.io_loop.time() + 3600)
|
||||
return conn, future
|
||||
|
||||
def test_immediate_success(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assertEqual(list(self.connect_futures.keys()), [(AF1, "a")])
|
||||
self.resolve_connect(AF1, "a", True)
|
||||
self.assertEqual(future.result(), (AF1, "a", self.streams["a"]))
|
||||
|
||||
def test_immediate_failure(self):
|
||||
# Fail with just one address.
|
||||
conn, future = self.start_connect([(AF1, "a")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assertRaises(IOError, future.result)
|
||||
|
||||
def test_one_family_second_try(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"))
|
||||
self.resolve_connect(AF1, "b", True)
|
||||
self.assertEqual(future.result(), (AF1, "b", self.streams["b"]))
|
||||
|
||||
def test_one_family_second_try_failure(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"))
|
||||
self.resolve_connect(AF1, "b", False)
|
||||
self.assertRaises(IOError, future.result)
|
||||
|
||||
def test_one_family_second_try_timeout(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
# trigger the timeout while the first lookup is pending;
|
||||
# nothing happens.
|
||||
conn.on_timeout()
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"))
|
||||
self.resolve_connect(AF1, "b", True)
|
||||
self.assertEqual(future.result(), (AF1, "b", self.streams["b"]))
|
||||
|
||||
def test_two_families_immediate_failure(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"), (AF2, "c"))
|
||||
self.resolve_connect(AF1, "b", False)
|
||||
self.resolve_connect(AF2, "c", True)
|
||||
self.assertEqual(future.result(), (AF2, "c", self.streams["c"]))
|
||||
|
||||
def test_two_families_timeout(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_timeout()
|
||||
self.assert_pending((AF1, "a"), (AF2, "c"))
|
||||
self.resolve_connect(AF2, "c", True)
|
||||
self.assertEqual(future.result(), (AF2, "c", self.streams["c"]))
|
||||
# resolving 'a' after the connection has completed doesn't start 'b'
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending()
|
||||
|
||||
def test_success_after_timeout(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_timeout()
|
||||
self.assert_pending((AF1, "a"), (AF2, "c"))
|
||||
self.resolve_connect(AF1, "a", True)
|
||||
self.assertEqual(future.result(), (AF1, "a", self.streams["a"]))
|
||||
# resolving 'c' after completion closes the connection.
|
||||
self.resolve_connect(AF2, "c", True)
|
||||
self.assertTrue(self.streams.pop("c").closed)
|
||||
|
||||
def test_all_fail(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_timeout()
|
||||
self.assert_pending((AF1, "a"), (AF2, "c"))
|
||||
self.resolve_connect(AF2, "c", False)
|
||||
self.assert_pending((AF1, "a"), (AF2, "d"))
|
||||
self.resolve_connect(AF2, "d", False)
|
||||
# one queue is now empty
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"))
|
||||
self.assertFalse(future.done())
|
||||
self.resolve_connect(AF1, "b", False)
|
||||
self.assertRaises(IOError, future.result)
|
||||
|
||||
def test_one_family_timeout_after_connect_timeout(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_connect_timeout()
|
||||
# the connector will close all streams on connect timeout, we
|
||||
# should explicitly pop the connect_future.
|
||||
self.connect_futures.pop((AF1, "a"))
|
||||
self.assertTrue(self.streams.pop("a").closed)
|
||||
conn.on_timeout()
|
||||
# if the future is set with TimeoutError, we will not iterate next
|
||||
# possible address.
|
||||
self.assert_pending()
|
||||
self.assertEqual(len(conn.streams), 1)
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertRaises(TimeoutError, future.result)
|
||||
|
||||
def test_one_family_success_before_connect_timeout(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", True)
|
||||
conn.on_connect_timeout()
|
||||
self.assert_pending()
|
||||
self.assertEqual(self.streams["a"].closed, False)
|
||||
# success stream will be pop
|
||||
self.assertEqual(len(conn.streams), 0)
|
||||
# streams in connector should be closed after connect timeout
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertEqual(future.result(), (AF1, "a", self.streams["a"]))
|
||||
|
||||
def test_one_family_second_try_after_connect_timeout(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"))
|
||||
conn.on_connect_timeout()
|
||||
self.connect_futures.pop((AF1, "b"))
|
||||
self.assertTrue(self.streams.pop("b").closed)
|
||||
self.assert_pending()
|
||||
self.assertEqual(len(conn.streams), 2)
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertRaises(TimeoutError, future.result)
|
||||
|
||||
def test_one_family_second_try_failure_before_connect_timeout(self):
|
||||
conn, future = self.start_connect([(AF1, "a"), (AF1, "b")])
|
||||
self.assert_pending((AF1, "a"))
|
||||
self.resolve_connect(AF1, "a", False)
|
||||
self.assert_pending((AF1, "b"))
|
||||
self.resolve_connect(AF1, "b", False)
|
||||
conn.on_connect_timeout()
|
||||
self.assert_pending()
|
||||
self.assertEqual(len(conn.streams), 2)
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertRaises(IOError, future.result)
|
||||
|
||||
def test_two_family_timeout_before_connect_timeout(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_timeout()
|
||||
self.assert_pending((AF1, "a"), (AF2, "c"))
|
||||
conn.on_connect_timeout()
|
||||
self.connect_futures.pop((AF1, "a"))
|
||||
self.assertTrue(self.streams.pop("a").closed)
|
||||
self.connect_futures.pop((AF2, "c"))
|
||||
self.assertTrue(self.streams.pop("c").closed)
|
||||
self.assert_pending()
|
||||
self.assertEqual(len(conn.streams), 2)
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertRaises(TimeoutError, future.result)
|
||||
|
||||
def test_two_family_success_after_timeout(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_timeout()
|
||||
self.assert_pending((AF1, "a"), (AF2, "c"))
|
||||
self.resolve_connect(AF1, "a", True)
|
||||
# if one of streams succeed, connector will close all other streams
|
||||
self.connect_futures.pop((AF2, "c"))
|
||||
self.assertTrue(self.streams.pop("c").closed)
|
||||
self.assert_pending()
|
||||
self.assertEqual(len(conn.streams), 1)
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertEqual(future.result(), (AF1, "a", self.streams["a"]))
|
||||
|
||||
def test_two_family_timeout_after_connect_timeout(self):
|
||||
conn, future = self.start_connect(self.addrinfo)
|
||||
self.assert_pending((AF1, "a"))
|
||||
conn.on_connect_timeout()
|
||||
self.connect_futures.pop((AF1, "a"))
|
||||
self.assertTrue(self.streams.pop("a").closed)
|
||||
self.assert_pending()
|
||||
conn.on_timeout()
|
||||
# if the future is set with TimeoutError, connector will not
|
||||
# trigger secondary address.
|
||||
self.assert_pending()
|
||||
self.assertEqual(len(conn.streams), 1)
|
||||
self.assert_connector_streams_closed(conn)
|
||||
self.assertRaises(TimeoutError, future.result)
|
192
venv/Lib/site-packages/tornado/test/tcpserver_test.py
Normal file
192
venv/Lib/site-packages/tornado/test/tcpserver_test.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
from tornado.escape import utf8, to_unicode
|
||||
from tornado import gen
|
||||
from tornado.iostream import IOStream
|
||||
from tornado.log import app_log
|
||||
from tornado.tcpserver import TCPServer
|
||||
from tornado.test.util import skipIfNonUnix
|
||||
from tornado.testing import AsyncTestCase, ExpectLog, bind_unused_port, gen_test
|
||||
|
||||
|
||||
class TCPServerTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_handle_stream_coroutine_logging(self):
|
||||
# handle_stream may be a coroutine and any exception in its
|
||||
# Future will be logged.
|
||||
class TestServer(TCPServer):
|
||||
@gen.coroutine
|
||||
def handle_stream(self, stream, address):
|
||||
yield stream.read_bytes(len(b"hello"))
|
||||
stream.close()
|
||||
1 / 0
|
||||
|
||||
server = client = None
|
||||
try:
|
||||
sock, port = bind_unused_port()
|
||||
server = TestServer()
|
||||
server.add_socket(sock)
|
||||
client = IOStream(socket.socket())
|
||||
with ExpectLog(app_log, "Exception in callback"):
|
||||
yield client.connect(("localhost", port))
|
||||
yield client.write(b"hello")
|
||||
yield client.read_until_close()
|
||||
yield gen.moment
|
||||
finally:
|
||||
if server is not None:
|
||||
server.stop()
|
||||
if client is not None:
|
||||
client.close()
|
||||
|
||||
@gen_test
|
||||
def test_handle_stream_native_coroutine(self):
|
||||
# handle_stream may be a native coroutine.
|
||||
|
||||
class TestServer(TCPServer):
|
||||
async def handle_stream(self, stream, address):
|
||||
stream.write(b"data")
|
||||
stream.close()
|
||||
|
||||
sock, port = bind_unused_port()
|
||||
server = TestServer()
|
||||
server.add_socket(sock)
|
||||
client = IOStream(socket.socket())
|
||||
yield client.connect(("localhost", port))
|
||||
result = yield client.read_until_close()
|
||||
self.assertEqual(result, b"data")
|
||||
server.stop()
|
||||
client.close()
|
||||
|
||||
def test_stop_twice(self):
|
||||
sock, port = bind_unused_port()
|
||||
server = TCPServer()
|
||||
server.add_socket(sock)
|
||||
server.stop()
|
||||
server.stop()
|
||||
|
||||
@gen_test
|
||||
def test_stop_in_callback(self):
|
||||
# Issue #2069: calling server.stop() in a loop callback should not
|
||||
# raise EBADF when the loop handles other server connection
|
||||
# requests in the same loop iteration
|
||||
|
||||
class TestServer(TCPServer):
|
||||
@gen.coroutine
|
||||
def handle_stream(self, stream, address):
|
||||
server.stop() # type: ignore
|
||||
yield stream.read_until_close()
|
||||
|
||||
sock, port = bind_unused_port()
|
||||
server = TestServer()
|
||||
server.add_socket(sock)
|
||||
server_addr = ("localhost", port)
|
||||
N = 40
|
||||
clients = [IOStream(socket.socket()) for i in range(N)]
|
||||
connected_clients = []
|
||||
|
||||
@gen.coroutine
|
||||
def connect(c):
|
||||
try:
|
||||
yield c.connect(server_addr)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
else:
|
||||
connected_clients.append(c)
|
||||
|
||||
yield [connect(c) for c in clients]
|
||||
|
||||
self.assertGreater(len(connected_clients), 0, "all clients failed connecting")
|
||||
try:
|
||||
if len(connected_clients) == N:
|
||||
# Ideally we'd make the test deterministic, but we're testing
|
||||
# for a race condition in combination with the system's TCP stack...
|
||||
self.skipTest(
|
||||
"at least one client should fail connecting "
|
||||
"for the test to be meaningful"
|
||||
)
|
||||
finally:
|
||||
for c in connected_clients:
|
||||
c.close()
|
||||
|
||||
# Here tearDown() would re-raise the EBADF encountered in the IO loop
|
||||
|
||||
|
||||
@skipIfNonUnix
|
||||
class TestMultiprocess(unittest.TestCase):
|
||||
# These tests verify that the two multiprocess examples from the
|
||||
# TCPServer docs work. Both tests start a server with three worker
|
||||
# processes, each of which prints its task id to stdout (a single
|
||||
# byte, so we don't have to worry about atomicity of the shared
|
||||
# stdout stream) and then exits.
|
||||
def run_subproc(self, code):
|
||||
proc = subprocess.Popen(
|
||||
sys.executable, stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
||||
)
|
||||
proc.stdin.write(utf8(code))
|
||||
proc.stdin.close()
|
||||
proc.wait()
|
||||
stdout = proc.stdout.read()
|
||||
proc.stdout.close()
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(
|
||||
"Process returned %d. stdout=%r" % (proc.returncode, stdout)
|
||||
)
|
||||
return to_unicode(stdout)
|
||||
|
||||
def test_single(self):
|
||||
# As a sanity check, run the single-process version through this test
|
||||
# harness too.
|
||||
code = textwrap.dedent(
|
||||
"""
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.tcpserver import TCPServer
|
||||
|
||||
server = TCPServer()
|
||||
server.listen(0, address='127.0.0.1')
|
||||
IOLoop.current().run_sync(lambda: None)
|
||||
print('012', end='')
|
||||
"""
|
||||
)
|
||||
out = self.run_subproc(code)
|
||||
self.assertEqual("".join(sorted(out)), "012")
|
||||
|
||||
def test_simple(self):
|
||||
code = textwrap.dedent(
|
||||
"""
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.process import task_id
|
||||
from tornado.tcpserver import TCPServer
|
||||
|
||||
server = TCPServer()
|
||||
server.bind(0, address='127.0.0.1')
|
||||
server.start(3)
|
||||
IOLoop.current().run_sync(lambda: None)
|
||||
print(task_id(), end='')
|
||||
"""
|
||||
)
|
||||
out = self.run_subproc(code)
|
||||
self.assertEqual("".join(sorted(out)), "012")
|
||||
|
||||
def test_advanced(self):
|
||||
code = textwrap.dedent(
|
||||
"""
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.netutil import bind_sockets
|
||||
from tornado.process import fork_processes, task_id
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.tcpserver import TCPServer
|
||||
|
||||
sockets = bind_sockets(0, address='127.0.0.1')
|
||||
fork_processes(3)
|
||||
server = TCPServer()
|
||||
server.add_sockets(sockets)
|
||||
IOLoop.current().run_sync(lambda: None)
|
||||
print(task_id(), end='')
|
||||
"""
|
||||
)
|
||||
out = self.run_subproc(code)
|
||||
self.assertEqual("".join(sorted(out)), "012")
|
536
venv/Lib/site-packages/tornado/test/template_test.py
Normal file
536
venv/Lib/site-packages/tornado/test/template_test.py
Normal file
|
@ -0,0 +1,536 @@
|
|||
import os
|
||||
import traceback
|
||||
import unittest
|
||||
|
||||
from tornado.escape import utf8, native_str, to_unicode
|
||||
from tornado.template import Template, DictLoader, ParseError, Loader
|
||||
from tornado.util import ObjectDict
|
||||
|
||||
import typing # noqa: F401
|
||||
|
||||
|
||||
class TemplateTest(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
template = Template("Hello {{ name }}!")
|
||||
self.assertEqual(template.generate(name="Ben"), b"Hello Ben!")
|
||||
|
||||
def test_bytes(self):
|
||||
template = Template("Hello {{ name }}!")
|
||||
self.assertEqual(template.generate(name=utf8("Ben")), b"Hello Ben!")
|
||||
|
||||
def test_expressions(self):
|
||||
template = Template("2 + 2 = {{ 2 + 2 }}")
|
||||
self.assertEqual(template.generate(), b"2 + 2 = 4")
|
||||
|
||||
def test_comment(self):
|
||||
template = Template("Hello{# TODO i18n #} {{ name }}!")
|
||||
self.assertEqual(template.generate(name=utf8("Ben")), b"Hello Ben!")
|
||||
|
||||
def test_include(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"index.html": '{% include "header.html" %}\nbody text',
|
||||
"header.html": "header text",
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("index.html").generate(), b"header text\nbody text"
|
||||
)
|
||||
|
||||
def test_extends(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"base.html": """\
|
||||
<title>{% block title %}default title{% end %}</title>
|
||||
<body>{% block body %}default body{% end %}</body>
|
||||
""",
|
||||
"page.html": """\
|
||||
{% extends "base.html" %}
|
||||
{% block title %}page title{% end %}
|
||||
{% block body %}page body{% end %}
|
||||
""",
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("page.html").generate(),
|
||||
b"<title>page title</title>\n<body>page body</body>\n",
|
||||
)
|
||||
|
||||
def test_relative_load(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"a/1.html": "{% include '2.html' %}",
|
||||
"a/2.html": "{% include '../b/3.html' %}",
|
||||
"b/3.html": "ok",
|
||||
}
|
||||
)
|
||||
self.assertEqual(loader.load("a/1.html").generate(), b"ok")
|
||||
|
||||
def test_escaping(self):
|
||||
self.assertRaises(ParseError, lambda: Template("{{"))
|
||||
self.assertRaises(ParseError, lambda: Template("{%"))
|
||||
self.assertEqual(Template("{{!").generate(), b"{{")
|
||||
self.assertEqual(Template("{%!").generate(), b"{%")
|
||||
self.assertEqual(Template("{#!").generate(), b"{#")
|
||||
self.assertEqual(
|
||||
Template("{{ 'expr' }} {{!jquery expr}}").generate(),
|
||||
b"expr {{jquery expr}}",
|
||||
)
|
||||
|
||||
def test_unicode_template(self):
|
||||
template = Template(utf8(u"\u00e9"))
|
||||
self.assertEqual(template.generate(), utf8(u"\u00e9"))
|
||||
|
||||
def test_unicode_literal_expression(self):
|
||||
# Unicode literals should be usable in templates. Note that this
|
||||
# test simulates unicode characters appearing directly in the
|
||||
# template file (with utf8 encoding), i.e. \u escapes would not
|
||||
# be used in the template file itself.
|
||||
template = Template(utf8(u'{{ "\u00e9" }}'))
|
||||
self.assertEqual(template.generate(), utf8(u"\u00e9"))
|
||||
|
||||
def test_custom_namespace(self):
|
||||
loader = DictLoader(
|
||||
{"test.html": "{{ inc(5) }}"}, namespace={"inc": lambda x: x + 1}
|
||||
)
|
||||
self.assertEqual(loader.load("test.html").generate(), b"6")
|
||||
|
||||
def test_apply(self):
|
||||
def upper(s):
|
||||
return s.upper()
|
||||
|
||||
template = Template(utf8("{% apply upper %}foo{% end %}"))
|
||||
self.assertEqual(template.generate(upper=upper), b"FOO")
|
||||
|
||||
def test_unicode_apply(self):
|
||||
def upper(s):
|
||||
return to_unicode(s).upper()
|
||||
|
||||
template = Template(utf8(u"{% apply upper %}foo \u00e9{% end %}"))
|
||||
self.assertEqual(template.generate(upper=upper), utf8(u"FOO \u00c9"))
|
||||
|
||||
def test_bytes_apply(self):
|
||||
def upper(s):
|
||||
return utf8(to_unicode(s).upper())
|
||||
|
||||
template = Template(utf8(u"{% apply upper %}foo \u00e9{% end %}"))
|
||||
self.assertEqual(template.generate(upper=upper), utf8(u"FOO \u00c9"))
|
||||
|
||||
def test_if(self):
|
||||
template = Template(utf8("{% if x > 4 %}yes{% else %}no{% end %}"))
|
||||
self.assertEqual(template.generate(x=5), b"yes")
|
||||
self.assertEqual(template.generate(x=3), b"no")
|
||||
|
||||
def test_if_empty_body(self):
|
||||
template = Template(utf8("{% if True %}{% else %}{% end %}"))
|
||||
self.assertEqual(template.generate(), b"")
|
||||
|
||||
def test_try(self):
|
||||
template = Template(
|
||||
utf8(
|
||||
"""{% try %}
|
||||
try{% set y = 1/x %}
|
||||
{% except %}-except
|
||||
{% else %}-else
|
||||
{% finally %}-finally
|
||||
{% end %}"""
|
||||
)
|
||||
)
|
||||
self.assertEqual(template.generate(x=1), b"\ntry\n-else\n-finally\n")
|
||||
self.assertEqual(template.generate(x=0), b"\ntry-except\n-finally\n")
|
||||
|
||||
def test_comment_directive(self):
|
||||
template = Template(utf8("{% comment blah blah %}foo"))
|
||||
self.assertEqual(template.generate(), b"foo")
|
||||
|
||||
def test_break_continue(self):
|
||||
template = Template(
|
||||
utf8(
|
||||
"""\
|
||||
{% for i in range(10) %}
|
||||
{% if i == 2 %}
|
||||
{% continue %}
|
||||
{% end %}
|
||||
{{ i }}
|
||||
{% if i == 6 %}
|
||||
{% break %}
|
||||
{% end %}
|
||||
{% end %}"""
|
||||
)
|
||||
)
|
||||
result = template.generate()
|
||||
# remove extraneous whitespace
|
||||
result = b"".join(result.split())
|
||||
self.assertEqual(result, b"013456")
|
||||
|
||||
def test_break_outside_loop(self):
|
||||
try:
|
||||
Template(utf8("{% break %}"))
|
||||
raise Exception("Did not get expected exception")
|
||||
except ParseError:
|
||||
pass
|
||||
|
||||
def test_break_in_apply(self):
|
||||
# This test verifies current behavior, although of course it would
|
||||
# be nice if apply didn't cause seemingly unrelated breakage
|
||||
try:
|
||||
Template(
|
||||
utf8("{% for i in [] %}{% apply foo %}{% break %}{% end %}{% end %}")
|
||||
)
|
||||
raise Exception("Did not get expected exception")
|
||||
except ParseError:
|
||||
pass
|
||||
|
||||
@unittest.skip("no testable future imports")
|
||||
def test_no_inherit_future(self):
|
||||
# TODO(bdarnell): make a test like this for one of the future
|
||||
# imports available in python 3. Unfortunately they're harder
|
||||
# to use in a template than division was.
|
||||
|
||||
# This file has from __future__ import division...
|
||||
self.assertEqual(1 / 2, 0.5)
|
||||
# ...but the template doesn't
|
||||
template = Template("{{ 1 / 2 }}")
|
||||
self.assertEqual(template.generate(), "0")
|
||||
|
||||
def test_non_ascii_name(self):
|
||||
loader = DictLoader({u"t\u00e9st.html": "hello"})
|
||||
self.assertEqual(loader.load(u"t\u00e9st.html").generate(), b"hello")
|
||||
|
||||
|
||||
class StackTraceTest(unittest.TestCase):
|
||||
def test_error_line_number_expression(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"test.html": """one
|
||||
two{{1/0}}
|
||||
three
|
||||
"""
|
||||
}
|
||||
)
|
||||
try:
|
||||
loader.load("test.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
self.assertTrue("# test.html:2" in traceback.format_exc())
|
||||
|
||||
def test_error_line_number_directive(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"test.html": """one
|
||||
two{%if 1/0%}
|
||||
three{%end%}
|
||||
"""
|
||||
}
|
||||
)
|
||||
try:
|
||||
loader.load("test.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
self.assertTrue("# test.html:2" in traceback.format_exc())
|
||||
|
||||
def test_error_line_number_module(self):
|
||||
loader = None # type: typing.Optional[DictLoader]
|
||||
|
||||
def load_generate(path, **kwargs):
|
||||
assert loader is not None
|
||||
return loader.load(path).generate(**kwargs)
|
||||
|
||||
loader = DictLoader(
|
||||
{"base.html": "{% module Template('sub.html') %}", "sub.html": "{{1/0}}"},
|
||||
namespace={"_tt_modules": ObjectDict(Template=load_generate)},
|
||||
)
|
||||
try:
|
||||
loader.load("base.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
exc_stack = traceback.format_exc()
|
||||
self.assertTrue("# base.html:1" in exc_stack)
|
||||
self.assertTrue("# sub.html:1" in exc_stack)
|
||||
|
||||
def test_error_line_number_include(self):
|
||||
loader = DictLoader(
|
||||
{"base.html": "{% include 'sub.html' %}", "sub.html": "{{1/0}}"}
|
||||
)
|
||||
try:
|
||||
loader.load("base.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
self.assertTrue("# sub.html:1 (via base.html:1)" in traceback.format_exc())
|
||||
|
||||
def test_error_line_number_extends_base_error(self):
|
||||
loader = DictLoader(
|
||||
{"base.html": "{{1/0}}", "sub.html": "{% extends 'base.html' %}"}
|
||||
)
|
||||
try:
|
||||
loader.load("sub.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
exc_stack = traceback.format_exc()
|
||||
self.assertTrue("# base.html:1" in exc_stack)
|
||||
|
||||
def test_error_line_number_extends_sub_error(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"base.html": "{% block 'block' %}{% end %}",
|
||||
"sub.html": """
|
||||
{% extends 'base.html' %}
|
||||
{% block 'block' %}
|
||||
{{1/0}}
|
||||
{% end %}
|
||||
""",
|
||||
}
|
||||
)
|
||||
try:
|
||||
loader.load("sub.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
self.assertTrue("# sub.html:4 (via base.html:1)" in traceback.format_exc())
|
||||
|
||||
def test_multi_includes(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"a.html": "{% include 'b.html' %}",
|
||||
"b.html": "{% include 'c.html' %}",
|
||||
"c.html": "{{1/0}}",
|
||||
}
|
||||
)
|
||||
try:
|
||||
loader.load("a.html").generate()
|
||||
self.fail("did not get expected exception")
|
||||
except ZeroDivisionError:
|
||||
self.assertTrue(
|
||||
"# c.html:1 (via b.html:1, a.html:1)" in traceback.format_exc()
|
||||
)
|
||||
|
||||
|
||||
class ParseErrorDetailTest(unittest.TestCase):
|
||||
def test_details(self):
|
||||
loader = DictLoader({"foo.html": "\n\n{{"})
|
||||
with self.assertRaises(ParseError) as cm:
|
||||
loader.load("foo.html")
|
||||
self.assertEqual("Missing end expression }} at foo.html:3", str(cm.exception))
|
||||
self.assertEqual("foo.html", cm.exception.filename)
|
||||
self.assertEqual(3, cm.exception.lineno)
|
||||
|
||||
def test_custom_parse_error(self):
|
||||
# Make sure that ParseErrors remain compatible with their
|
||||
# pre-4.3 signature.
|
||||
self.assertEqual("asdf at None:0", str(ParseError("asdf")))
|
||||
|
||||
|
||||
class AutoEscapeTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.templates = {
|
||||
"escaped.html": "{% autoescape xhtml_escape %}{{ name }}",
|
||||
"unescaped.html": "{% autoescape None %}{{ name }}",
|
||||
"default.html": "{{ name }}",
|
||||
"include.html": """\
|
||||
escaped: {% include 'escaped.html' %}
|
||||
unescaped: {% include 'unescaped.html' %}
|
||||
default: {% include 'default.html' %}
|
||||
""",
|
||||
"escaped_block.html": """\
|
||||
{% autoescape xhtml_escape %}\
|
||||
{% block name %}base: {{ name }}{% end %}""",
|
||||
"unescaped_block.html": """\
|
||||
{% autoescape None %}\
|
||||
{% block name %}base: {{ name }}{% end %}""",
|
||||
# Extend a base template with different autoescape policy,
|
||||
# with and without overriding the base's blocks
|
||||
"escaped_extends_unescaped.html": """\
|
||||
{% autoescape xhtml_escape %}\
|
||||
{% extends "unescaped_block.html" %}""",
|
||||
"escaped_overrides_unescaped.html": """\
|
||||
{% autoescape xhtml_escape %}\
|
||||
{% extends "unescaped_block.html" %}\
|
||||
{% block name %}extended: {{ name }}{% end %}""",
|
||||
"unescaped_extends_escaped.html": """\
|
||||
{% autoescape None %}\
|
||||
{% extends "escaped_block.html" %}""",
|
||||
"unescaped_overrides_escaped.html": """\
|
||||
{% autoescape None %}\
|
||||
{% extends "escaped_block.html" %}\
|
||||
{% block name %}extended: {{ name }}{% end %}""",
|
||||
"raw_expression.html": """\
|
||||
{% autoescape xhtml_escape %}\
|
||||
expr: {{ name }}
|
||||
raw: {% raw name %}""",
|
||||
}
|
||||
|
||||
def test_default_off(self):
|
||||
loader = DictLoader(self.templates, autoescape=None)
|
||||
name = "Bobby <table>s"
|
||||
self.assertEqual(
|
||||
loader.load("escaped.html").generate(name=name), b"Bobby <table>s"
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("unescaped.html").generate(name=name), b"Bobby <table>s"
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("default.html").generate(name=name), b"Bobby <table>s"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
loader.load("include.html").generate(name=name),
|
||||
b"escaped: Bobby <table>s\n"
|
||||
b"unescaped: Bobby <table>s\n"
|
||||
b"default: Bobby <table>s\n",
|
||||
)
|
||||
|
||||
def test_default_on(self):
|
||||
loader = DictLoader(self.templates, autoescape="xhtml_escape")
|
||||
name = "Bobby <table>s"
|
||||
self.assertEqual(
|
||||
loader.load("escaped.html").generate(name=name), b"Bobby <table>s"
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("unescaped.html").generate(name=name), b"Bobby <table>s"
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("default.html").generate(name=name), b"Bobby <table>s"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
loader.load("include.html").generate(name=name),
|
||||
b"escaped: Bobby <table>s\n"
|
||||
b"unescaped: Bobby <table>s\n"
|
||||
b"default: Bobby <table>s\n",
|
||||
)
|
||||
|
||||
def test_unextended_block(self):
|
||||
loader = DictLoader(self.templates)
|
||||
name = "<script>"
|
||||
self.assertEqual(
|
||||
loader.load("escaped_block.html").generate(name=name),
|
||||
b"base: <script>",
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("unescaped_block.html").generate(name=name), b"base: <script>"
|
||||
)
|
||||
|
||||
def test_extended_block(self):
|
||||
loader = DictLoader(self.templates)
|
||||
|
||||
def render(name):
|
||||
return loader.load(name).generate(name="<script>")
|
||||
|
||||
self.assertEqual(render("escaped_extends_unescaped.html"), b"base: <script>")
|
||||
self.assertEqual(
|
||||
render("escaped_overrides_unescaped.html"), b"extended: <script>"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
render("unescaped_extends_escaped.html"), b"base: <script>"
|
||||
)
|
||||
self.assertEqual(
|
||||
render("unescaped_overrides_escaped.html"), b"extended: <script>"
|
||||
)
|
||||
|
||||
def test_raw_expression(self):
|
||||
loader = DictLoader(self.templates)
|
||||
|
||||
def render(name):
|
||||
return loader.load(name).generate(name='<>&"')
|
||||
|
||||
self.assertEqual(
|
||||
render("raw_expression.html"), b"expr: <>&"\n" b'raw: <>&"'
|
||||
)
|
||||
|
||||
def test_custom_escape(self):
|
||||
loader = DictLoader({"foo.py": "{% autoescape py_escape %}s = {{ name }}\n"})
|
||||
|
||||
def py_escape(s):
|
||||
self.assertEqual(type(s), bytes)
|
||||
return repr(native_str(s))
|
||||
|
||||
def render(template, name):
|
||||
return loader.load(template).generate(py_escape=py_escape, name=name)
|
||||
|
||||
self.assertEqual(render("foo.py", "<html>"), b"s = '<html>'\n")
|
||||
self.assertEqual(render("foo.py", "';sys.exit()"), b"""s = "';sys.exit()"\n""")
|
||||
self.assertEqual(
|
||||
render("foo.py", ["not a string"]), b"""s = "['not a string']"\n"""
|
||||
)
|
||||
|
||||
def test_manual_minimize_whitespace(self):
|
||||
# Whitespace including newlines is allowed within template tags
|
||||
# and directives, and this is one way to avoid long lines while
|
||||
# keeping extra whitespace out of the rendered output.
|
||||
loader = DictLoader(
|
||||
{
|
||||
"foo.txt": """\
|
||||
{% for i in items
|
||||
%}{% if i > 0 %}, {% end %}{#
|
||||
#}{{i
|
||||
}}{% end
|
||||
%}"""
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("foo.txt").generate(items=range(5)), b"0, 1, 2, 3, 4"
|
||||
)
|
||||
|
||||
def test_whitespace_by_filename(self):
|
||||
# Default whitespace handling depends on the template filename.
|
||||
loader = DictLoader(
|
||||
{
|
||||
"foo.html": " \n\t\n asdf\t ",
|
||||
"bar.js": " \n\n\n\t qwer ",
|
||||
"baz.txt": "\t zxcv\n\n",
|
||||
"include.html": " {% include baz.txt %} \n ",
|
||||
"include.txt": "\t\t{% include foo.html %} ",
|
||||
}
|
||||
)
|
||||
|
||||
# HTML and JS files have whitespace compressed by default.
|
||||
self.assertEqual(loader.load("foo.html").generate(), b"\nasdf ")
|
||||
self.assertEqual(loader.load("bar.js").generate(), b"\nqwer ")
|
||||
# TXT files do not.
|
||||
self.assertEqual(loader.load("baz.txt").generate(), b"\t zxcv\n\n")
|
||||
|
||||
# Each file maintains its own status even when included in
|
||||
# a file of the other type.
|
||||
self.assertEqual(loader.load("include.html").generate(), b" \t zxcv\n\n\n")
|
||||
self.assertEqual(loader.load("include.txt").generate(), b"\t\t\nasdf ")
|
||||
|
||||
def test_whitespace_by_loader(self):
|
||||
templates = {"foo.html": "\t\tfoo\n\n", "bar.txt": "\t\tbar\n\n"}
|
||||
loader = DictLoader(templates, whitespace="all")
|
||||
self.assertEqual(loader.load("foo.html").generate(), b"\t\tfoo\n\n")
|
||||
self.assertEqual(loader.load("bar.txt").generate(), b"\t\tbar\n\n")
|
||||
|
||||
loader = DictLoader(templates, whitespace="single")
|
||||
self.assertEqual(loader.load("foo.html").generate(), b" foo\n")
|
||||
self.assertEqual(loader.load("bar.txt").generate(), b" bar\n")
|
||||
|
||||
loader = DictLoader(templates, whitespace="oneline")
|
||||
self.assertEqual(loader.load("foo.html").generate(), b" foo ")
|
||||
self.assertEqual(loader.load("bar.txt").generate(), b" bar ")
|
||||
|
||||
def test_whitespace_directive(self):
|
||||
loader = DictLoader(
|
||||
{
|
||||
"foo.html": """\
|
||||
{% whitespace oneline %}
|
||||
{% for i in range(3) %}
|
||||
{{ i }}
|
||||
{% end %}
|
||||
{% whitespace all %}
|
||||
pre\tformatted
|
||||
"""
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
loader.load("foo.html").generate(), b" 0 1 2 \n pre\tformatted\n"
|
||||
)
|
||||
|
||||
|
||||
class TemplateLoaderTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.loader = Loader(os.path.join(os.path.dirname(__file__), "templates"))
|
||||
|
||||
def test_utf8_in_file(self):
|
||||
tmpl = self.loader.load("utf8.html")
|
||||
result = tmpl.generate()
|
||||
self.assertEqual(to_unicode(result).strip(), u"H\u00e9llo")
|
1
venv/Lib/site-packages/tornado/test/templates/utf8.html
Normal file
1
venv/Lib/site-packages/tornado/test/templates/utf8.html
Normal file
|
@ -0,0 +1 @@
|
|||
Héllo
|
20
venv/Lib/site-packages/tornado/test/test.crt
Normal file
20
venv/Lib/site-packages/tornado/test/test.crt
Normal file
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkOgAwIBAgIUV4spou0CenmvKqa7Hml/MC+JKiAwDQYJKoZIhvcNAQEL
|
||||
BQAwPTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExGTAXBgNVBAoM
|
||||
EFRvcm5hZG8gV2ViIFRlc3QwHhcNMTgwOTI5MTM1NjQ1WhcNMjgwOTI2MTM1NjQ1
|
||||
WjA9MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEZMBcGA1UECgwQ
|
||||
VG9ybmFkbyBXZWIgVGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AKT0LdyI8tW5uwP3ahE8BFSz+j3SsKBDv/0cKvqxVVE6sLEST2s3HjArZvIIG5sb
|
||||
iBkWDrqnZ6UKDvB4jlobLGAkepxDbrxHWxK53n0C28XXGLqJQ01TlTZ5rpjttMeg
|
||||
5SKNjHbxpOvpUwwQS4br4WjZKKyTGiXpFkFUty+tYVU35/U2yyvreWHmzpHx/25t
|
||||
H7O2RBARVwJYKOGPtlH62lQjpIWfVfklY4Ip8Hjl3B6rBxPyBULmVQw0qgoZn648
|
||||
oa4oLjs0wnYBz01gVjNMDHej52SsB/ieH7W1TxFMzqOlcvHh41uFbQJPgcXsruSS
|
||||
9Z4twzSWkUp2vk/C//4Sz38CAwEAAaNTMFEwHQYDVR0OBBYEFLf8fQ5+u8sDWAd3
|
||||
r5ZjZ5MmDWJeMB8GA1UdIwQYMBaAFLf8fQ5+u8sDWAd3r5ZjZ5MmDWJeMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADkkm3pIb9IeqVNmQ2uhQOgw
|
||||
UwyToTYUHNTb/Nm5lzBTBqC8gbXAS24RQ30AB/7G115Uxeo+YMKfITxm/CgR+vhF
|
||||
F59/YrzwXj+G8bdbuVl/UbB6f9RSp+Zo93rUZAtPWr77gxLUrcwSRzzDwxFjC2nC
|
||||
6eigbkvt1OQY775RwnFAt7HKPclE0Out+cGJIboJuO1f3r57ZdyFH0GzbZEff/7K
|
||||
atGXohijWJjYvU4mk0KFHORZrcBpsv9cfkFbmgVmiRwxRJ1tLauHM3Ne+VfqYE5M
|
||||
4rTStSyz3ASqVKJ2iFMQueNR/tUOuDlfRt+0nhJMuYSSkW+KTgnwyOGU9cv+mxA=
|
||||
-----END CERTIFICATE-----
|
28
venv/Lib/site-packages/tornado/test/test.key
Normal file
28
venv/Lib/site-packages/tornado/test/test.key
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCk9C3ciPLVubsD
|
||||
92oRPARUs/o90rCgQ7/9HCr6sVVROrCxEk9rNx4wK2byCBubG4gZFg66p2elCg7w
|
||||
eI5aGyxgJHqcQ268R1sSud59AtvF1xi6iUNNU5U2ea6Y7bTHoOUijYx28aTr6VMM
|
||||
EEuG6+Fo2Siskxol6RZBVLcvrWFVN+f1Nssr63lh5s6R8f9ubR+ztkQQEVcCWCjh
|
||||
j7ZR+tpUI6SFn1X5JWOCKfB45dweqwcT8gVC5lUMNKoKGZ+uPKGuKC47NMJ2Ac9N
|
||||
YFYzTAx3o+dkrAf4nh+1tU8RTM6jpXLx4eNbhW0CT4HF7K7kkvWeLcM0lpFKdr5P
|
||||
wv/+Es9/AgMBAAECggEABi6AaXtYXloPgB6NgwfUwbfc8OQsalUfpMShd7OdluW0
|
||||
KW6eO05de0ClIvzay/1EJGyHMMeFQtIVrT1XWFkcWJ4FWkXMqJGkABenFtg8lDVz
|
||||
X8o1E3jGZrw4ptKBq9mDvL/BO9PiclTUH+ecbPn6AIvi0lTQ7grGIryiAM9mjmLy
|
||||
jpCwoutF2LD4RPNg8vqWe/Z1rQw5lp8FOHhRwPooHHeoq1bSrp8dqvVAwAam7Mmf
|
||||
uFgI8jrNycPgr2cwEEtbq2TQ625MhVnCpwT+kErmAStfbXXuqv1X1ZZgiNxf+61C
|
||||
OL0bhPRVIHmmjiK/5qHRuN4Q5u9/Yp2SJ4W5xadSQQKBgQDR7dnOlYYQiaoPJeD/
|
||||
7jcLVJbWwbr7bE19O/QpYAtkA/FtGlKr+hQxPhK6OYp+in8eHf+ga/NSAjCWRBoh
|
||||
MNAVCJtiirHo2tFsLFOmlJpGL9n3sX8UnkJN90oHfWrzJ8BZnXaSw2eOuyw8LLj+
|
||||
Q+ISl6Go8/xfsuy3EDv4AP1wCwKBgQDJJ4vEV3Kr+bc6N/xeu+G0oHvRAWwuQpcx
|
||||
9D+XpnqbJbFDnWKNE7oGsDCs8Qjr0CdFUN1pm1ppITDZ5N1cWuDg/47ZAXqEK6D1
|
||||
z13S7O0oQPlnsPL7mHs2Vl73muAaBPAojFvceHHfccr7Z94BXqKsiyfaWz6kclT/
|
||||
Nl4JTdsC3QKBgQCeYgozL2J/da2lUhnIXcyPstk+29kbueFYu/QBh2HwqnzqqLJ4
|
||||
5+t2H3P3plQUFp/DdDSZrvhcBiTsKiNgqThEtkKtfSCvIvBf4a2W/4TJsW6MzxCm
|
||||
2KQDuK/UqM4Y+APKWN/N6Lln2VWNbNyBkWuuRVKFatccyJyJnSjxeqW7cwKBgGyN
|
||||
idCYPIrwROAHLItXKvOWE5t0ABRq3TsZC2RkdA/b5HCPs4pclexcEriRjvXrK/Yt
|
||||
MH94Ve8b+UftSUQ4ytjBMS6MrLg87y0YDhLwxv8NKUq65DXAUOW+8JsAmmWQOqY3
|
||||
MK+m1BT4TMklgVoN3w3sPsKIsSJ/jLz5cv/kYweFAoGAG4iWU1378tI2Ts/Fngsv
|
||||
7eoWhoda77Y9D0Yoy20aN9VdMHzIYCBOubtRPEuwgaReNwbUBWap01J63yY/fF3K
|
||||
8PTz6covjoOJqxQJOvM7nM0CsJawG9ccw3YXyd9KgRIdSt6ooEhb7N8W2EXYoKl3
|
||||
g1i2t41Q/SC3HUGC5mJjpO8=
|
||||
-----END PRIVATE KEY-----
|
353
venv/Lib/site-packages/tornado/test/testing_test.py
Normal file
353
venv/Lib/site-packages/tornado/test/testing_test.py
Normal file
|
@ -0,0 +1,353 @@
|
|||
from tornado import gen, ioloop
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.locks import Event
|
||||
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, bind_unused_port, gen_test
|
||||
from tornado.web import Application
|
||||
import asyncio
|
||||
import contextlib
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
import traceback
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def set_environ(name, value):
|
||||
old_value = os.environ.get(name)
|
||||
os.environ[name] = value
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if old_value is None:
|
||||
del os.environ[name]
|
||||
else:
|
||||
os.environ[name] = old_value
|
||||
|
||||
|
||||
class AsyncTestCaseTest(AsyncTestCase):
|
||||
def test_wait_timeout(self):
|
||||
time = self.io_loop.time
|
||||
|
||||
# Accept default 5-second timeout, no error
|
||||
self.io_loop.add_timeout(time() + 0.01, self.stop)
|
||||
self.wait()
|
||||
|
||||
# Timeout passed to wait()
|
||||
self.io_loop.add_timeout(time() + 1, self.stop)
|
||||
with self.assertRaises(self.failureException):
|
||||
self.wait(timeout=0.01)
|
||||
|
||||
# Timeout set with environment variable
|
||||
self.io_loop.add_timeout(time() + 1, self.stop)
|
||||
with set_environ("ASYNC_TEST_TIMEOUT", "0.01"):
|
||||
with self.assertRaises(self.failureException):
|
||||
self.wait()
|
||||
|
||||
def test_subsequent_wait_calls(self):
|
||||
"""
|
||||
This test makes sure that a second call to wait()
|
||||
clears the first timeout.
|
||||
"""
|
||||
# The first wait ends with time left on the clock
|
||||
self.io_loop.add_timeout(self.io_loop.time() + 0.00, self.stop)
|
||||
self.wait(timeout=0.1)
|
||||
# The second wait has enough time for itself but would fail if the
|
||||
# first wait's deadline were still in effect.
|
||||
self.io_loop.add_timeout(self.io_loop.time() + 0.2, self.stop)
|
||||
self.wait(timeout=0.4)
|
||||
|
||||
|
||||
class LeakTest(AsyncTestCase):
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
# Trigger a gc to make warnings more deterministic.
|
||||
gc.collect()
|
||||
|
||||
def test_leaked_coroutine(self):
|
||||
# This test verifies that "leaked" coroutines are shut down
|
||||
# without triggering warnings like "task was destroyed but it
|
||||
# is pending". If this test were to fail, it would fail
|
||||
# because runtests.py detected unexpected output to stderr.
|
||||
event = Event()
|
||||
|
||||
async def callback():
|
||||
try:
|
||||
await event.wait()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
self.io_loop.add_callback(callback)
|
||||
self.io_loop.add_callback(self.stop)
|
||||
self.wait()
|
||||
|
||||
|
||||
class AsyncHTTPTestCaseTest(AsyncHTTPTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Bind a second port.
|
||||
sock, port = bind_unused_port()
|
||||
app = Application()
|
||||
server = HTTPServer(app, **self.get_httpserver_options())
|
||||
server.add_socket(sock)
|
||||
self.second_port = port
|
||||
self.second_server = server
|
||||
|
||||
def get_app(self):
|
||||
return Application()
|
||||
|
||||
def test_fetch_segment(self):
|
||||
path = "/path"
|
||||
response = self.fetch(path)
|
||||
self.assertEqual(response.request.url, self.get_url(path))
|
||||
|
||||
def test_fetch_full_http_url(self):
|
||||
# Ensure that self.fetch() recognizes absolute urls and does
|
||||
# not transform them into references to our main test server.
|
||||
path = "http://localhost:%d/path" % self.second_port
|
||||
|
||||
response = self.fetch(path)
|
||||
self.assertEqual(response.request.url, path)
|
||||
|
||||
def tearDown(self):
|
||||
self.second_server.stop()
|
||||
super().tearDown()
|
||||
|
||||
|
||||
class AsyncTestCaseWrapperTest(unittest.TestCase):
|
||||
def test_undecorated_generator(self):
|
||||
class Test(AsyncTestCase):
|
||||
def test_gen(self):
|
||||
yield
|
||||
|
||||
test = Test("test_gen")
|
||||
result = unittest.TestResult()
|
||||
test.run(result)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertIn("should be decorated", result.errors[0][1])
|
||||
|
||||
@unittest.skipIf(
|
||||
platform.python_implementation() == "PyPy",
|
||||
"pypy destructor warnings cannot be silenced",
|
||||
)
|
||||
def test_undecorated_coroutine(self):
|
||||
class Test(AsyncTestCase):
|
||||
async def test_coro(self):
|
||||
pass
|
||||
|
||||
test = Test("test_coro")
|
||||
result = unittest.TestResult()
|
||||
|
||||
# Silence "RuntimeWarning: coroutine 'test_coro' was never awaited".
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
test.run(result)
|
||||
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertIn("should be decorated", result.errors[0][1])
|
||||
|
||||
def test_undecorated_generator_with_skip(self):
|
||||
class Test(AsyncTestCase):
|
||||
@unittest.skip("don't run this")
|
||||
def test_gen(self):
|
||||
yield
|
||||
|
||||
test = Test("test_gen")
|
||||
result = unittest.TestResult()
|
||||
test.run(result)
|
||||
self.assertEqual(len(result.errors), 0)
|
||||
self.assertEqual(len(result.skipped), 1)
|
||||
|
||||
def test_other_return(self):
|
||||
class Test(AsyncTestCase):
|
||||
def test_other_return(self):
|
||||
return 42
|
||||
|
||||
test = Test("test_other_return")
|
||||
result = unittest.TestResult()
|
||||
test.run(result)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertIn("Return value from test method ignored", result.errors[0][1])
|
||||
|
||||
|
||||
class SetUpTearDownTest(unittest.TestCase):
|
||||
def test_set_up_tear_down(self):
|
||||
"""
|
||||
This test makes sure that AsyncTestCase calls super methods for
|
||||
setUp and tearDown.
|
||||
|
||||
InheritBoth is a subclass of both AsyncTestCase and
|
||||
SetUpTearDown, with the ordering so that the super of
|
||||
AsyncTestCase will be SetUpTearDown.
|
||||
"""
|
||||
events = []
|
||||
result = unittest.TestResult()
|
||||
|
||||
class SetUpTearDown(unittest.TestCase):
|
||||
def setUp(self):
|
||||
events.append("setUp")
|
||||
|
||||
def tearDown(self):
|
||||
events.append("tearDown")
|
||||
|
||||
class InheritBoth(AsyncTestCase, SetUpTearDown):
|
||||
def test(self):
|
||||
events.append("test")
|
||||
|
||||
InheritBoth("test").run(result)
|
||||
expected = ["setUp", "test", "tearDown"]
|
||||
self.assertEqual(expected, events)
|
||||
|
||||
|
||||
class AsyncHTTPTestCaseSetUpTearDownTest(unittest.TestCase):
|
||||
def test_tear_down_releases_app_and_http_server(self):
|
||||
result = unittest.TestResult()
|
||||
|
||||
class SetUpTearDown(AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return Application()
|
||||
|
||||
def test(self):
|
||||
self.assertTrue(hasattr(self, "_app"))
|
||||
self.assertTrue(hasattr(self, "http_server"))
|
||||
|
||||
test = SetUpTearDown("test")
|
||||
test.run(result)
|
||||
self.assertFalse(hasattr(test, "_app"))
|
||||
self.assertFalse(hasattr(test, "http_server"))
|
||||
|
||||
|
||||
class GenTest(AsyncTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.finished = False
|
||||
|
||||
def tearDown(self):
|
||||
self.assertTrue(self.finished)
|
||||
super().tearDown()
|
||||
|
||||
@gen_test
|
||||
def test_sync(self):
|
||||
self.finished = True
|
||||
|
||||
@gen_test
|
||||
def test_async(self):
|
||||
yield gen.moment
|
||||
self.finished = True
|
||||
|
||||
def test_timeout(self):
|
||||
# Set a short timeout and exceed it.
|
||||
@gen_test(timeout=0.1)
|
||||
def test(self):
|
||||
yield gen.sleep(1)
|
||||
|
||||
# This can't use assertRaises because we need to inspect the
|
||||
# exc_info triple (and not just the exception object)
|
||||
try:
|
||||
test(self)
|
||||
self.fail("did not get expected exception")
|
||||
except ioloop.TimeoutError:
|
||||
# The stack trace should blame the add_timeout line, not just
|
||||
# unrelated IOLoop/testing internals.
|
||||
self.assertIn("gen.sleep(1)", traceback.format_exc())
|
||||
|
||||
self.finished = True
|
||||
|
||||
def test_no_timeout(self):
|
||||
# A test that does not exceed its timeout should succeed.
|
||||
@gen_test(timeout=1)
|
||||
def test(self):
|
||||
yield gen.sleep(0.1)
|
||||
|
||||
test(self)
|
||||
self.finished = True
|
||||
|
||||
def test_timeout_environment_variable(self):
|
||||
@gen_test(timeout=0.5)
|
||||
def test_long_timeout(self):
|
||||
yield gen.sleep(0.25)
|
||||
|
||||
# Uses provided timeout of 0.5 seconds, doesn't time out.
|
||||
with set_environ("ASYNC_TEST_TIMEOUT", "0.1"):
|
||||
test_long_timeout(self)
|
||||
|
||||
self.finished = True
|
||||
|
||||
def test_no_timeout_environment_variable(self):
|
||||
@gen_test(timeout=0.01)
|
||||
def test_short_timeout(self):
|
||||
yield gen.sleep(1)
|
||||
|
||||
# Uses environment-variable timeout of 0.1, times out.
|
||||
with set_environ("ASYNC_TEST_TIMEOUT", "0.1"):
|
||||
with self.assertRaises(ioloop.TimeoutError):
|
||||
test_short_timeout(self)
|
||||
|
||||
self.finished = True
|
||||
|
||||
def test_with_method_args(self):
|
||||
@gen_test
|
||||
def test_with_args(self, *args):
|
||||
self.assertEqual(args, ("test",))
|
||||
yield gen.moment
|
||||
|
||||
test_with_args(self, "test")
|
||||
self.finished = True
|
||||
|
||||
def test_with_method_kwargs(self):
|
||||
@gen_test
|
||||
def test_with_kwargs(self, **kwargs):
|
||||
self.assertDictEqual(kwargs, {"test": "test"})
|
||||
yield gen.moment
|
||||
|
||||
test_with_kwargs(self, test="test")
|
||||
self.finished = True
|
||||
|
||||
def test_native_coroutine(self):
|
||||
@gen_test
|
||||
async def test(self):
|
||||
self.finished = True
|
||||
|
||||
test(self)
|
||||
|
||||
def test_native_coroutine_timeout(self):
|
||||
# Set a short timeout and exceed it.
|
||||
@gen_test(timeout=0.1)
|
||||
async def test(self):
|
||||
await gen.sleep(1)
|
||||
|
||||
try:
|
||||
test(self)
|
||||
self.fail("did not get expected exception")
|
||||
except ioloop.TimeoutError:
|
||||
self.finished = True
|
||||
|
||||
|
||||
class GetNewIOLoopTest(AsyncTestCase):
|
||||
def get_new_ioloop(self):
|
||||
# Use the current loop instead of creating a new one here.
|
||||
return ioloop.IOLoop.current()
|
||||
|
||||
def setUp(self):
|
||||
# This simulates the effect of an asyncio test harness like
|
||||
# pytest-asyncio.
|
||||
self.orig_loop = asyncio.get_event_loop()
|
||||
self.new_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.new_loop)
|
||||
super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
# AsyncTestCase must not affect the existing asyncio loop.
|
||||
self.assertFalse(asyncio.get_event_loop().is_closed())
|
||||
asyncio.set_event_loop(self.orig_loop)
|
||||
self.new_loop.close()
|
||||
|
||||
def test_loop(self):
|
||||
self.assertIs(self.io_loop.asyncio_loop, self.new_loop) # type: ignore
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
247
venv/Lib/site-packages/tornado/test/twisted_test.py
Normal file
247
venv/Lib/site-packages/tornado/test/twisted_test.py
Normal file
|
@ -0,0 +1,247 @@
|
|||
# Author: Ovidiu Predescu
|
||||
# Date: July 2011
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from tornado.escape import utf8
|
||||
from tornado import gen
|
||||
from tornado.httpclient import AsyncHTTPClient
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.testing import bind_unused_port, AsyncTestCase, gen_test
|
||||
from tornado.web import RequestHandler, Application
|
||||
|
||||
try:
|
||||
from twisted.internet.defer import ( # type: ignore
|
||||
Deferred,
|
||||
inlineCallbacks,
|
||||
returnValue,
|
||||
)
|
||||
from twisted.internet.protocol import Protocol # type: ignore
|
||||
from twisted.internet.asyncioreactor import AsyncioSelectorReactor # type: ignore
|
||||
from twisted.web.client import Agent, readBody # type: ignore
|
||||
from twisted.web.resource import Resource # type: ignore
|
||||
from twisted.web.server import Site # type: ignore
|
||||
|
||||
have_twisted = True
|
||||
except ImportError:
|
||||
have_twisted = False
|
||||
else:
|
||||
# Not used directly but needed for `yield deferred` to work.
|
||||
import tornado.platform.twisted # noqa: F401
|
||||
|
||||
skipIfNoTwisted = unittest.skipUnless(have_twisted, "twisted module not present")
|
||||
|
||||
|
||||
def save_signal_handlers():
|
||||
saved = {}
|
||||
signals = [signal.SIGINT, signal.SIGTERM]
|
||||
if hasattr(signal, "SIGCHLD"):
|
||||
signals.append(signal.SIGCHLD)
|
||||
for sig in signals:
|
||||
saved[sig] = signal.getsignal(sig)
|
||||
if "twisted" in repr(saved):
|
||||
# This indicates we're not cleaning up after ourselves properly.
|
||||
raise Exception("twisted signal handlers already installed")
|
||||
return saved
|
||||
|
||||
|
||||
def restore_signal_handlers(saved):
|
||||
for sig, handler in saved.items():
|
||||
signal.signal(sig, handler)
|
||||
|
||||
|
||||
# Test various combinations of twisted and tornado http servers,
|
||||
# http clients, and event loop interfaces.
|
||||
|
||||
|
||||
@skipIfNoTwisted
|
||||
class CompatibilityTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.saved_signals = save_signal_handlers()
|
||||
self.saved_policy = asyncio.get_event_loop_policy()
|
||||
if hasattr(asyncio, "WindowsSelectorEventLoopPolicy"):
|
||||
# Twisted requires a selector event loop, even if Tornado is
|
||||
# doing its own tricks in AsyncIOLoop to support proactors.
|
||||
# Setting an AddThreadSelectorEventLoop exposes various edge
|
||||
# cases so just use a regular selector.
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # type: ignore
|
||||
self.io_loop = IOLoop()
|
||||
self.io_loop.make_current()
|
||||
self.reactor = AsyncioSelectorReactor()
|
||||
|
||||
def tearDown(self):
|
||||
self.reactor.disconnectAll()
|
||||
self.io_loop.clear_current()
|
||||
self.io_loop.close(all_fds=True)
|
||||
asyncio.set_event_loop_policy(self.saved_policy)
|
||||
restore_signal_handlers(self.saved_signals)
|
||||
|
||||
def start_twisted_server(self):
|
||||
class HelloResource(Resource):
|
||||
isLeaf = True
|
||||
|
||||
def render_GET(self, request):
|
||||
return b"Hello from twisted!"
|
||||
|
||||
site = Site(HelloResource())
|
||||
port = self.reactor.listenTCP(0, site, interface="127.0.0.1")
|
||||
self.twisted_port = port.getHost().port
|
||||
|
||||
def start_tornado_server(self):
|
||||
class HelloHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("Hello from tornado!")
|
||||
|
||||
app = Application([("/", HelloHandler)], log_function=lambda x: None)
|
||||
server = HTTPServer(app)
|
||||
sock, self.tornado_port = bind_unused_port()
|
||||
server.add_sockets([sock])
|
||||
|
||||
def run_reactor(self):
|
||||
# In theory, we can run the event loop through Tornado,
|
||||
# Twisted, or asyncio interfaces. However, since we're trying
|
||||
# to avoid installing anything as the global event loop, only
|
||||
# the twisted interface gets everything wired up correectly
|
||||
# without extra hacks. This method is a part of a
|
||||
# no-longer-used generalization that allowed us to test
|
||||
# different combinations.
|
||||
self.stop_loop = self.reactor.stop
|
||||
self.stop = self.reactor.stop
|
||||
self.reactor.run()
|
||||
|
||||
def tornado_fetch(self, url, runner):
|
||||
client = AsyncHTTPClient()
|
||||
fut = asyncio.ensure_future(client.fetch(url))
|
||||
fut.add_done_callback(lambda f: self.stop_loop())
|
||||
runner()
|
||||
return fut.result()
|
||||
|
||||
def twisted_fetch(self, url, runner):
|
||||
# http://twistedmatrix.com/documents/current/web/howto/client.html
|
||||
chunks = []
|
||||
client = Agent(self.reactor)
|
||||
d = client.request(b"GET", utf8(url))
|
||||
|
||||
class Accumulator(Protocol):
|
||||
def __init__(self, finished):
|
||||
self.finished = finished
|
||||
|
||||
def dataReceived(self, data):
|
||||
chunks.append(data)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.finished.callback(None)
|
||||
|
||||
def callback(response):
|
||||
finished = Deferred()
|
||||
response.deliverBody(Accumulator(finished))
|
||||
return finished
|
||||
|
||||
d.addCallback(callback)
|
||||
|
||||
def shutdown(failure):
|
||||
if hasattr(self, "stop_loop"):
|
||||
self.stop_loop()
|
||||
elif failure is not None:
|
||||
# loop hasn't been initialized yet; try our best to
|
||||
# get an error message out. (the runner() interaction
|
||||
# should probably be refactored).
|
||||
try:
|
||||
failure.raiseException()
|
||||
except:
|
||||
logging.error("exception before starting loop", exc_info=True)
|
||||
|
||||
d.addBoth(shutdown)
|
||||
runner()
|
||||
self.assertTrue(chunks)
|
||||
return b"".join(chunks)
|
||||
|
||||
def twisted_coroutine_fetch(self, url, runner):
|
||||
body = [None]
|
||||
|
||||
@gen.coroutine
|
||||
def f():
|
||||
# This is simpler than the non-coroutine version, but it cheats
|
||||
# by reading the body in one blob instead of streaming it with
|
||||
# a Protocol.
|
||||
client = Agent(self.reactor)
|
||||
response = yield client.request(b"GET", utf8(url))
|
||||
with warnings.catch_warnings():
|
||||
# readBody has a buggy DeprecationWarning in Twisted 15.0:
|
||||
# https://twistedmatrix.com/trac/changeset/43379
|
||||
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||
body[0] = yield readBody(response)
|
||||
self.stop_loop()
|
||||
|
||||
self.io_loop.add_callback(f)
|
||||
runner()
|
||||
return body[0]
|
||||
|
||||
def testTwistedServerTornadoClientReactor(self):
|
||||
self.start_twisted_server()
|
||||
response = self.tornado_fetch(
|
||||
"http://127.0.0.1:%d" % self.twisted_port, self.run_reactor
|
||||
)
|
||||
self.assertEqual(response.body, b"Hello from twisted!")
|
||||
|
||||
def testTornadoServerTwistedClientReactor(self):
|
||||
self.start_tornado_server()
|
||||
response = self.twisted_fetch(
|
||||
"http://127.0.0.1:%d" % self.tornado_port, self.run_reactor
|
||||
)
|
||||
self.assertEqual(response, b"Hello from tornado!")
|
||||
|
||||
def testTornadoServerTwistedCoroutineClientReactor(self):
|
||||
self.start_tornado_server()
|
||||
response = self.twisted_coroutine_fetch(
|
||||
"http://127.0.0.1:%d" % self.tornado_port, self.run_reactor
|
||||
)
|
||||
self.assertEqual(response, b"Hello from tornado!")
|
||||
|
||||
|
||||
@skipIfNoTwisted
|
||||
class ConvertDeferredTest(AsyncTestCase):
|
||||
@gen_test
|
||||
def test_success(self):
|
||||
@inlineCallbacks
|
||||
def fn():
|
||||
if False:
|
||||
# inlineCallbacks doesn't work with regular functions;
|
||||
# must have a yield even if it's unreachable.
|
||||
yield
|
||||
returnValue(42)
|
||||
|
||||
res = yield fn()
|
||||
self.assertEqual(res, 42)
|
||||
|
||||
@gen_test
|
||||
def test_failure(self):
|
||||
@inlineCallbacks
|
||||
def fn():
|
||||
if False:
|
||||
yield
|
||||
1 / 0
|
||||
|
||||
with self.assertRaises(ZeroDivisionError):
|
||||
yield fn()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
114
venv/Lib/site-packages/tornado/test/util.py
Normal file
114
venv/Lib/site-packages/tornado/test/util.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
import contextlib
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
import textwrap
|
||||
import typing # noqa: F401
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from tornado.testing import bind_unused_port
|
||||
|
||||
skipIfNonUnix = unittest.skipIf(
|
||||
os.name != "posix" or sys.platform == "cygwin", "non-unix platform"
|
||||
)
|
||||
|
||||
# travis-ci.org runs our tests in an overworked virtual machine, which makes
|
||||
# timing-related tests unreliable.
|
||||
skipOnTravis = unittest.skipIf(
|
||||
"TRAVIS" in os.environ, "timing tests unreliable on travis"
|
||||
)
|
||||
|
||||
# Set the environment variable NO_NETWORK=1 to disable any tests that
|
||||
# depend on an external network.
|
||||
skipIfNoNetwork = unittest.skipIf("NO_NETWORK" in os.environ, "network access disabled")
|
||||
|
||||
skipNotCPython = unittest.skipIf(
|
||||
platform.python_implementation() != "CPython", "Not CPython implementation"
|
||||
)
|
||||
|
||||
# Used for tests affected by
|
||||
# https://bitbucket.org/pypy/pypy/issues/2616/incomplete-error-handling-in
|
||||
# TODO: remove this after pypy3 5.8 is obsolete.
|
||||
skipPypy3V58 = unittest.skipIf(
|
||||
platform.python_implementation() == "PyPy"
|
||||
and sys.version_info > (3,)
|
||||
and sys.pypy_version_info < (5, 9), # type: ignore
|
||||
"pypy3 5.8 has buggy ssl module",
|
||||
)
|
||||
|
||||
|
||||
def _detect_ipv6():
|
||||
if not socket.has_ipv6:
|
||||
# socket.has_ipv6 check reports whether ipv6 was present at compile
|
||||
# time. It's usually true even when ipv6 doesn't work for other reasons.
|
||||
return False
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET6)
|
||||
sock.bind(("::1", 0))
|
||||
except socket.error:
|
||||
return False
|
||||
finally:
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
return True
|
||||
|
||||
|
||||
skipIfNoIPv6 = unittest.skipIf(not _detect_ipv6(), "ipv6 support not present")
|
||||
|
||||
|
||||
def refusing_port():
|
||||
"""Returns a local port number that will refuse all connections.
|
||||
|
||||
Return value is (cleanup_func, port); the cleanup function
|
||||
must be called to free the port to be reused.
|
||||
"""
|
||||
# On travis-ci, port numbers are reassigned frequently. To avoid
|
||||
# collisions with other tests, we use an open client-side socket's
|
||||
# ephemeral port number to ensure that nothing can listen on that
|
||||
# port.
|
||||
server_socket, port = bind_unused_port()
|
||||
server_socket.setblocking(True)
|
||||
client_socket = socket.socket()
|
||||
client_socket.connect(("127.0.0.1", port))
|
||||
conn, client_addr = server_socket.accept()
|
||||
conn.close()
|
||||
server_socket.close()
|
||||
return (client_socket.close, client_addr[1])
|
||||
|
||||
|
||||
def exec_test(caller_globals, caller_locals, s):
|
||||
"""Execute ``s`` in a given context and return the result namespace.
|
||||
|
||||
Used to define functions for tests in particular python
|
||||
versions that would be syntax errors in older versions.
|
||||
"""
|
||||
# Flatten the real global and local namespace into our fake
|
||||
# globals: it's all global from the perspective of code defined
|
||||
# in s.
|
||||
global_namespace = dict(caller_globals, **caller_locals) # type: ignore
|
||||
local_namespace = {} # type: typing.Dict[str, typing.Any]
|
||||
exec(textwrap.dedent(s), global_namespace, local_namespace)
|
||||
return local_namespace
|
||||
|
||||
|
||||
def subTest(test, *args, **kwargs):
|
||||
"""Compatibility shim for unittest.TestCase.subTest.
|
||||
|
||||
Usage: ``with tornado.test.util.subTest(self, x=x):``
|
||||
"""
|
||||
try:
|
||||
subTest = test.subTest # py34+
|
||||
except AttributeError:
|
||||
subTest = contextlib.contextmanager(lambda *a, **kw: (yield))
|
||||
return subTest(*args, **kwargs)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignore_deprecation():
|
||||
"""Context manager to ignore deprecation warnings."""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
yield
|
308
venv/Lib/site-packages/tornado/test/util_test.py
Normal file
308
venv/Lib/site-packages/tornado/test/util_test.py
Normal file
|
@ -0,0 +1,308 @@
|
|||
from io import StringIO
|
||||
import re
|
||||
import sys
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
import tornado.escape
|
||||
from tornado.escape import utf8
|
||||
from tornado.util import (
|
||||
raise_exc_info,
|
||||
Configurable,
|
||||
exec_in,
|
||||
ArgReplacer,
|
||||
timedelta_to_seconds,
|
||||
import_object,
|
||||
re_unescape,
|
||||
is_finalizing,
|
||||
)
|
||||
|
||||
import typing
|
||||
from typing import cast
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import Dict, Any # noqa: F401
|
||||
|
||||
|
||||
class RaiseExcInfoTest(unittest.TestCase):
|
||||
def test_two_arg_exception(self):
|
||||
# This test would fail on python 3 if raise_exc_info were simply
|
||||
# a three-argument raise statement, because TwoArgException
|
||||
# doesn't have a "copy constructor"
|
||||
class TwoArgException(Exception):
|
||||
def __init__(self, a, b):
|
||||
super().__init__()
|
||||
self.a, self.b = a, b
|
||||
|
||||
try:
|
||||
raise TwoArgException(1, 2)
|
||||
except TwoArgException:
|
||||
exc_info = sys.exc_info()
|
||||
try:
|
||||
raise_exc_info(exc_info)
|
||||
self.fail("didn't get expected exception")
|
||||
except TwoArgException as e:
|
||||
self.assertIs(e, exc_info[1])
|
||||
|
||||
|
||||
class TestConfigurable(Configurable):
|
||||
@classmethod
|
||||
def configurable_base(cls):
|
||||
return TestConfigurable
|
||||
|
||||
@classmethod
|
||||
def configurable_default(cls):
|
||||
return TestConfig1
|
||||
|
||||
|
||||
class TestConfig1(TestConfigurable):
|
||||
def initialize(self, pos_arg=None, a=None):
|
||||
self.a = a
|
||||
self.pos_arg = pos_arg
|
||||
|
||||
|
||||
class TestConfig2(TestConfigurable):
|
||||
def initialize(self, pos_arg=None, b=None):
|
||||
self.b = b
|
||||
self.pos_arg = pos_arg
|
||||
|
||||
|
||||
class TestConfig3(TestConfigurable):
|
||||
# TestConfig3 is a configuration option that is itself configurable.
|
||||
@classmethod
|
||||
def configurable_base(cls):
|
||||
return TestConfig3
|
||||
|
||||
@classmethod
|
||||
def configurable_default(cls):
|
||||
return TestConfig3A
|
||||
|
||||
|
||||
class TestConfig3A(TestConfig3):
|
||||
def initialize(self, a=None):
|
||||
self.a = a
|
||||
|
||||
|
||||
class TestConfig3B(TestConfig3):
|
||||
def initialize(self, b=None):
|
||||
self.b = b
|
||||
|
||||
|
||||
class ConfigurableTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.saved = TestConfigurable._save_configuration()
|
||||
self.saved3 = TestConfig3._save_configuration()
|
||||
|
||||
def tearDown(self):
|
||||
TestConfigurable._restore_configuration(self.saved)
|
||||
TestConfig3._restore_configuration(self.saved3)
|
||||
|
||||
def checkSubclasses(self):
|
||||
# no matter how the class is configured, it should always be
|
||||
# possible to instantiate the subclasses directly
|
||||
self.assertIsInstance(TestConfig1(), TestConfig1)
|
||||
self.assertIsInstance(TestConfig2(), TestConfig2)
|
||||
|
||||
obj = TestConfig1(a=1)
|
||||
self.assertEqual(obj.a, 1)
|
||||
obj2 = TestConfig2(b=2)
|
||||
self.assertEqual(obj2.b, 2)
|
||||
|
||||
def test_default(self):
|
||||
# In these tests we combine a typing.cast to satisfy mypy with
|
||||
# a runtime type-assertion. Without the cast, mypy would only
|
||||
# let us access attributes of the base class.
|
||||
obj = cast(TestConfig1, TestConfigurable())
|
||||
self.assertIsInstance(obj, TestConfig1)
|
||||
self.assertIs(obj.a, None)
|
||||
|
||||
obj = cast(TestConfig1, TestConfigurable(a=1))
|
||||
self.assertIsInstance(obj, TestConfig1)
|
||||
self.assertEqual(obj.a, 1)
|
||||
|
||||
self.checkSubclasses()
|
||||
|
||||
def test_config_class(self):
|
||||
TestConfigurable.configure(TestConfig2)
|
||||
obj = cast(TestConfig2, TestConfigurable())
|
||||
self.assertIsInstance(obj, TestConfig2)
|
||||
self.assertIs(obj.b, None)
|
||||
|
||||
obj = cast(TestConfig2, TestConfigurable(b=2))
|
||||
self.assertIsInstance(obj, TestConfig2)
|
||||
self.assertEqual(obj.b, 2)
|
||||
|
||||
self.checkSubclasses()
|
||||
|
||||
def test_config_str(self):
|
||||
TestConfigurable.configure("tornado.test.util_test.TestConfig2")
|
||||
obj = cast(TestConfig2, TestConfigurable())
|
||||
self.assertIsInstance(obj, TestConfig2)
|
||||
self.assertIs(obj.b, None)
|
||||
|
||||
obj = cast(TestConfig2, TestConfigurable(b=2))
|
||||
self.assertIsInstance(obj, TestConfig2)
|
||||
self.assertEqual(obj.b, 2)
|
||||
|
||||
self.checkSubclasses()
|
||||
|
||||
def test_config_args(self):
|
||||
TestConfigurable.configure(None, a=3)
|
||||
obj = cast(TestConfig1, TestConfigurable())
|
||||
self.assertIsInstance(obj, TestConfig1)
|
||||
self.assertEqual(obj.a, 3)
|
||||
|
||||
obj = cast(TestConfig1, TestConfigurable(42, a=4))
|
||||
self.assertIsInstance(obj, TestConfig1)
|
||||
self.assertEqual(obj.a, 4)
|
||||
self.assertEqual(obj.pos_arg, 42)
|
||||
|
||||
self.checkSubclasses()
|
||||
# args bound in configure don't apply when using the subclass directly
|
||||
obj = TestConfig1()
|
||||
self.assertIs(obj.a, None)
|
||||
|
||||
def test_config_class_args(self):
|
||||
TestConfigurable.configure(TestConfig2, b=5)
|
||||
obj = cast(TestConfig2, TestConfigurable())
|
||||
self.assertIsInstance(obj, TestConfig2)
|
||||
self.assertEqual(obj.b, 5)
|
||||
|
||||
obj = cast(TestConfig2, TestConfigurable(42, b=6))
|
||||
self.assertIsInstance(obj, TestConfig2)
|
||||
self.assertEqual(obj.b, 6)
|
||||
self.assertEqual(obj.pos_arg, 42)
|
||||
|
||||
self.checkSubclasses()
|
||||
# args bound in configure don't apply when using the subclass directly
|
||||
obj = TestConfig2()
|
||||
self.assertIs(obj.b, None)
|
||||
|
||||
def test_config_multi_level(self):
|
||||
TestConfigurable.configure(TestConfig3, a=1)
|
||||
obj = cast(TestConfig3A, TestConfigurable())
|
||||
self.assertIsInstance(obj, TestConfig3A)
|
||||
self.assertEqual(obj.a, 1)
|
||||
|
||||
TestConfigurable.configure(TestConfig3)
|
||||
TestConfig3.configure(TestConfig3B, b=2)
|
||||
obj2 = cast(TestConfig3B, TestConfigurable())
|
||||
self.assertIsInstance(obj2, TestConfig3B)
|
||||
self.assertEqual(obj2.b, 2)
|
||||
|
||||
def test_config_inner_level(self):
|
||||
# The inner level can be used even when the outer level
|
||||
# doesn't point to it.
|
||||
obj = TestConfig3()
|
||||
self.assertIsInstance(obj, TestConfig3A)
|
||||
|
||||
TestConfig3.configure(TestConfig3B)
|
||||
obj = TestConfig3()
|
||||
self.assertIsInstance(obj, TestConfig3B)
|
||||
|
||||
# Configuring the base doesn't configure the inner.
|
||||
obj2 = TestConfigurable()
|
||||
self.assertIsInstance(obj2, TestConfig1)
|
||||
TestConfigurable.configure(TestConfig2)
|
||||
|
||||
obj3 = TestConfigurable()
|
||||
self.assertIsInstance(obj3, TestConfig2)
|
||||
|
||||
obj = TestConfig3()
|
||||
self.assertIsInstance(obj, TestConfig3B)
|
||||
|
||||
|
||||
class UnicodeLiteralTest(unittest.TestCase):
|
||||
def test_unicode_escapes(self):
|
||||
self.assertEqual(utf8(u"\u00e9"), b"\xc3\xa9")
|
||||
|
||||
|
||||
class ExecInTest(unittest.TestCase):
|
||||
# TODO(bdarnell): make a version of this test for one of the new
|
||||
# future imports available in python 3.
|
||||
@unittest.skip("no testable future imports")
|
||||
def test_no_inherit_future(self):
|
||||
# This file has from __future__ import print_function...
|
||||
f = StringIO()
|
||||
print("hello", file=f)
|
||||
# ...but the template doesn't
|
||||
exec_in('print >> f, "world"', dict(f=f))
|
||||
self.assertEqual(f.getvalue(), "hello\nworld\n")
|
||||
|
||||
|
||||
class ArgReplacerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
def function(x, y, callback=None, z=None):
|
||||
pass
|
||||
|
||||
self.replacer = ArgReplacer(function, "callback")
|
||||
|
||||
def test_omitted(self):
|
||||
args = (1, 2)
|
||||
kwargs = dict() # type: Dict[str, Any]
|
||||
self.assertIs(self.replacer.get_old_value(args, kwargs), None)
|
||||
self.assertEqual(
|
||||
self.replacer.replace("new", args, kwargs),
|
||||
(None, (1, 2), dict(callback="new")),
|
||||
)
|
||||
|
||||
def test_position(self):
|
||||
args = (1, 2, "old", 3)
|
||||
kwargs = dict() # type: Dict[str, Any]
|
||||
self.assertEqual(self.replacer.get_old_value(args, kwargs), "old")
|
||||
self.assertEqual(
|
||||
self.replacer.replace("new", args, kwargs),
|
||||
("old", [1, 2, "new", 3], dict()),
|
||||
)
|
||||
|
||||
def test_keyword(self):
|
||||
args = (1,)
|
||||
kwargs = dict(y=2, callback="old", z=3)
|
||||
self.assertEqual(self.replacer.get_old_value(args, kwargs), "old")
|
||||
self.assertEqual(
|
||||
self.replacer.replace("new", args, kwargs),
|
||||
("old", (1,), dict(y=2, callback="new", z=3)),
|
||||
)
|
||||
|
||||
|
||||
class TimedeltaToSecondsTest(unittest.TestCase):
|
||||
def test_timedelta_to_seconds(self):
|
||||
time_delta = datetime.timedelta(hours=1)
|
||||
self.assertEqual(timedelta_to_seconds(time_delta), 3600.0)
|
||||
|
||||
|
||||
class ImportObjectTest(unittest.TestCase):
|
||||
def test_import_member(self):
|
||||
self.assertIs(import_object("tornado.escape.utf8"), utf8)
|
||||
|
||||
def test_import_member_unicode(self):
|
||||
self.assertIs(import_object(u"tornado.escape.utf8"), utf8)
|
||||
|
||||
def test_import_module(self):
|
||||
self.assertIs(import_object("tornado.escape"), tornado.escape)
|
||||
|
||||
def test_import_module_unicode(self):
|
||||
# The internal implementation of __import__ differs depending on
|
||||
# whether the thing being imported is a module or not.
|
||||
# This variant requires a byte string in python 2.
|
||||
self.assertIs(import_object(u"tornado.escape"), tornado.escape)
|
||||
|
||||
|
||||
class ReUnescapeTest(unittest.TestCase):
|
||||
def test_re_unescape(self):
|
||||
test_strings = ("/favicon.ico", "index.html", "Hello, World!", "!$@#%;")
|
||||
for string in test_strings:
|
||||
self.assertEqual(string, re_unescape(re.escape(string)))
|
||||
|
||||
def test_re_unescape_raises_error_on_invalid_input(self):
|
||||
with self.assertRaises(ValueError):
|
||||
re_unescape("\\d")
|
||||
with self.assertRaises(ValueError):
|
||||
re_unescape("\\b")
|
||||
with self.assertRaises(ValueError):
|
||||
re_unescape("\\Z")
|
||||
|
||||
|
||||
class IsFinalizingTest(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
self.assertFalse(is_finalizing())
|
3156
venv/Lib/site-packages/tornado/test/web_test.py
Normal file
3156
venv/Lib/site-packages/tornado/test/web_test.py
Normal file
File diff suppressed because it is too large
Load diff
840
venv/Lib/site-packages/tornado/test/websocket_test.py
Normal file
840
venv/Lib/site-packages/tornado/test/websocket_test.py
Normal file
|
@ -0,0 +1,840 @@
|
|||
import asyncio
|
||||
import functools
|
||||
import traceback
|
||||
import typing
|
||||
import unittest
|
||||
|
||||
from tornado.concurrent import Future
|
||||
from tornado import gen
|
||||
from tornado.httpclient import HTTPError, HTTPRequest
|
||||
from tornado.locks import Event
|
||||
from tornado.log import gen_log, app_log
|
||||
from tornado.simple_httpclient import SimpleAsyncHTTPClient
|
||||
from tornado.template import DictLoader
|
||||
from tornado.testing import AsyncHTTPTestCase, gen_test, bind_unused_port, ExpectLog
|
||||
from tornado.web import Application, RequestHandler
|
||||
|
||||
try:
|
||||
import tornado.websocket # noqa: F401
|
||||
from tornado.util import _websocket_mask_python
|
||||
except ImportError:
|
||||
# The unittest module presents misleading errors on ImportError
|
||||
# (it acts as if websocket_test could not be found, hiding the underlying
|
||||
# error). If we get an ImportError here (which could happen due to
|
||||
# TORNADO_EXTENSION=1), print some extra information before failing.
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
from tornado.websocket import (
|
||||
WebSocketHandler,
|
||||
websocket_connect,
|
||||
WebSocketError,
|
||||
WebSocketClosedError,
|
||||
)
|
||||
|
||||
try:
|
||||
from tornado import speedups
|
||||
except ImportError:
|
||||
speedups = None # type: ignore
|
||||
|
||||
|
||||
class TestWebSocketHandler(WebSocketHandler):
|
||||
"""Base class for testing handlers that exposes the on_close event.
|
||||
|
||||
This allows for tests to see the close code and reason on the
|
||||
server side.
|
||||
|
||||
"""
|
||||
|
||||
def initialize(self, close_future=None, compression_options=None):
|
||||
self.close_future = close_future
|
||||
self.compression_options = compression_options
|
||||
|
||||
def get_compression_options(self):
|
||||
return self.compression_options
|
||||
|
||||
def on_close(self):
|
||||
if self.close_future is not None:
|
||||
self.close_future.set_result((self.close_code, self.close_reason))
|
||||
|
||||
|
||||
class EchoHandler(TestWebSocketHandler):
|
||||
@gen.coroutine
|
||||
def on_message(self, message):
|
||||
try:
|
||||
yield self.write_message(message, isinstance(message, bytes))
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except WebSocketClosedError:
|
||||
pass
|
||||
|
||||
|
||||
class ErrorInOnMessageHandler(TestWebSocketHandler):
|
||||
def on_message(self, message):
|
||||
1 / 0
|
||||
|
||||
|
||||
class HeaderHandler(TestWebSocketHandler):
|
||||
def open(self):
|
||||
methods_to_test = [
|
||||
functools.partial(self.write, "This should not work"),
|
||||
functools.partial(self.redirect, "http://localhost/elsewhere"),
|
||||
functools.partial(self.set_header, "X-Test", ""),
|
||||
functools.partial(self.set_cookie, "Chocolate", "Chip"),
|
||||
functools.partial(self.set_status, 503),
|
||||
self.flush,
|
||||
self.finish,
|
||||
]
|
||||
for method in methods_to_test:
|
||||
try:
|
||||
# In a websocket context, many RequestHandler methods
|
||||
# raise RuntimeErrors.
|
||||
method() # type: ignore
|
||||
raise Exception("did not get expected exception")
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.write_message(self.request.headers.get("X-Test", ""))
|
||||
|
||||
|
||||
class HeaderEchoHandler(TestWebSocketHandler):
|
||||
def set_default_headers(self):
|
||||
self.set_header("X-Extra-Response-Header", "Extra-Response-Value")
|
||||
|
||||
def prepare(self):
|
||||
for k, v in self.request.headers.get_all():
|
||||
if k.lower().startswith("x-test"):
|
||||
self.set_header(k, v)
|
||||
|
||||
|
||||
class NonWebSocketHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("ok")
|
||||
|
||||
|
||||
class RedirectHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.redirect("/echo")
|
||||
|
||||
|
||||
class CloseReasonHandler(TestWebSocketHandler):
|
||||
def open(self):
|
||||
self.on_close_called = False
|
||||
self.close(1001, "goodbye")
|
||||
|
||||
|
||||
class AsyncPrepareHandler(TestWebSocketHandler):
|
||||
@gen.coroutine
|
||||
def prepare(self):
|
||||
yield gen.moment
|
||||
|
||||
def on_message(self, message):
|
||||
self.write_message(message)
|
||||
|
||||
|
||||
class PathArgsHandler(TestWebSocketHandler):
|
||||
def open(self, arg):
|
||||
self.write_message(arg)
|
||||
|
||||
|
||||
class CoroutineOnMessageHandler(TestWebSocketHandler):
|
||||
def initialize(self, **kwargs):
|
||||
super().initialize(**kwargs)
|
||||
self.sleeping = 0
|
||||
|
||||
@gen.coroutine
|
||||
def on_message(self, message):
|
||||
if self.sleeping > 0:
|
||||
self.write_message("another coroutine is already sleeping")
|
||||
self.sleeping += 1
|
||||
yield gen.sleep(0.01)
|
||||
self.sleeping -= 1
|
||||
self.write_message(message)
|
||||
|
||||
|
||||
class RenderMessageHandler(TestWebSocketHandler):
|
||||
def on_message(self, message):
|
||||
self.write_message(self.render_string("message.html", message=message))
|
||||
|
||||
|
||||
class SubprotocolHandler(TestWebSocketHandler):
|
||||
def initialize(self, **kwargs):
|
||||
super().initialize(**kwargs)
|
||||
self.select_subprotocol_called = False
|
||||
|
||||
def select_subprotocol(self, subprotocols):
|
||||
if self.select_subprotocol_called:
|
||||
raise Exception("select_subprotocol called twice")
|
||||
self.select_subprotocol_called = True
|
||||
if "goodproto" in subprotocols:
|
||||
return "goodproto"
|
||||
return None
|
||||
|
||||
def open(self):
|
||||
if not self.select_subprotocol_called:
|
||||
raise Exception("select_subprotocol not called")
|
||||
self.write_message("subprotocol=%s" % self.selected_subprotocol)
|
||||
|
||||
|
||||
class OpenCoroutineHandler(TestWebSocketHandler):
|
||||
def initialize(self, test, **kwargs):
|
||||
super().initialize(**kwargs)
|
||||
self.test = test
|
||||
self.open_finished = False
|
||||
|
||||
@gen.coroutine
|
||||
def open(self):
|
||||
yield self.test.message_sent.wait()
|
||||
yield gen.sleep(0.010)
|
||||
self.open_finished = True
|
||||
|
||||
def on_message(self, message):
|
||||
if not self.open_finished:
|
||||
raise Exception("on_message called before open finished")
|
||||
self.write_message("ok")
|
||||
|
||||
|
||||
class ErrorInOpenHandler(TestWebSocketHandler):
|
||||
def open(self):
|
||||
raise Exception("boom")
|
||||
|
||||
|
||||
class ErrorInAsyncOpenHandler(TestWebSocketHandler):
|
||||
async def open(self):
|
||||
await asyncio.sleep(0)
|
||||
raise Exception("boom")
|
||||
|
||||
|
||||
class NoDelayHandler(TestWebSocketHandler):
|
||||
def open(self):
|
||||
self.set_nodelay(True)
|
||||
self.write_message("hello")
|
||||
|
||||
|
||||
class WebSocketBaseTestCase(AsyncHTTPTestCase):
|
||||
@gen.coroutine
|
||||
def ws_connect(self, path, **kwargs):
|
||||
ws = yield websocket_connect(
|
||||
"ws://127.0.0.1:%d%s" % (self.get_http_port(), path), **kwargs
|
||||
)
|
||||
raise gen.Return(ws)
|
||||
|
||||
|
||||
class WebSocketTest(WebSocketBaseTestCase):
|
||||
def get_app(self):
|
||||
self.close_future = Future() # type: Future[None]
|
||||
return Application(
|
||||
[
|
||||
("/echo", EchoHandler, dict(close_future=self.close_future)),
|
||||
("/non_ws", NonWebSocketHandler),
|
||||
("/redirect", RedirectHandler),
|
||||
("/header", HeaderHandler, dict(close_future=self.close_future)),
|
||||
(
|
||||
"/header_echo",
|
||||
HeaderEchoHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
(
|
||||
"/close_reason",
|
||||
CloseReasonHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
(
|
||||
"/error_in_on_message",
|
||||
ErrorInOnMessageHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
(
|
||||
"/async_prepare",
|
||||
AsyncPrepareHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
(
|
||||
"/path_args/(.*)",
|
||||
PathArgsHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
(
|
||||
"/coroutine",
|
||||
CoroutineOnMessageHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
("/render", RenderMessageHandler, dict(close_future=self.close_future)),
|
||||
(
|
||||
"/subprotocol",
|
||||
SubprotocolHandler,
|
||||
dict(close_future=self.close_future),
|
||||
),
|
||||
(
|
||||
"/open_coroutine",
|
||||
OpenCoroutineHandler,
|
||||
dict(close_future=self.close_future, test=self),
|
||||
),
|
||||
("/error_in_open", ErrorInOpenHandler),
|
||||
("/error_in_async_open", ErrorInAsyncOpenHandler),
|
||||
("/nodelay", NoDelayHandler),
|
||||
],
|
||||
template_loader=DictLoader({"message.html": "<b>{{ message }}</b>"}),
|
||||
)
|
||||
|
||||
def get_http_client(self):
|
||||
# These tests require HTTP/1; force the use of SimpleAsyncHTTPClient.
|
||||
return SimpleAsyncHTTPClient()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
RequestHandler._template_loaders.clear()
|
||||
|
||||
def test_http_request(self):
|
||||
# WS server, HTTP client.
|
||||
response = self.fetch("/echo")
|
||||
self.assertEqual(response.code, 400)
|
||||
|
||||
def test_missing_websocket_key(self):
|
||||
response = self.fetch(
|
||||
"/echo",
|
||||
headers={
|
||||
"Connection": "Upgrade",
|
||||
"Upgrade": "WebSocket",
|
||||
"Sec-WebSocket-Version": "13",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.code, 400)
|
||||
|
||||
def test_bad_websocket_version(self):
|
||||
response = self.fetch(
|
||||
"/echo",
|
||||
headers={
|
||||
"Connection": "Upgrade",
|
||||
"Upgrade": "WebSocket",
|
||||
"Sec-WebSocket-Version": "12",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.code, 426)
|
||||
|
||||
@gen_test
|
||||
def test_websocket_gen(self):
|
||||
ws = yield self.ws_connect("/echo")
|
||||
yield ws.write_message("hello")
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "hello")
|
||||
|
||||
def test_websocket_callbacks(self):
|
||||
websocket_connect(
|
||||
"ws://127.0.0.1:%d/echo" % self.get_http_port(), callback=self.stop
|
||||
)
|
||||
ws = self.wait().result()
|
||||
ws.write_message("hello")
|
||||
ws.read_message(self.stop)
|
||||
response = self.wait().result()
|
||||
self.assertEqual(response, "hello")
|
||||
self.close_future.add_done_callback(lambda f: self.stop())
|
||||
ws.close()
|
||||
self.wait()
|
||||
|
||||
@gen_test
|
||||
def test_binary_message(self):
|
||||
ws = yield self.ws_connect("/echo")
|
||||
ws.write_message(b"hello \xe9", binary=True)
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, b"hello \xe9")
|
||||
|
||||
@gen_test
|
||||
def test_unicode_message(self):
|
||||
ws = yield self.ws_connect("/echo")
|
||||
ws.write_message(u"hello \u00e9")
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, u"hello \u00e9")
|
||||
|
||||
@gen_test
|
||||
def test_render_message(self):
|
||||
ws = yield self.ws_connect("/render")
|
||||
ws.write_message("hello")
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "<b>hello</b>")
|
||||
|
||||
@gen_test
|
||||
def test_error_in_on_message(self):
|
||||
ws = yield self.ws_connect("/error_in_on_message")
|
||||
ws.write_message("hello")
|
||||
with ExpectLog(app_log, "Uncaught exception"):
|
||||
response = yield ws.read_message()
|
||||
self.assertIs(response, None)
|
||||
|
||||
@gen_test
|
||||
def test_websocket_http_fail(self):
|
||||
with self.assertRaises(HTTPError) as cm:
|
||||
yield self.ws_connect("/notfound")
|
||||
self.assertEqual(cm.exception.code, 404)
|
||||
|
||||
@gen_test
|
||||
def test_websocket_http_success(self):
|
||||
with self.assertRaises(WebSocketError):
|
||||
yield self.ws_connect("/non_ws")
|
||||
|
||||
@gen_test
|
||||
def test_websocket_http_redirect(self):
|
||||
with self.assertRaises(HTTPError):
|
||||
yield self.ws_connect("/redirect")
|
||||
|
||||
@gen_test
|
||||
def test_websocket_network_fail(self):
|
||||
sock, port = bind_unused_port()
|
||||
sock.close()
|
||||
with self.assertRaises(IOError):
|
||||
with ExpectLog(gen_log, ".*"):
|
||||
yield websocket_connect(
|
||||
"ws://127.0.0.1:%d/" % port, connect_timeout=3600
|
||||
)
|
||||
|
||||
@gen_test
|
||||
def test_websocket_close_buffered_data(self):
|
||||
ws = yield websocket_connect("ws://127.0.0.1:%d/echo" % self.get_http_port())
|
||||
ws.write_message("hello")
|
||||
ws.write_message("world")
|
||||
# Close the underlying stream.
|
||||
ws.stream.close()
|
||||
|
||||
@gen_test
|
||||
def test_websocket_headers(self):
|
||||
# Ensure that arbitrary headers can be passed through websocket_connect.
|
||||
ws = yield websocket_connect(
|
||||
HTTPRequest(
|
||||
"ws://127.0.0.1:%d/header" % self.get_http_port(),
|
||||
headers={"X-Test": "hello"},
|
||||
)
|
||||
)
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "hello")
|
||||
|
||||
@gen_test
|
||||
def test_websocket_header_echo(self):
|
||||
# Ensure that headers can be returned in the response.
|
||||
# Specifically, that arbitrary headers passed through websocket_connect
|
||||
# can be returned.
|
||||
ws = yield websocket_connect(
|
||||
HTTPRequest(
|
||||
"ws://127.0.0.1:%d/header_echo" % self.get_http_port(),
|
||||
headers={"X-Test-Hello": "hello"},
|
||||
)
|
||||
)
|
||||
self.assertEqual(ws.headers.get("X-Test-Hello"), "hello")
|
||||
self.assertEqual(
|
||||
ws.headers.get("X-Extra-Response-Header"), "Extra-Response-Value"
|
||||
)
|
||||
|
||||
@gen_test
|
||||
def test_server_close_reason(self):
|
||||
ws = yield self.ws_connect("/close_reason")
|
||||
msg = yield ws.read_message()
|
||||
# A message of None means the other side closed the connection.
|
||||
self.assertIs(msg, None)
|
||||
self.assertEqual(ws.close_code, 1001)
|
||||
self.assertEqual(ws.close_reason, "goodbye")
|
||||
# The on_close callback is called no matter which side closed.
|
||||
code, reason = yield self.close_future
|
||||
# The client echoed the close code it received to the server,
|
||||
# so the server's close code (returned via close_future) is
|
||||
# the same.
|
||||
self.assertEqual(code, 1001)
|
||||
|
||||
@gen_test
|
||||
def test_client_close_reason(self):
|
||||
ws = yield self.ws_connect("/echo")
|
||||
ws.close(1001, "goodbye")
|
||||
code, reason = yield self.close_future
|
||||
self.assertEqual(code, 1001)
|
||||
self.assertEqual(reason, "goodbye")
|
||||
|
||||
@gen_test
|
||||
def test_write_after_close(self):
|
||||
ws = yield self.ws_connect("/close_reason")
|
||||
msg = yield ws.read_message()
|
||||
self.assertIs(msg, None)
|
||||
with self.assertRaises(WebSocketClosedError):
|
||||
ws.write_message("hello")
|
||||
|
||||
@gen_test
|
||||
def test_async_prepare(self):
|
||||
# Previously, an async prepare method triggered a bug that would
|
||||
# result in a timeout on test shutdown (and a memory leak).
|
||||
ws = yield self.ws_connect("/async_prepare")
|
||||
ws.write_message("hello")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello")
|
||||
|
||||
@gen_test
|
||||
def test_path_args(self):
|
||||
ws = yield self.ws_connect("/path_args/hello")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello")
|
||||
|
||||
@gen_test
|
||||
def test_coroutine(self):
|
||||
ws = yield self.ws_connect("/coroutine")
|
||||
# Send both messages immediately, coroutine must process one at a time.
|
||||
yield ws.write_message("hello1")
|
||||
yield ws.write_message("hello2")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello1")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello2")
|
||||
|
||||
@gen_test
|
||||
def test_check_origin_valid_no_path(self):
|
||||
port = self.get_http_port()
|
||||
|
||||
url = "ws://127.0.0.1:%d/echo" % port
|
||||
headers = {"Origin": "http://127.0.0.1:%d" % port}
|
||||
|
||||
ws = yield websocket_connect(HTTPRequest(url, headers=headers))
|
||||
ws.write_message("hello")
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "hello")
|
||||
|
||||
@gen_test
|
||||
def test_check_origin_valid_with_path(self):
|
||||
port = self.get_http_port()
|
||||
|
||||
url = "ws://127.0.0.1:%d/echo" % port
|
||||
headers = {"Origin": "http://127.0.0.1:%d/something" % port}
|
||||
|
||||
ws = yield websocket_connect(HTTPRequest(url, headers=headers))
|
||||
ws.write_message("hello")
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "hello")
|
||||
|
||||
@gen_test
|
||||
def test_check_origin_invalid_partial_url(self):
|
||||
port = self.get_http_port()
|
||||
|
||||
url = "ws://127.0.0.1:%d/echo" % port
|
||||
headers = {"Origin": "127.0.0.1:%d" % port}
|
||||
|
||||
with self.assertRaises(HTTPError) as cm:
|
||||
yield websocket_connect(HTTPRequest(url, headers=headers))
|
||||
self.assertEqual(cm.exception.code, 403)
|
||||
|
||||
@gen_test
|
||||
def test_check_origin_invalid(self):
|
||||
port = self.get_http_port()
|
||||
|
||||
url = "ws://127.0.0.1:%d/echo" % port
|
||||
# Host is 127.0.0.1, which should not be accessible from some other
|
||||
# domain
|
||||
headers = {"Origin": "http://somewhereelse.com"}
|
||||
|
||||
with self.assertRaises(HTTPError) as cm:
|
||||
yield websocket_connect(HTTPRequest(url, headers=headers))
|
||||
|
||||
self.assertEqual(cm.exception.code, 403)
|
||||
|
||||
@gen_test
|
||||
def test_check_origin_invalid_subdomains(self):
|
||||
port = self.get_http_port()
|
||||
|
||||
url = "ws://localhost:%d/echo" % port
|
||||
# Subdomains should be disallowed by default. If we could pass a
|
||||
# resolver to websocket_connect we could test sibling domains as well.
|
||||
headers = {"Origin": "http://subtenant.localhost"}
|
||||
|
||||
with self.assertRaises(HTTPError) as cm:
|
||||
yield websocket_connect(HTTPRequest(url, headers=headers))
|
||||
|
||||
self.assertEqual(cm.exception.code, 403)
|
||||
|
||||
@gen_test
|
||||
def test_subprotocols(self):
|
||||
ws = yield self.ws_connect(
|
||||
"/subprotocol", subprotocols=["badproto", "goodproto"]
|
||||
)
|
||||
self.assertEqual(ws.selected_subprotocol, "goodproto")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "subprotocol=goodproto")
|
||||
|
||||
@gen_test
|
||||
def test_subprotocols_not_offered(self):
|
||||
ws = yield self.ws_connect("/subprotocol")
|
||||
self.assertIs(ws.selected_subprotocol, None)
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "subprotocol=None")
|
||||
|
||||
@gen_test
|
||||
def test_open_coroutine(self):
|
||||
self.message_sent = Event()
|
||||
ws = yield self.ws_connect("/open_coroutine")
|
||||
yield ws.write_message("hello")
|
||||
self.message_sent.set()
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "ok")
|
||||
|
||||
@gen_test
|
||||
def test_error_in_open(self):
|
||||
with ExpectLog(app_log, "Uncaught exception"):
|
||||
ws = yield self.ws_connect("/error_in_open")
|
||||
res = yield ws.read_message()
|
||||
self.assertIsNone(res)
|
||||
|
||||
@gen_test
|
||||
def test_error_in_async_open(self):
|
||||
with ExpectLog(app_log, "Uncaught exception"):
|
||||
ws = yield self.ws_connect("/error_in_async_open")
|
||||
res = yield ws.read_message()
|
||||
self.assertIsNone(res)
|
||||
|
||||
@gen_test
|
||||
def test_nodelay(self):
|
||||
ws = yield self.ws_connect("/nodelay")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello")
|
||||
|
||||
|
||||
class NativeCoroutineOnMessageHandler(TestWebSocketHandler):
|
||||
def initialize(self, **kwargs):
|
||||
super().initialize(**kwargs)
|
||||
self.sleeping = 0
|
||||
|
||||
async def on_message(self, message):
|
||||
if self.sleeping > 0:
|
||||
self.write_message("another coroutine is already sleeping")
|
||||
self.sleeping += 1
|
||||
await gen.sleep(0.01)
|
||||
self.sleeping -= 1
|
||||
self.write_message(message)
|
||||
|
||||
|
||||
class WebSocketNativeCoroutineTest(WebSocketBaseTestCase):
|
||||
def get_app(self):
|
||||
return Application([("/native", NativeCoroutineOnMessageHandler)])
|
||||
|
||||
@gen_test
|
||||
def test_native_coroutine(self):
|
||||
ws = yield self.ws_connect("/native")
|
||||
# Send both messages immediately, coroutine must process one at a time.
|
||||
yield ws.write_message("hello1")
|
||||
yield ws.write_message("hello2")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello1")
|
||||
res = yield ws.read_message()
|
||||
self.assertEqual(res, "hello2")
|
||||
|
||||
|
||||
class CompressionTestMixin(object):
|
||||
MESSAGE = "Hello world. Testing 123 123"
|
||||
|
||||
def get_app(self):
|
||||
class LimitedHandler(TestWebSocketHandler):
|
||||
@property
|
||||
def max_message_size(self):
|
||||
return 1024
|
||||
|
||||
def on_message(self, message):
|
||||
self.write_message(str(len(message)))
|
||||
|
||||
return Application(
|
||||
[
|
||||
(
|
||||
"/echo",
|
||||
EchoHandler,
|
||||
dict(compression_options=self.get_server_compression_options()),
|
||||
),
|
||||
(
|
||||
"/limited",
|
||||
LimitedHandler,
|
||||
dict(compression_options=self.get_server_compression_options()),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def get_server_compression_options(self):
|
||||
return None
|
||||
|
||||
def get_client_compression_options(self):
|
||||
return None
|
||||
|
||||
def verify_wire_bytes(self, bytes_in: int, bytes_out: int) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@gen_test
|
||||
def test_message_sizes(self: typing.Any):
|
||||
ws = yield self.ws_connect(
|
||||
"/echo", compression_options=self.get_client_compression_options()
|
||||
)
|
||||
# Send the same message three times so we can measure the
|
||||
# effect of the context_takeover options.
|
||||
for i in range(3):
|
||||
ws.write_message(self.MESSAGE)
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, self.MESSAGE)
|
||||
self.assertEqual(ws.protocol._message_bytes_out, len(self.MESSAGE) * 3)
|
||||
self.assertEqual(ws.protocol._message_bytes_in, len(self.MESSAGE) * 3)
|
||||
self.verify_wire_bytes(ws.protocol._wire_bytes_in, ws.protocol._wire_bytes_out)
|
||||
|
||||
@gen_test
|
||||
def test_size_limit(self: typing.Any):
|
||||
ws = yield self.ws_connect(
|
||||
"/limited", compression_options=self.get_client_compression_options()
|
||||
)
|
||||
# Small messages pass through.
|
||||
ws.write_message("a" * 128)
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "128")
|
||||
# This message is too big after decompression, but it compresses
|
||||
# down to a size that will pass the initial checks.
|
||||
ws.write_message("a" * 2048)
|
||||
response = yield ws.read_message()
|
||||
self.assertIsNone(response)
|
||||
|
||||
|
||||
class UncompressedTestMixin(CompressionTestMixin):
|
||||
"""Specialization of CompressionTestMixin when we expect no compression."""
|
||||
|
||||
def verify_wire_bytes(self: typing.Any, bytes_in, bytes_out):
|
||||
# Bytes out includes the 4-byte mask key per message.
|
||||
self.assertEqual(bytes_out, 3 * (len(self.MESSAGE) + 6))
|
||||
self.assertEqual(bytes_in, 3 * (len(self.MESSAGE) + 2))
|
||||
|
||||
|
||||
class NoCompressionTest(UncompressedTestMixin, WebSocketBaseTestCase):
|
||||
pass
|
||||
|
||||
|
||||
# If only one side tries to compress, the extension is not negotiated.
|
||||
class ServerOnlyCompressionTest(UncompressedTestMixin, WebSocketBaseTestCase):
|
||||
def get_server_compression_options(self):
|
||||
return {}
|
||||
|
||||
|
||||
class ClientOnlyCompressionTest(UncompressedTestMixin, WebSocketBaseTestCase):
|
||||
def get_client_compression_options(self):
|
||||
return {}
|
||||
|
||||
|
||||
class DefaultCompressionTest(CompressionTestMixin, WebSocketBaseTestCase):
|
||||
def get_server_compression_options(self):
|
||||
return {}
|
||||
|
||||
def get_client_compression_options(self):
|
||||
return {}
|
||||
|
||||
def verify_wire_bytes(self, bytes_in, bytes_out):
|
||||
self.assertLess(bytes_out, 3 * (len(self.MESSAGE) + 6))
|
||||
self.assertLess(bytes_in, 3 * (len(self.MESSAGE) + 2))
|
||||
# Bytes out includes the 4 bytes mask key per message.
|
||||
self.assertEqual(bytes_out, bytes_in + 12)
|
||||
|
||||
|
||||
class MaskFunctionMixin(object):
|
||||
# Subclasses should define self.mask(mask, data)
|
||||
def mask(self, mask: bytes, data: bytes) -> bytes:
|
||||
raise NotImplementedError()
|
||||
|
||||
def test_mask(self: typing.Any):
|
||||
self.assertEqual(self.mask(b"abcd", b""), b"")
|
||||
self.assertEqual(self.mask(b"abcd", b"b"), b"\x03")
|
||||
self.assertEqual(self.mask(b"abcd", b"54321"), b"TVPVP")
|
||||
self.assertEqual(self.mask(b"ZXCV", b"98765432"), b"c`t`olpd")
|
||||
# Include test cases with \x00 bytes (to ensure that the C
|
||||
# extension isn't depending on null-terminated strings) and
|
||||
# bytes with the high bit set (to smoke out signedness issues).
|
||||
self.assertEqual(
|
||||
self.mask(b"\x00\x01\x02\x03", b"\xff\xfb\xfd\xfc\xfe\xfa"),
|
||||
b"\xff\xfa\xff\xff\xfe\xfb",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.mask(b"\xff\xfb\xfd\xfc", b"\x00\x01\x02\x03\x04\x05"),
|
||||
b"\xff\xfa\xff\xff\xfb\xfe",
|
||||
)
|
||||
|
||||
|
||||
class PythonMaskFunctionTest(MaskFunctionMixin, unittest.TestCase):
|
||||
def mask(self, mask, data):
|
||||
return _websocket_mask_python(mask, data)
|
||||
|
||||
|
||||
@unittest.skipIf(speedups is None, "tornado.speedups module not present")
|
||||
class CythonMaskFunctionTest(MaskFunctionMixin, unittest.TestCase):
|
||||
def mask(self, mask, data):
|
||||
return speedups.websocket_mask(mask, data)
|
||||
|
||||
|
||||
class ServerPeriodicPingTest(WebSocketBaseTestCase):
|
||||
def get_app(self):
|
||||
class PingHandler(TestWebSocketHandler):
|
||||
def on_pong(self, data):
|
||||
self.write_message("got pong")
|
||||
|
||||
return Application([("/", PingHandler)], websocket_ping_interval=0.01)
|
||||
|
||||
@gen_test
|
||||
def test_server_ping(self):
|
||||
ws = yield self.ws_connect("/")
|
||||
for i in range(3):
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "got pong")
|
||||
# TODO: test that the connection gets closed if ping responses stop.
|
||||
|
||||
|
||||
class ClientPeriodicPingTest(WebSocketBaseTestCase):
|
||||
def get_app(self):
|
||||
class PingHandler(TestWebSocketHandler):
|
||||
def on_ping(self, data):
|
||||
self.write_message("got ping")
|
||||
|
||||
return Application([("/", PingHandler)])
|
||||
|
||||
@gen_test
|
||||
def test_client_ping(self):
|
||||
ws = yield self.ws_connect("/", ping_interval=0.01)
|
||||
for i in range(3):
|
||||
response = yield ws.read_message()
|
||||
self.assertEqual(response, "got ping")
|
||||
# TODO: test that the connection gets closed if ping responses stop.
|
||||
|
||||
|
||||
class ManualPingTest(WebSocketBaseTestCase):
|
||||
def get_app(self):
|
||||
class PingHandler(TestWebSocketHandler):
|
||||
def on_ping(self, data):
|
||||
self.write_message(data, binary=isinstance(data, bytes))
|
||||
|
||||
return Application([("/", PingHandler)])
|
||||
|
||||
@gen_test
|
||||
def test_manual_ping(self):
|
||||
ws = yield self.ws_connect("/")
|
||||
|
||||
self.assertRaises(ValueError, ws.ping, "a" * 126)
|
||||
|
||||
ws.ping("hello")
|
||||
resp = yield ws.read_message()
|
||||
# on_ping always sees bytes.
|
||||
self.assertEqual(resp, b"hello")
|
||||
|
||||
ws.ping(b"binary hello")
|
||||
resp = yield ws.read_message()
|
||||
self.assertEqual(resp, b"binary hello")
|
||||
|
||||
|
||||
class MaxMessageSizeTest(WebSocketBaseTestCase):
|
||||
def get_app(self):
|
||||
return Application([("/", EchoHandler)], websocket_max_message_size=1024)
|
||||
|
||||
@gen_test
|
||||
def test_large_message(self):
|
||||
ws = yield self.ws_connect("/")
|
||||
|
||||
# Write a message that is allowed.
|
||||
msg = "a" * 1024
|
||||
ws.write_message(msg)
|
||||
resp = yield ws.read_message()
|
||||
self.assertEqual(resp, msg)
|
||||
|
||||
# Write a message that is too large.
|
||||
ws.write_message(msg + "b")
|
||||
resp = yield ws.read_message()
|
||||
# A message of None means the other side closed the connection.
|
||||
self.assertIs(resp, None)
|
||||
self.assertEqual(ws.close_code, 1009)
|
||||
self.assertEqual(ws.close_reason, "message too big")
|
||||
# TODO: Needs tests of messages split over multiple
|
||||
# continuation frames.
|
20
venv/Lib/site-packages/tornado/test/wsgi_test.py
Normal file
20
venv/Lib/site-packages/tornado/test/wsgi_test.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from wsgiref.validate import validator
|
||||
|
||||
from tornado.testing import AsyncHTTPTestCase
|
||||
from tornado.wsgi import WSGIContainer
|
||||
|
||||
|
||||
class WSGIContainerTest(AsyncHTTPTestCase):
|
||||
# TODO: Now that WSGIAdapter is gone, this is a pretty weak test.
|
||||
def wsgi_app(self, environ, start_response):
|
||||
status = "200 OK"
|
||||
response_headers = [("Content-Type", "text/plain")]
|
||||
start_response(status, response_headers)
|
||||
return [b"Hello world!"]
|
||||
|
||||
def get_app(self):
|
||||
return WSGIContainer(validator(self.wsgi_app))
|
||||
|
||||
def test_simple(self):
|
||||
response = self.fetch("/")
|
||||
self.assertEqual(response.body, b"Hello world!")
|
Loading…
Add table
Add a link
Reference in a new issue