Skip to content

Commit

Permalink
Add tests for Waitress (#797)
Browse files Browse the repository at this point in the history
* Change import format

* Initial commit

* Add more tests to adapter_waitress

* Remove commented out code

* [Mega-Linter] Apply linters fixes

* Add assertions to all tests

* Add more NR testing to waitress

---------

Co-authored-by: lrafeei <lrafeei@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored May 1, 2023
1 parent 7103506 commit be4fb3d
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 13 deletions.
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

0 comments on commit be4fb3d

Please sign in to comment.