Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for Waitress #797

Merged
merged 11 commits into from
May 1, 2023
17 changes: 8 additions & 9 deletions newrelic/hooks/adapter_waitress.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import newrelic.api.wsgi_application
import newrelic.api.in_function
from newrelic.api.in_function import wrap_in_function
from newrelic.api.wsgi_application import WSGIApplicationWrapper
from newrelic.common.package_version_utils import get_package_version

def instrument_waitress_server(module):

def wrap_wsgi_application_entry_point(server, application,
*args, **kwargs):
application = newrelic.api.wsgi_application.WSGIApplicationWrapper(
application)
def instrument_waitress_server(module):
def wrap_wsgi_application_entry_point(server, application, *args, **kwargs):
dispatcher_details = ("Waitress", get_package_version("waitress"))
application = WSGIApplicationWrapper(application, dispatcher=dispatcher_details)
args = [server, application] + list(args)
return (args, kwargs)

newrelic.api.in_function.wrap_in_function(module,
'WSGIServer.__init__', wrap_wsgi_application_entry_point)
wrap_in_function(module, "WSGIServer.__init__", wrap_wsgi_application_entry_point)
54 changes: 54 additions & 0 deletions tests/adapter_waitress/_application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright 2010 New Relic, Inc.
#
# 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 threading import Thread
from time import sleep

from testing_support.sample_applications import (
raise_exception_application,
raise_exception_finalize,
raise_exception_response,
simple_app_raw,
)
from testing_support.util import get_open_port


def sample_application(environ, start_response):
path_info = environ.get("PATH_INFO")

if path_info.startswith("/raise-exception-application"):
return raise_exception_application(environ, start_response)
elif path_info.startswith("/raise-exception-response"):
return raise_exception_response(environ, start_response)
elif path_info.startswith("/raise-exception-finalize"):
return raise_exception_finalize(environ, start_response)

return simple_app_raw(environ, start_response)


def setup_application():
port = get_open_port()

def run_wsgi():
from waitress import serve

serve(sample_application, host="127.0.0.1", port=port)

wsgi_thread = Thread(target=run_wsgi)
wsgi_thread.daemon = True
wsgi_thread.start()

sleep(1)

return port
40 changes: 40 additions & 0 deletions tests/adapter_waitress/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2010 New Relic, Inc.
#
# 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 pytest
import webtest
from testing_support.fixtures import ( # noqa: F401; pylint: disable=W0611
collector_agent_registration_fixture,
collector_available_fixture,
)

_default_settings = {
"transaction_tracer.explain_threshold": 0.0,
"transaction_tracer.transaction_threshold": 0.0,
"transaction_tracer.stack_trace_threshold": 0.0,
"debug.log_data_collector_payloads": True,
"debug.record_transaction_failure": True,
}

collector_agent_registration = collector_agent_registration_fixture(
app_name="Python Agent Test (Waitress)", default_settings=_default_settings
)


@pytest.fixture(autouse=True, scope="session")
def target_application():
import _application

port = _application.setup_application()
return webtest.TestApp("http://localhost:%d" % port)
101 changes: 101 additions & 0 deletions tests/adapter_waitress/test_wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright 2010 New Relic, Inc.
#
# 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 testing_support.fixtures import (
override_application_settings,
raise_background_exceptions,
wait_for_background_threads,
)
from testing_support.validators.validate_transaction_errors import (
validate_transaction_errors,
)
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
)

from newrelic.common.package_version_utils import get_package_version

WAITRESS_VERSION = get_package_version("waitress")


@override_application_settings({"transaction_name.naming_scheme": "framework"})
def test_wsgi_application_index(target_application):
@validate_transaction_metrics(
"_application:sample_application",
custom_metrics=[
("Python/Dispatcher/Waitress/%s" % WAITRESS_VERSION, 1),
],
)
@raise_background_exceptions()
@wait_for_background_threads()
def _test():
response = target_application.get("/")
assert response.status == "200 OK"

_test()


@override_application_settings({"transaction_name.naming_scheme": "framework"})
def test_raise_exception_application(target_application):
@validate_transaction_errors(["builtins:RuntimeError"])
@validate_transaction_metrics(
"_application:sample_application",
custom_metrics=[
("Python/Dispatcher/Waitress/%s" % WAITRESS_VERSION, 1),
],
)
@raise_background_exceptions()
@wait_for_background_threads()
def _test():
response = target_application.get("/raise-exception-application/", status=500)
assert response.status == "500 Internal Server Error"

_test()


@override_application_settings({"transaction_name.naming_scheme": "framework"})
def test_raise_exception_response(target_application):
@validate_transaction_errors(["builtins:RuntimeError"])
@validate_transaction_metrics(
"_application:sample_application",
custom_metrics=[
("Python/Dispatcher/Waitress/%s" % WAITRESS_VERSION, 1),
],
)
@raise_background_exceptions()
@wait_for_background_threads()
def _test():
response = target_application.get("/raise-exception-response/", status=500)
assert response.status == "500 Internal Server Error"

_test()


@override_application_settings({"transaction_name.naming_scheme": "framework"})
def test_raise_exception_finalize(target_application):
@validate_transaction_errors(["builtins:RuntimeError"])
@validate_transaction_metrics(
"_application:sample_application",
custom_metrics=[
("Python/Dispatcher/Waitress/%s" % WAITRESS_VERSION, 1),
],
)
@raise_background_exceptions()
@wait_for_background_threads()
def _test():
response = target_application.get("/raise-exception-finalize/", status=500)
assert response.status == "500 Internal Server Error"

_test()
43 changes: 39 additions & 4 deletions tests/testing_support/sample_applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def fully_featured_app(environ, start_response):
environ["wsgi.input"].readlines()

if use_user_attrs:

for attr, val in _custom_parameters.items():
add_custom_attribute(attr, val)

Expand All @@ -97,7 +96,6 @@ def fully_featured_app(environ, start_response):
n_errors = int(environ.get("n_errors", 1))
for i in range(n_errors):
try:

# append number to stats engine to get unique errors, so they
# don't immediately get filtered out.

Expand All @@ -122,7 +120,6 @@ def fully_featured_app(environ, start_response):

@wsgi_application()
def simple_exceptional_app(environ, start_response):

start_response("500 :(", [])

raise ValueError("Transaction had bad value")
Expand All @@ -140,9 +137,47 @@ def simple_app_raw(environ, start_response):
simple_app = wsgi_application()(simple_app_raw)


def raise_exception_application(environ, start_response):
raise RuntimeError("raise_exception_application")

status = "200 OK"
output = b"WSGI RESPONSE"

response_headers = [("Content-type", "text/plain"), ("Content-Length", str(len(output)))]
start_response(status, response_headers)

return [output]


def raise_exception_response(environ, start_response):
status = "200 OK"

response_headers = [("Content-type", "text/plain")]
start_response(status, response_headers)

yield b"WSGI"

raise RuntimeError("raise_exception_response")

yield b" "
yield b"RESPONSE"


def raise_exception_finalize(environ, start_response):
status = "200 OK"

response_headers = [("Content-type", "text/plain")]
start_response(status, response_headers)

try:
yield b"WSGI RESPONSE"

finally:
raise RuntimeError("raise_exception_finalize")


@wsgi_application()
def simple_custom_event_app(environ, start_response):

params = {"snowman": "\u2603", "foo": "bar"}
record_custom_event("SimpleAppEvent", params)

Expand Down
8 changes: 8 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ envlist =
python-adapter_hypercorn-py38-hypercorn{0010,0011,0012,0013},
python-adapter_uvicorn-py37-uvicorn03,
python-adapter_uvicorn-{py37,py38,py39,py310,py311}-uvicornlatest,
python-adapter_waitress-{py37,py38,py39}-waitress010404,
python-adapter_waitress-{py37,py38,py39,py310}-waitress02,
python-adapter_waitress-{py37,py38,py39,py310,py311}-waitresslatest,
python-agent_features-{py27,py37,py38,py39,py310,py311}-{with,without}_extensions,
python-agent_features-{pypy,pypy37}-without_extensions,
python-agent_streaming-py27-grpc0125-{with,without}_extensions,
Expand Down Expand Up @@ -192,6 +195,10 @@ deps =
adapter_uvicorn-uvicorn03: uvicorn<0.4
adapter_uvicorn-uvicorn014: uvicorn<0.15
adapter_uvicorn-uvicornlatest: uvicorn
adapter_waitress: WSGIProxy2
adapter_waitress-waitress010404: waitress<1.4.5
adapter_waitress-waitress02: waitress<2.1
adapter_waitress-waitresslatest: waitress
agent_features: beautifulsoup4
application_celery: celery<6.0
application_celery-py{py37,37}: importlib-metadata<5.0
Expand Down Expand Up @@ -418,6 +425,7 @@ changedir =
adapter_gunicorn: tests/adapter_gunicorn
adapter_hypercorn: tests/adapter_hypercorn
adapter_uvicorn: tests/adapter_uvicorn
adapter_waitress: tests/adapter_waitress
agent_features: tests/agent_features
agent_streaming: tests/agent_streaming
agent_unittests: tests/agent_unittests
Expand Down