debugpy/tests/func/test_flask.py

358 lines
13 KiB
Python

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
from __future__ import print_function, with_statement, absolute_import
import os.path
import platform
import pytest
import sys
import tests.helpers
from tests.helpers.pattern import ANY, Path
from tests.helpers.session import DebugSession
from tests.helpers.timeline import Event
from tests.helpers.webhelper import get_web_content, wait_for_connection
from tests.helpers.pathutils import get_test_root
FLASK1_ROOT = get_test_root('flask1')
FLASK1_APP = os.path.join(FLASK1_ROOT, 'app.py')
FLASK1_TEMPLATE = os.path.join(FLASK1_ROOT, 'templates', 'hello.html')
FLASK1_BAD_TEMPLATE = os.path.join(FLASK1_ROOT, 'templates', 'bad.html')
FLASK_PORT = tests.helpers.get_unique_port(5000)
FLASK_LINK = 'http://127.0.0.1:{}/'.format(FLASK_PORT)
def _initialize_flask_session_no_multiproc(session, start_method):
env = {
'FLASK_APP': 'app.py',
'FLASK_ENV': 'development',
'FLASK_DEBUG': '0',
}
if platform.system() != 'Windows':
locale = 'en_US.utf8' if platform.system() == 'Linux' else 'en_US.UTF-8'
env.update({
'LC_ALL': locale,
'LANG': locale,
})
session.initialize(
start_method=start_method,
target=('module', 'flask'),
program_args=['run', '--no-debugger', '--no-reload', '--with-threads', '--port', str(FLASK_PORT)],
ignore_unobserved=[Event('stopped')],
debug_options=['Jinja'],
cwd=FLASK1_ROOT,
env=env,
expected_returncode=ANY.int, # No clean way to kill Flask server
)
@pytest.mark.parametrize('bp_target', ['code', 'template'])
@pytest.mark.parametrize('start_method', ['launch', 'attach_socket_cmdline'])
@pytest.mark.timeout(60)
def test_flask_breakpoint_no_multiproc(bp_target, start_method):
bp_file, bp_line, bp_name = {
'code': (FLASK1_APP, 11, 'home'),
'template': (FLASK1_TEMPLATE, 8, 'template')
}[bp_target]
with DebugSession() as session:
_initialize_flask_session_no_multiproc(session, start_method)
bp_var_content = 'Flask-Jinja-Test'
session.set_breakpoints(bp_file, [bp_line])
session.start_debugging()
# wait for Flask web server to start
wait_for_connection(FLASK_PORT)
link = FLASK_LINK
web_request = get_web_content(link, {})
thread_stopped = session.wait_for_next(Event('stopped'), ANY.dict_with({'reason': 'breakpoint'}))
assert thread_stopped.body['threadId'] is not None
tid = thread_stopped.body['threadId']
resp_stacktrace = session.send_request('stackTrace', arguments={
'threadId': tid,
}).wait_for_response()
assert resp_stacktrace.body['totalFrames'] > 0
frames = resp_stacktrace.body['stackFrames']
assert frames[0] == {
'id': ANY.dap_id,
'name': bp_name,
'source': {
'sourceReference': ANY.dap_id,
'path': Path(bp_file),
},
'line': bp_line,
'column': 1,
}
fid = frames[0]['id']
resp_scopes = session.send_request('scopes', arguments={
'frameId': fid
}).wait_for_response()
scopes = resp_scopes.body['scopes']
assert len(scopes) > 0
resp_variables = session.send_request('variables', arguments={
'variablesReference': scopes[0]['variablesReference']
}).wait_for_response()
variables = list(v for v in resp_variables.body['variables'] if v['name'] == 'content')
assert variables == [{
'name': 'content',
'type': 'str',
'value': repr(bp_var_content),
'presentationHint': {'attributes': ['rawString']},
'evaluateName': 'content',
'variablesReference': 0,
}]
session.send_request('continue').wait_for_response(freeze=False)
web_content = web_request.wait_for_response()
assert web_content.find(bp_var_content) != -1
# shutdown to web server
link = FLASK_LINK + 'exit'
get_web_content(link).wait_for_response()
session.wait_for_exit()
@pytest.mark.parametrize('start_method', ['launch', 'attach_socket_cmdline'])
@pytest.mark.timeout(60)
def test_flask_template_exception_no_multiproc(start_method):
with DebugSession() as session:
_initialize_flask_session_no_multiproc(session, start_method)
session.send_request('setExceptionBreakpoints', arguments={
'filters': ['raised', 'uncaught'],
}).wait_for_response()
session.start_debugging()
# wait for Flask web server to start
wait_for_connection(FLASK_PORT)
base_link = FLASK_LINK
part = 'badtemplate'
link = base_link + part if base_link.endswith('/') else ('/' + part)
web_request = get_web_content(link, {})
hit = session.wait_for_thread_stopped()
frames = hit.stacktrace.body['stackFrames']
assert frames[0] == ANY.dict_with({
'id': ANY.dap_id,
'name': 'template' if sys.version_info[0] >= 3 else 'Jinja2 TemplateSyntaxError',
'source': ANY.dict_with({
'sourceReference': ANY.dap_id,
'path': Path(FLASK1_BAD_TEMPLATE),
}),
'line': 8,
'column': 1,
})
resp_exception_info = session.send_request(
'exceptionInfo',
arguments={'threadId': hit.thread_id, }
).wait_for_response()
exception = resp_exception_info.body
assert exception == ANY.dict_with({
'exceptionId': ANY.such_that(lambda s: s.endswith('TemplateSyntaxError')),
'breakMode': 'always',
'description': ANY.such_that(lambda s: s.find('doesnotexist') > -1),
'details': ANY.dict_with({
'message': ANY.such_that(lambda s: s.find('doesnotexist') > -1),
'typeName': ANY.such_that(lambda s: s.endswith('TemplateSyntaxError')),
})
})
session.send_request('continue').wait_for_response(freeze=False)
# ignore response for exception tests
web_request.wait_for_response()
# shutdown to web server
link = base_link + 'exit' if base_link.endswith('/') else '/exit'
get_web_content(link).wait_for_response()
session.wait_for_exit()
@pytest.mark.parametrize('ex_type', ['handled', 'unhandled'])
@pytest.mark.parametrize('start_method', ['launch', 'attach_socket_cmdline'])
@pytest.mark.timeout(60)
def test_flask_exception_no_multiproc(ex_type, start_method):
ex_line = {
'handled': 21,
'unhandled': 33,
}[ex_type]
with DebugSession() as session:
_initialize_flask_session_no_multiproc(session, start_method)
session.send_request('setExceptionBreakpoints', arguments={
'filters': ['raised', 'uncaught'],
}).wait_for_response()
session.start_debugging()
# wait for Flask web server to start
wait_for_connection(FLASK_PORT)
base_link = FLASK_LINK
link = base_link + ex_type if base_link.endswith('/') else ('/' + ex_type)
web_request = get_web_content(link, {})
thread_stopped = session.wait_for_next(Event('stopped', ANY.dict_with({'reason': 'exception'})))
assert thread_stopped == Event('stopped', ANY.dict_with({
'reason': 'exception',
'text': ANY.such_that(lambda s: s.endswith('ArithmeticError')),
'description': 'Hello'
}))
tid = thread_stopped.body['threadId']
resp_exception_info = session.send_request(
'exceptionInfo',
arguments={'threadId': tid, }
).wait_for_response()
exception = resp_exception_info.body
assert exception == {
'exceptionId': ANY.such_that(lambda s: s.endswith('ArithmeticError')),
'breakMode': 'always',
'description': 'Hello',
'details': {
'message': 'Hello',
'typeName': ANY.such_that(lambda s: s.endswith('ArithmeticError')),
'source': Path(FLASK1_APP),
'stackTrace': ANY.such_that(lambda s: True)
}
}
resp_stacktrace = session.send_request('stackTrace', arguments={
'threadId': tid,
}).wait_for_response()
assert resp_stacktrace.body['totalFrames'] > 0
frames = resp_stacktrace.body['stackFrames']
assert frames[0] == {
'id': ANY.dap_id,
'name': 'bad_route_' + ex_type,
'source': {
'sourceReference': ANY.dap_id,
'path': Path(FLASK1_APP),
},
'line': ex_line,
'column': 1,
}
session.send_request('continue').wait_for_response(freeze=False)
# ignore response for exception tests
web_request.wait_for_response()
# shutdown to web server
link = base_link + 'exit' if base_link.endswith('/') else '/exit'
get_web_content(link).wait_for_response()
session.wait_for_exit()
@pytest.mark.timeout(120)
@pytest.mark.parametrize('start_method', ['launch'])
@pytest.mark.skipif((sys.version_info < (3, 0)) and (platform.system() != 'Windows'), reason='Bug #935')
def test_flask_breakpoint_multiproc(start_method):
env = {
'FLASK_APP': 'app',
'FLASK_ENV': 'development',
'FLASK_DEBUG': '1',
}
if platform.system() != 'Windows':
locale = 'en_US.utf8' if platform.system() == 'Linux' else 'en_US.UTF-8'
env.update({
'LC_ALL': locale,
'LANG': locale,
})
with DebugSession() as parent_session:
parent_session.initialize(
start_method=start_method,
target=('module', 'flask'),
multiprocess=True,
program_args=['run', '--port', str(FLASK_PORT)],
ignore_unobserved=[Event('stopped')],
debug_options=['Jinja'],
cwd=FLASK1_ROOT,
env=env,
expected_returncode=ANY.int, # No clean way to kill Flask server
)
bp_line = 11
bp_var_content = 'Flask-Jinja-Test'
parent_session.set_breakpoints(FLASK1_APP, [bp_line])
parent_session.start_debugging()
with parent_session.connect_to_next_child_session() as child_session:
child_session.send_request('setBreakpoints', arguments={
'source': {'path': FLASK1_APP},
'breakpoints': [{'line': bp_line}, ],
}).wait_for_response()
child_session.start_debugging()
# wait for Flask server to start
wait_for_connection(FLASK_PORT)
web_request = get_web_content(FLASK_LINK, {})
thread_stopped = child_session.wait_for_next(Event('stopped', ANY.dict_with({'reason': 'breakpoint'})))
assert thread_stopped.body['threadId'] is not None
tid = thread_stopped.body['threadId']
resp_stacktrace = child_session.send_request('stackTrace', arguments={
'threadId': tid,
}).wait_for_response()
assert resp_stacktrace.body['totalFrames'] > 0
frames = resp_stacktrace.body['stackFrames']
assert frames[0] == {
'id': ANY.dap_id,
'name': 'home',
'source': {
'sourceReference': ANY.dap_id,
'path': Path(FLASK1_APP),
},
'line': bp_line,
'column': 1,
}
fid = frames[0]['id']
resp_scopes = child_session.send_request('scopes', arguments={
'frameId': fid
}).wait_for_response()
scopes = resp_scopes.body['scopes']
assert len(scopes) > 0
resp_variables = child_session.send_request('variables', arguments={
'variablesReference': scopes[0]['variablesReference']
}).wait_for_response()
variables = [v for v in resp_variables.body['variables'] if v['name'] == 'content']
assert variables == [{
'name': 'content',
'type': 'str',
'value': repr(bp_var_content),
'presentationHint': {'attributes': ['rawString']},
'evaluateName': 'content',
'variablesReference': 0,
}]
child_session.send_request('continue').wait_for_response(freeze=False)
web_content = web_request.wait_for_response()
assert web_content.find(bp_var_content) != -1
# shutdown to web server
link = FLASK_LINK + 'exit'
get_web_content(link).wait_for_response()
child_session.wait_for_termination()
parent_session.wait_for_exit()