Skip to content

Commit 03131c9

Browse files
umaannamalaiTimPansinolrafeeihmstepanek
authored
Add double wrapped testing for Hypercorn and Daphne and dispatcher argument to WSGI API. (#667)
* Add double wrapped app tests. * Fix linting errors. * [Mega-Linter] Apply linters fixes * Add co-authors. Co-authored-by: Tim Pansino <[email protected]> Co-authored-by: Lalleh Rafeei <[email protected]> Co-authored-by: Hannah Stepanek <[email protected]> Co-authored-by: umaannamalai <[email protected]> Co-authored-by: Tim Pansino <[email protected]> Co-authored-by: Lalleh Rafeei <[email protected]> Co-authored-by: Hannah Stepanek <[email protected]>
1 parent 820e0b7 commit 03131c9

File tree

6 files changed

+145
-70
lines changed

6 files changed

+145
-70
lines changed

newrelic/api/asgi_application.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,20 +371,23 @@ async def nr_async_asgi(receive, send):
371371
return FunctionWrapper(wrapped, nr_asgi_wrapper)
372372

373373

374-
def asgi_application(application=None, name=None, group=None, framework=None):
374+
def asgi_application(application=None, name=None, group=None, framework=None, dispatcher=None):
375375
return functools.partial(
376376
ASGIApplicationWrapper,
377377
application=application,
378378
name=name,
379379
group=group,
380380
framework=framework,
381+
dispatcher=dispatcher,
381382
)
382383

383384

384-
def wrap_asgi_application(module, object_path, application=None, name=None, group=None, framework=None):
385+
def wrap_asgi_application(
386+
module, object_path, application=None, name=None, group=None, framework=None, dispatcher=None
387+
):
385388
wrap_object(
386389
module,
387390
object_path,
388391
ASGIApplicationWrapper,
389-
(application, name, group, framework),
392+
(application, name, group, framework, dispatcher),
390393
)

newrelic/api/wsgi_application.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,11 +691,18 @@ def write(data):
691691
return FunctionWrapper(wrapped, _nr_wsgi_application_wrapper_)
692692

693693

694-
def wsgi_application(application=None, name=None, group=None, framework=None):
694+
def wsgi_application(application=None, name=None, group=None, framework=None, dispatcher=None):
695695
return functools.partial(
696-
WSGIApplicationWrapper, application=application, name=name, group=group, framework=framework
696+
WSGIApplicationWrapper,
697+
application=application,
698+
name=name,
699+
group=group,
700+
framework=framework,
701+
dispatcher=dispatcher,
697702
)
698703

699704

700-
def wrap_wsgi_application(module, object_path, application=None, name=None, group=None, framework=None):
701-
wrap_object(module, object_path, WSGIApplicationWrapper, (application, name, group, framework))
705+
def wrap_wsgi_application(
706+
module, object_path, application=None, name=None, group=None, framework=None, dispatcher=None
707+
):
708+
wrap_object(module, object_path, WSGIApplicationWrapper, (application, name, group, framework, dispatcher))

tests/adapter_daphne/test_daphne.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
AppWithCall,
3030
AppWithCallRaw,
3131
simple_app_v2_raw,
32+
simple_app_v3,
3233
)
3334
from testing_support.util import get_open_port
3435

@@ -45,6 +46,10 @@
4546
simple_app_v2_raw,
4647
marks=skip_asgi_2_unsupported,
4748
),
49+
pytest.param(
50+
simple_app_v3,
51+
marks=skip_asgi_3_unsupported,
52+
),
4853
pytest.param(
4954
AppWithCallRaw(),
5055
marks=skip_asgi_3_unsupported,
@@ -54,7 +59,7 @@
5459
marks=skip_asgi_3_unsupported,
5560
),
5661
),
57-
ids=("raw", "class_with_call", "class_with_call_double_wrapped"),
62+
ids=("raw", "wrapped", "class_with_call", "class_with_call_double_wrapped"),
5863
)
5964
def app(request, server_and_port):
6065
app = request.param

tests/agent_features/test_asgi_transaction.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import asyncio
1615
import logging
1716

1817
import pytest
@@ -124,15 +123,34 @@ def test_asgi_application_decorator_no_params_double_callable():
124123
assert response.body == b""
125124

126125

127-
# Test for presence of framework info based on whether framework is specified
128-
@validate_transaction_metrics(name="test", custom_metrics=[("Python/Framework/framework/v1", 1)])
129-
def test_framework_metrics():
130-
asgi_decorator = asgi_application(name="test", framework=("framework", "v1"))
126+
# Test for presence of framework and dispatcher info based on whether framework is specified
127+
@validate_transaction_metrics(
128+
name="test", custom_metrics=[("Python/Framework/framework/v1", 1), ("Python/Dispatcher/dispatcher/v1.0.0", 1)]
129+
)
130+
def test_dispatcher_and_framework_metrics():
131+
asgi_decorator = asgi_application(name="test", framework=("framework", "v1"), dispatcher=("dispatcher", "v1.0.0"))
131132
decorated_application = asgi_decorator(simple_app_v2_raw)
132133
application = AsgiTest(decorated_application)
133134
application.make_request("GET", "/")
134135

135136

137+
# Test for presence of framework and dispatcher info under existing transaction
138+
@validate_transaction_metrics(
139+
name="test", custom_metrics=[("Python/Framework/framework/v1", 1), ("Python/Dispatcher/dispatcher/v1.0.0", 1)]
140+
)
141+
def test_double_wrapped_dispatcher_and_framework_metrics():
142+
inner_asgi_decorator = asgi_application(
143+
name="test", framework=("framework", "v1"), dispatcher=("dispatcher", "v1.0.0")
144+
)
145+
decorated_application = inner_asgi_decorator(simple_app_v2_raw)
146+
147+
outer_asgi_decorator = asgi_application(name="double_wrapped")
148+
double_decorated_application = outer_asgi_decorator(decorated_application)
149+
150+
application = AsgiTest(double_decorated_application)
151+
application.make_request("GET", "/")
152+
153+
136154
@pytest.mark.parametrize("method", ("method", "cls", "static"))
137155
@validate_transaction_metrics(name="", group="Uri")
138156
def test_app_with_descriptor(method):

tests/agent_features/test_web_transaction.py

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,92 +14,132 @@
1414
# limitations under the License.
1515

1616
import gc
17-
import webtest
18-
import pytest
1917
import time
18+
19+
import pytest
20+
import webtest
21+
from testing_support.fixtures import validate_attributes, validate_transaction_metrics
22+
from testing_support.sample_applications import simple_app, simple_app_raw
23+
24+
import newrelic.packages.six as six
2025
from newrelic.api.application import application_instance
2126
from newrelic.api.web_transaction import WebTransaction
22-
from testing_support.fixtures import (validate_transaction_metrics,
23-
validate_attributes)
24-
from testing_support.sample_applications import simple_app
25-
import newrelic.packages.six as six
27+
from newrelic.api.wsgi_application import wsgi_application
28+
2629
application = webtest.TestApp(simple_app)
2730

2831

2932
# TODO: WSGI metrics must not be generated for a WebTransaction
3033
METRICS = (
31-
('Python/WSGI/Input/Bytes', None),
32-
('Python/WSGI/Input/Time', None),
33-
('Python/WSGI/Input/Calls/read', None),
34-
('Python/WSGI/Input/Calls/readline', None),
35-
('Python/WSGI/Input/Calls/readlines', None),
36-
('Python/WSGI/Output/Bytes', None),
37-
('Python/WSGI/Output/Time', None),
38-
('Python/WSGI/Output/Calls/yield', None),
39-
('Python/WSGI/Output/Calls/write', None),
34+
("Python/WSGI/Input/Bytes", None),
35+
("Python/WSGI/Input/Time", None),
36+
("Python/WSGI/Input/Calls/read", None),
37+
("Python/WSGI/Input/Calls/readline", None),
38+
("Python/WSGI/Input/Calls/readlines", None),
39+
("Python/WSGI/Output/Bytes", None),
40+
("Python/WSGI/Output/Time", None),
41+
("Python/WSGI/Output/Calls/yield", None),
42+
("Python/WSGI/Output/Calls/write", None),
4043
)
4144

4245

43-
# TODO: Add rollup_metrics=METRICS
46+
# Test for presence of framework and dispatcher info based on whether framework is specified
47+
@validate_transaction_metrics(
48+
name="test", custom_metrics=[("Python/Framework/framework/v1", 1), ("Python/Dispatcher/dispatcher/v1.0.0", 1)]
49+
)
50+
def test_dispatcher_and_framework_metrics():
51+
inner_wsgi_decorator = wsgi_application(
52+
name="test", framework=("framework", "v1"), dispatcher=("dispatcher", "v1.0.0")
53+
)
54+
decorated_application = inner_wsgi_decorator(simple_app_raw)
55+
56+
application = webtest.TestApp(decorated_application)
57+
application.get("/")
58+
59+
60+
# Test for presence of framework and dispatcher info under existing transaction
4461
@validate_transaction_metrics(
45-
'test_base_web_transaction',
46-
group='Test')
47-
@validate_attributes('agent',
48-
[
49-
'request.headers.accept', 'request.headers.contentLength',
50-
'request.headers.contentType', 'request.headers.host',
51-
'request.headers.referer', 'request.headers.userAgent', 'request.method',
52-
'request.uri', 'response.status', 'response.headers.contentLength',
53-
'response.headers.contentType', 'request.parameters.foo',
54-
'request.parameters.boo', 'webfrontend.queue.seconds',
55-
])
56-
@pytest.mark.parametrize('use_bytes', (True, False))
62+
name="test", custom_metrics=[("Python/Framework/framework/v1", 1), ("Python/Dispatcher/dispatcher/v1.0.0", 1)]
63+
)
64+
def test_double_wrapped_dispatcher_and_framework_metrics():
65+
inner_wsgi_decorator = wsgi_application(
66+
name="test", framework=("framework", "v1"), dispatcher=("dispatcher", "v1.0.0")
67+
)
68+
decorated_application = inner_wsgi_decorator(simple_app_raw)
69+
70+
outer_wsgi_decorator = wsgi_application(name="double_wrapped")
71+
double_decorated_application = outer_wsgi_decorator(decorated_application)
72+
73+
application = webtest.TestApp(double_decorated_application)
74+
application.get("/")
75+
76+
77+
# TODO: Add rollup_metrics=METRICS
78+
@validate_transaction_metrics("test_base_web_transaction", group="Test")
79+
@validate_attributes(
80+
"agent",
81+
[
82+
"request.headers.accept",
83+
"request.headers.contentLength",
84+
"request.headers.contentType",
85+
"request.headers.host",
86+
"request.headers.referer",
87+
"request.headers.userAgent",
88+
"request.method",
89+
"request.uri",
90+
"response.status",
91+
"response.headers.contentLength",
92+
"response.headers.contentType",
93+
"request.parameters.foo",
94+
"request.parameters.boo",
95+
"webfrontend.queue.seconds",
96+
],
97+
)
98+
@pytest.mark.parametrize("use_bytes", (True, False))
5799
def test_base_web_transaction(use_bytes):
58100
application = application_instance()
59101

60102
request_headers = {
61-
'Accept': 'text/plain',
62-
'Content-Length': '0',
63-
'Content-Type': 'text/plain',
64-
'Host': 'localhost',
65-
'Referer': 'http://example.com?q=1&boat=⛵',
66-
'User-Agent': 'potato',
67-
'X-Request-Start': str(time.time() - 0.2),
68-
'newRelic': 'invalid',
103+
"Accept": "text/plain",
104+
"Content-Length": "0",
105+
"Content-Type": "text/plain",
106+
"Host": "localhost",
107+
"Referer": "http://example.com?q=1&boat=⛵",
108+
"User-Agent": "potato",
109+
"X-Request-Start": str(time.time() - 0.2),
110+
"newRelic": "invalid",
69111
}
70112

71113
if use_bytes:
72114
byte_headers = {}
73115

74116
for name, value in request_headers.items():
75-
name = name.encode('utf-8')
117+
name = name.encode("utf-8")
76118
try:
77-
value = value.encode('utf-8')
119+
value = value.encode("utf-8")
78120
except UnicodeDecodeError:
79121
assert six.PY2
80122
byte_headers[name] = value
81123

82124
request_headers = byte_headers
83125

84126
transaction = WebTransaction(
85-
application,
86-
'test_base_web_transaction',
87-
group='Test',
88-
scheme='http',
89-
host='localhost',
90-
port=8000,
91-
request_method='HEAD',
92-
request_path='/foobar',
93-
query_string='foo=bar&boo=baz',
94-
headers=request_headers.items(),
127+
application,
128+
"test_base_web_transaction",
129+
group="Test",
130+
scheme="http",
131+
host="localhost",
132+
port=8000,
133+
request_method="HEAD",
134+
request_path="/foobar",
135+
query_string="foo=bar&boo=baz",
136+
headers=request_headers.items(),
95137
)
96138

97139
if use_bytes:
98-
response_headers = ((b'Content-Length', b'0'),
99-
(b'Content-Type', b'text/plain'))
140+
response_headers = ((b"Content-Length", b"0"), (b"Content-Type", b"text/plain"))
100141
else:
101-
response_headers = (('Content-Length', '0'),
102-
('Content-Type', 'text/plain'))
142+
response_headers = (("Content-Length", "0"), ("Content-Type", "text/plain"))
103143

104144
with transaction:
105145
transaction.process_response(200, response_headers)
@@ -117,8 +157,8 @@ def validate_no_garbage():
117157

118158

119159
@validate_transaction_metrics(
120-
name='',
121-
group='Uri',
160+
name="",
161+
group="Uri",
122162
)
123163
def test_wsgi_app_memory(validate_no_garbage):
124-
application.get('/')
164+
application.get("/")

tests/testing_support/sample_applications.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,7 @@ def simple_exceptional_app(environ, start_response):
128128
raise ValueError("Transaction had bad value")
129129

130130

131-
@wsgi_application()
132-
def simple_app(environ, start_response):
131+
def simple_app_raw(environ, start_response):
133132
status = "200 OK"
134133

135134
_logger.info("Starting response")
@@ -138,6 +137,9 @@ def simple_app(environ, start_response):
138137
return []
139138

140139

140+
simple_app = wsgi_application()(simple_app_raw)
141+
142+
141143
@wsgi_application()
142144
def simple_custom_event_app(environ, start_response):
143145

0 commit comments

Comments
 (0)