From 27c874c438c85faf6ba08fba359316d226a07735 Mon Sep 17 00:00:00 2001 From: Timothy Pansino <11214426+TimPansino@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:06:57 -0800 Subject: [PATCH] Deprecate get_browser_timing_footer API (#999) * Add nonce to CSP in browser agent * Adjust nonce position * Add testing for browser timing nonces * Deprecated browser timing footer APIs. * Full rip out of browser timing footer * Remove cross agent tests for RUM footer (per repo) * Update cat_map tests * Adjust browser header generation timing accuracy * Fix browser tests * Linting * Apply suggestions from code review --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- newrelic/api/asgi_application.py | 13 +- newrelic/api/transaction.py | 9 +- newrelic/api/web_transaction.py | 623 +++++++++--------- newrelic/api/wsgi_application.py | 18 +- newrelic/hooks/framework_django.py | 89 +-- newrelic/hooks/middleware_flask_compress.py | 80 +-- tests/agent_features/test_asgi_browser.py | 56 +- tests/agent_features/test_browser.py | 99 +-- ...n_event_data_and_some_browser_stuff_too.py | 27 +- .../fixtures/rum_client_config.json | 91 --- .../close-body-in-comment.html | 26 - .../dynamic-iframe.html | 35 - tests/cross_agent/test_cat_map.py | 6 +- tests/cross_agent/test_rum_client_config.py | 145 ---- tests/framework_bottle/test_application.py | 260 ++++---- tests/framework_cherrypy/test_application.py | 120 ++-- tests/framework_django/templates/main.html | 1 - tests/framework_django/test_application.py | 539 +++++++-------- tests/framework_django/views.py | 81 +-- tests/framework_flask/_test_compress.py | 80 ++- tests/framework_flask/test_application.py | 301 +++++---- tests/framework_flask/test_compress.py | 99 +-- tests/framework_pyramid/test_application.py | 200 +++--- tests/testing_support/sample_applications.py | 5 +- .../validators/validate_browser_attributes.py | 16 +- 25 files changed, 1304 insertions(+), 1715 deletions(-) delete mode 100644 tests/cross_agent/fixtures/rum_client_config.json delete mode 100644 tests/cross_agent/fixtures/rum_footer_insertion_location/close-body-in-comment.html delete mode 100644 tests/cross_agent/fixtures/rum_footer_insertion_location/dynamic-iframe.html delete mode 100644 tests/cross_agent/test_rum_client_config.py diff --git a/newrelic/api/asgi_application.py b/newrelic/api/asgi_application.py index 201e8643e6..475faa7cbb 100644 --- a/newrelic/api/asgi_application.py +++ b/newrelic/api/asgi_application.py @@ -157,16 +157,9 @@ async def send_inject_browser_agent(self, message): # if there's a valid body string, attempt to insert the HTML if verify_body_exists(self.body): - header = self.transaction.browser_timing_header() - if not header: - # If there's no header, abort browser monitoring injection - await self.send_buffered() - return - - footer = self.transaction.browser_timing_footer() - browser_agent_data = six.b(header) + six.b(footer) - - body = insert_html_snippet(self.body, lambda: browser_agent_data, self.search_maximum) + body = insert_html_snippet( + self.body, lambda: six.b(self.transaction.browser_timing_header()), self.search_maximum + ) # If we have inserted the browser agent if len(body) != len(self.body): diff --git a/newrelic/api/transaction.py b/newrelic/api/transaction.py index 3accbdeb7f..1fcb453b4a 100644 --- a/newrelic/api/transaction.py +++ b/newrelic/api/transaction.py @@ -309,7 +309,7 @@ def __init__(self, application, enabled=None, source=None): self.synthetics_job_id = None self.synthetics_monitor_id = None self.synthetics_header = None - + # Synthetics Info Header self.synthetics_type = None self.synthetics_initiator = None @@ -1912,9 +1912,10 @@ def get_browser_timing_header(nonce=None): def get_browser_timing_footer(nonce=None): - transaction = current_transaction() - if transaction and hasattr(transaction, "browser_timing_footer"): - return transaction.browser_timing_footer(nonce) + warnings.warn( + "The get_browser_timing_footer function is deprecated. Please migrate to only using the get_browser_timing_header API instead.", + DeprecationWarning, + ) return "" diff --git a/newrelic/api/web_transaction.py b/newrelic/api/web_transaction.py index d74f1d6d35..5416f2e802 100644 --- a/newrelic/api/web_transaction.py +++ b/newrelic/api/web_transaction.py @@ -13,8 +13,8 @@ # limitations under the License. import functools -import time import logging +import time import warnings try: @@ -24,23 +24,22 @@ from newrelic.api.application import Application, application_instance from newrelic.api.transaction import Transaction, current_transaction - -from newrelic.common.async_proxy import async_proxy, TransactionContext -from newrelic.common.encoding_utils import (obfuscate, json_encode, - decode_newrelic_header, ensure_str) - +from newrelic.common.async_proxy import TransactionContext, async_proxy +from newrelic.common.encoding_utils import ( + decode_newrelic_header, + ensure_str, + json_encode, + obfuscate, +) +from newrelic.common.object_names import callable_name +from newrelic.common.object_wrapper import FunctionWrapper, wrap_object from newrelic.core.attribute import create_attributes, process_user_attribute from newrelic.core.attribute_filter import DST_BROWSER_MONITORING, DST_NONE - from newrelic.packages import six -from newrelic.common.object_names import callable_name -from newrelic.common.object_wrapper import FunctionWrapper, wrap_object - _logger = logging.getLogger(__name__) -_js_agent_header_fragment = '' -_js_agent_footer_fragment = '' +_js_agent_header_fragment = '' # Seconds since epoch for Jan 1 2000 JAN_1_2000 = time.mktime((2000, 1, 1, 0, 0, 0, 0, 0, 0)) @@ -80,8 +79,8 @@ def _parse_time_stamp(time_stamp): return converted_time -TRUE_VALUES = {'on', 'true', '1'} -FALSE_VALUES = {'off', 'false', '0'} +TRUE_VALUES = {"on", "true", "1"} +FALSE_VALUES = {"off", "false", "0"} def _lookup_environ_setting(environ, name, default=False): @@ -113,11 +112,11 @@ def _parse_synthetics_header(header): version = int(header[0]) if version == 1: - synthetics['version'] = version - synthetics['account_id'] = int(header[1]) - synthetics['resource_id'] = header[2] - synthetics['job_id'] = header[3] - synthetics['monitor_id'] = header[4] + synthetics["version"] = version + synthetics["account_id"] = int(header[1]) + synthetics["resource_id"] = header[2] + synthetics["job_id"] = header[3] + synthetics["monitor_id"] = header[4] except Exception: return @@ -135,10 +134,10 @@ def _parse_synthetics_info_header(header): version = int(header.get("version")) if version == 1: - synthetics_info['version'] = version - synthetics_info['type'] = header.get("type") - synthetics_info['initiator'] = header.get("initiator") - synthetics_info['attributes'] = header.get("attributes") + synthetics_info["version"] = version + synthetics_info["type"] = header.get("type") + synthetics_info["initiator"] = header.get("initiator") + synthetics_info["attributes"] = header.get("attributes") except Exception: return @@ -148,11 +147,11 @@ def _parse_synthetics_info_header(header): def _remove_query_string(url): url = ensure_str(url) out = urlparse.urlsplit(url) - return urlparse.urlunsplit((out.scheme, out.netloc, out.path, '', '')) + return urlparse.urlunsplit((out.scheme, out.netloc, out.path, "", "")) def _is_websocket(environ): - return environ.get('HTTP_UPGRADE', '').lower() == 'websocket' + return environ.get("HTTP_UPGRADE", "").lower() == "websocket" def _encode_nonce(nonce): @@ -164,20 +163,27 @@ def _encode_nonce(nonce): class WebTransaction(Transaction): unicode_error_reported = False - QUEUE_TIME_HEADERS = ('x-request-start', 'x-queue-start') - - def __init__(self, application, name, group=None, - scheme=None, host=None, port=None, request_method=None, - request_path=None, query_string=None, headers=None, - enabled=None, source=None): - + QUEUE_TIME_HEADERS = ("x-request-start", "x-queue-start") + + def __init__( + self, + application, + name, + group=None, + scheme=None, + host=None, + port=None, + request_method=None, + request_path=None, + query_string=None, + headers=None, + enabled=None, + source=None, + ): super(WebTransaction, self).__init__(application, enabled, source=source) - # Flags for tracking whether RUM header and footer have been - # generated. - + # Flag for tracking whether RUM header has been generated. self.rum_header_generated = False - self.rum_footer_generated = False if not self.enabled: return @@ -215,9 +221,7 @@ def __init__(self, application, name, group=None, if query_string and not self._settings.high_security: query_string = ensure_str(query_string) try: - params = urlparse.parse_qs( - query_string, - keep_blank_values=True) + params = urlparse.parse_qs(query_string, keep_blank_values=True) self._request_params.update(params) except Exception: pass @@ -229,7 +233,7 @@ def __init__(self, application, name, group=None, if name is not None: self.set_transaction_name(name, group, priority=1) elif request_path is not None: - self.set_transaction_name(request_path, 'Uri', priority=1) + self.set_transaction_name(request_path, "Uri", priority=1) def _process_queue_time(self): for queue_time_header in self.QUEUE_TIME_HEADERS: @@ -239,7 +243,7 @@ def _process_queue_time(self): value = ensure_str(value) try: - if value.startswith('t='): + if value.startswith("t="): self.queue_start = _parse_time_stamp(float(value[2:])) else: self.queue_start = _parse_time_stamp(float(value)) @@ -254,47 +258,37 @@ def _process_synthetics_header(self): settings = self._settings - if settings.synthetics.enabled and \ - settings.trusted_account_ids and \ - settings.encoding_key: - + if settings.synthetics.enabled and settings.trusted_account_ids and settings.encoding_key: # Synthetics Header - encoded_header = self._request_headers.get('x-newrelic-synthetics') + encoded_header = self._request_headers.get("x-newrelic-synthetics") encoded_header = encoded_header and ensure_str(encoded_header) if not encoded_header: return - decoded_header = decode_newrelic_header( - encoded_header, - settings.encoding_key) + decoded_header = decode_newrelic_header(encoded_header, settings.encoding_key) synthetics = _parse_synthetics_header(decoded_header) # Synthetics Info Header - encoded_info_header = self._request_headers.get('x-newrelic-synthetics-info') + encoded_info_header = self._request_headers.get("x-newrelic-synthetics-info") encoded_info_header = encoded_info_header and ensure_str(encoded_info_header) - decoded_info_header = decode_newrelic_header( - encoded_info_header, - settings.encoding_key) + decoded_info_header = decode_newrelic_header(encoded_info_header, settings.encoding_key) synthetics_info = _parse_synthetics_info_header(decoded_info_header) - if synthetics and \ - synthetics['account_id'] in \ - settings.trusted_account_ids: - + if synthetics and synthetics["account_id"] in settings.trusted_account_ids: # Save obfuscated headers, because we will pass them along # unchanged in all external requests. self.synthetics_header = encoded_header - self.synthetics_resource_id = synthetics['resource_id'] - self.synthetics_job_id = synthetics['job_id'] - self.synthetics_monitor_id = synthetics['monitor_id'] + self.synthetics_resource_id = synthetics["resource_id"] + self.synthetics_job_id = synthetics["job_id"] + self.synthetics_monitor_id = synthetics["monitor_id"] if synthetics_info: self.synthetics_info_header = encoded_info_header - self.synthetics_type = synthetics_info['type'] - self.synthetics_initiator = synthetics_info['initiator'] - self.synthetics_attributes = synthetics_info['attributes'] + self.synthetics_type = synthetics_info["type"] + self.synthetics_initiator = synthetics_info["initiator"] + self.synthetics_attributes = synthetics_info["attributes"] def _process_context_headers(self): # Process the New Relic cross process ID header and extract @@ -302,11 +296,9 @@ def _process_context_headers(self): if self._settings.distributed_tracing.enabled: self.accept_distributed_trace_headers(self._request_headers) else: - client_cross_process_id = \ - self._request_headers.get('x-newrelic-id') - txn_header = self._request_headers.get('x-newrelic-transaction') - self._process_incoming_cat_headers(client_cross_process_id, - txn_header) + client_cross_process_id = self._request_headers.get("x-newrelic-id") + txn_header = self._request_headers.get("x-newrelic-transaction") + self._process_incoming_cat_headers(client_cross_process_id, txn_header) def process_response(self, status_code, response_headers): """Processes response status and headers, extracting any @@ -345,50 +337,41 @@ def process_response(self, status_code, response_headers): # Generate CAT response headers try: - read_length = int(self._request_headers.get('content-length')) + read_length = int(self._request_headers.get("content-length")) except Exception: read_length = -1 return self._generate_response_headers(read_length) def _update_agent_attributes(self): - if 'accept' in self._request_headers: - self._add_agent_attribute('request.headers.accept', - self._request_headers['accept']) + if "accept" in self._request_headers: + self._add_agent_attribute("request.headers.accept", self._request_headers["accept"]) try: - content_length = int(self._request_headers['content-length']) - self._add_agent_attribute('request.headers.contentLength', - content_length) + content_length = int(self._request_headers["content-length"]) + self._add_agent_attribute("request.headers.contentLength", content_length) except: pass - if 'content-type' in self._request_headers: - self._add_agent_attribute('request.headers.contentType', - self._request_headers['content-type']) - if 'host' in self._request_headers: - self._add_agent_attribute('request.headers.host', - self._request_headers['host']) - if 'referer' in self._request_headers: - self._add_agent_attribute('request.headers.referer', - _remove_query_string(self._request_headers['referer'])) - if 'user-agent' in self._request_headers: - self._add_agent_attribute('request.headers.userAgent', - self._request_headers['user-agent']) + if "content-type" in self._request_headers: + self._add_agent_attribute("request.headers.contentType", self._request_headers["content-type"]) + if "host" in self._request_headers: + self._add_agent_attribute("request.headers.host", self._request_headers["host"]) + if "referer" in self._request_headers: + self._add_agent_attribute("request.headers.referer", _remove_query_string(self._request_headers["referer"])) + if "user-agent" in self._request_headers: + self._add_agent_attribute("request.headers.userAgent", self._request_headers["user-agent"]) if self._request_method: - self._add_agent_attribute('request.method', self._request_method) + self._add_agent_attribute("request.method", self._request_method) if self._request_uri: - self._add_agent_attribute('request.uri', self._request_uri) + self._add_agent_attribute("request.uri", self._request_uri) try: - content_length = int(self._response_headers['content-length']) - self._add_agent_attribute('response.headers.contentLength', - content_length) + content_length = int(self._response_headers["content-length"]) + self._add_agent_attribute("response.headers.contentLength", content_length) except: pass - if 'content-type' in self._response_headers: - self._add_agent_attribute('response.headers.contentType', - self._response_headers['content-type']) + if "content-type" in self._response_headers: + self._add_agent_attribute("response.headers.contentType", self._response_headers["content-type"]) if self._response_code is not None: - self._add_agent_attribute('response.status', - str(self._response_code)) + self._add_agent_attribute("response.status", str(self._response_code)) return super(WebTransaction, self)._update_agent_attributes() @@ -402,39 +385,39 @@ def browser_timing_header(self, nonce=None): """ if not self.enabled: - return '' + return "" if self._state != self.STATE_RUNNING: - return '' + return "" if self.background_task: - return '' + return "" if self.ignore_transaction: - return '' + return "" if not self._settings: - return '' + return "" if not self._settings.browser_monitoring.enabled: - return '' + return "" if not self._settings.license_key: - return '' + return "" # Don't return the header a second time if it has already # been generated. if self.rum_header_generated: - return '' + return "" # Requirement is that the first 13 characters of the account # license key is used as the key when obfuscating values for - # the RUM footer. Will not be able to perform the obfuscation + # the RUM configuration. Will not be able to perform the obfuscation # if license key isn't that long for some reason. if len(self._settings.license_key) < 13: - return '' + return "" # Return the RUM header only if the agent received a valid value # for js_agent_loader from the data collector. The data @@ -443,7 +426,48 @@ def browser_timing_header(self, nonce=None): # 'none'. if self._settings.js_agent_loader: - header = _js_agent_header_fragment % (_encode_nonce(nonce), self._settings.js_agent_loader) + # Make sure we freeze the path. + + self._freeze_path() + + # When obfuscating values for the browser agent configuration, we only use the + # first 13 characters of the account license key. + + obfuscation_key = self._settings.license_key[:13] + + attributes = {} + + user_attributes = {} + for attr in self.user_attributes: + if attr.destinations & DST_BROWSER_MONITORING: + user_attributes[attr.name] = attr.value + + if user_attributes: + attributes["u"] = user_attributes + + request_parameters = self.request_parameters + request_parameter_attributes = self.filter_request_parameters(request_parameters) + agent_attributes = {} + for attr in request_parameter_attributes: + if attr.destinations & DST_BROWSER_MONITORING: + agent_attributes[attr.name] = attr.value + + if agent_attributes: + attributes["a"] = agent_attributes + + # create the data structure that pull all our data in + + broswer_agent_configuration = self.browser_monitoring_intrinsics(obfuscation_key) + + if attributes: + attributes = obfuscate(json_encode(attributes), obfuscation_key) + broswer_agent_configuration["atts"] = attributes + + header = _js_agent_header_fragment % ( + _encode_nonce(nonce), + json_encode(broswer_agent_configuration), + self._settings.js_agent_loader, + ) # To avoid any issues with browser encodings, we will make sure # that the javascript we inject for the browser agent is ASCII @@ -457,25 +481,22 @@ def browser_timing_header(self, nonce=None): try: if six.PY2: - header = header.encode('ascii') + header = header.encode("ascii") else: - header.encode('ascii') + header.encode("ascii") except UnicodeError: if not WebTransaction.unicode_error_reported: - _logger.error('ASCII encoding of js-agent-header failed.', - header) + _logger.error("ASCII encoding of js-agent-header failed.", header) WebTransaction.unicode_error_reported = True - header = '' + header = "" else: - header = '' + header = "" # We remember if we have returned a non empty string value and - # if called a second time we will not return it again. The flag - # will also be used to check whether the footer should be - # generated. + # if called a second time we will not return it again. if header: self.rum_header_generated = True @@ -483,102 +504,12 @@ def browser_timing_header(self, nonce=None): return header def browser_timing_footer(self, nonce=None): - """Returns the JavaScript footer to be included in any HTML - response to perform real user monitoring. This function returns - the footer as a native Python string. In Python 2 native strings - are stored as bytes. In Python 3 native strings are stored as - unicode. - - """ - - if not self.enabled: - return '' - - if self._state != self.STATE_RUNNING: - return '' - - if self.ignore_transaction: - return '' - - # Only generate a footer if the header had already been - # generated and we haven't already generated the footer. - - if not self.rum_header_generated: - return '' - - if self.rum_footer_generated: - return '' - - # Make sure we freeze the path. - - self._freeze_path() - - # When obfuscating values for the footer, we only use the - # first 13 characters of the account license key. - - obfuscation_key = self._settings.license_key[:13] - - attributes = {} - - user_attributes = {} - for attr in self.user_attributes: - if attr.destinations & DST_BROWSER_MONITORING: - user_attributes[attr.name] = attr.value - - if user_attributes: - attributes['u'] = user_attributes - - request_parameters = self.request_parameters - request_parameter_attributes = self.filter_request_parameters( - request_parameters) - agent_attributes = {} - for attr in request_parameter_attributes: - if attr.destinations & DST_BROWSER_MONITORING: - agent_attributes[attr.name] = attr.value - - if agent_attributes: - attributes['a'] = agent_attributes - - # create the data structure that pull all our data in - - footer_data = self.browser_monitoring_intrinsics(obfuscation_key) - - if attributes: - attributes = obfuscate(json_encode(attributes), obfuscation_key) - footer_data['atts'] = attributes - - footer = _js_agent_footer_fragment % (_encode_nonce(nonce), json_encode(footer_data)) - - # To avoid any issues with browser encodings, we will make sure that - # the javascript we inject for the browser agent is ASCII encodable. - # Since we obfuscate all agent and user attributes, and the transaction - # name with base 64 encoding, this will preserve those strings, if - # they have values outside of the ASCII character set. - # In the case of Python 2, we actually then use the encoded value - # as we need a native string, which for Python 2 is a byte string. - # If encoding as ASCII fails we will return an empty string. - - try: - if six.PY2: - footer = footer.encode('ascii') - else: - footer.encode('ascii') - - except UnicodeError: - if not WebTransaction.unicode_error_reported: - _logger.error('ASCII encoding of js-agent-footer failed.', - footer) - WebTransaction.unicode_error_reported = True - - footer = '' - - # We remember if we have returned a non empty string value and - # if called a second time we will not return it again. - - if footer: - self.rum_footer_generated = True - - return footer + """Deprecated API that has been replaced entirely by browser_timing_header().""" + warnings.warn( + "The browser_timing_footer function is deprecated. Please migrate to only using the browser_timing_header api instead.", + DeprecationWarning, + ) + return "" def browser_monitoring_intrinsics(self, obfuscation_key): txn_name = obfuscate(self.path, obfuscation_key) @@ -603,7 +534,7 @@ def browser_monitoring_intrinsics(self, obfuscation_key): if self._settings.browser_monitoring.ssl_for_http is not None: ssl_for_http = self._settings.browser_monitoring.ssl_for_http - intrinsics['sslForHttp'] = ssl_for_http + intrinsics["sslForHttp"] = ssl_for_http return intrinsics @@ -616,16 +547,16 @@ def __init__(self, environ): @staticmethod def _to_wsgi(key): key = key.upper() - if key == 'CONTENT-LENGTH': - return 'CONTENT_LENGTH' - elif key == 'CONTENT-TYPE': - return 'CONTENT_TYPE' - return 'HTTP_' + key.replace('-', '_') + if key == "CONTENT-LENGTH": + return "CONTENT_LENGTH" + elif key == "CONTENT-TYPE": + return "CONTENT_TYPE" + return "HTTP_" + key.replace("-", "_") @staticmethod def _from_wsgi(key): key = key.lower() - return key[5:].replace('_', '-') + return key[5:].replace("_", "-") def __getitem__(self, key): wsgi_key = self._to_wsgi(key) @@ -633,14 +564,14 @@ def __getitem__(self, key): def __iter__(self): for key in self.environ: - if key == 'CONTENT_LENGTH': - yield 'content-length', self.environ['CONTENT_LENGTH'] - elif key == 'CONTENT_TYPE': - yield 'content-type', self.environ['CONTENT_TYPE'] - elif key == 'HTTP_CONTENT_LENGTH' or key == 'HTTP_CONTENT_TYPE': + if key == "CONTENT_LENGTH": + yield "content-length", self.environ["CONTENT_LENGTH"] + elif key == "CONTENT_TYPE": + yield "content-type", self.environ["CONTENT_TYPE"] + elif key == "HTTP_CONTENT_LENGTH" or key == "HTTP_CONTENT_TYPE": # These keys are illegal and should be ignored continue - elif key.startswith('HTTP_'): + elif key.startswith("HTTP_"): yield self._from_wsgi(key), self.environ[key] def __len__(self): @@ -650,11 +581,9 @@ def __len__(self): class WSGIWebTransaction(WebTransaction): - - MOD_WSGI_HEADERS = ('mod_wsgi.request_start', 'mod_wsgi.queue_start') + MOD_WSGI_HEADERS = ("mod_wsgi.request_start", "mod_wsgi.queue_start") def __init__(self, application, environ, source=None): - # The web transaction can be enabled/disabled by # the value of the variable "newrelic.enabled" # in the WSGI environ dictionary. We need to check @@ -664,17 +593,20 @@ def __init__(self, application, environ, source=None): # base class making the decision based on whether # application or agent as a whole are enabled. - enabled = _lookup_environ_setting(environ, - 'newrelic.enabled', None) + enabled = _lookup_environ_setting(environ, "newrelic.enabled", None) # Initialise the common transaction base class. super(WSGIWebTransaction, self).__init__( - application, name=None, port=environ.get('SERVER_PORT'), - request_method=environ.get('REQUEST_METHOD'), - query_string=environ.get('QUERY_STRING'), + application, + name=None, + port=environ.get("SERVER_PORT"), + request_method=environ.get("REQUEST_METHOD"), + query_string=environ.get("QUERY_STRING"), headers=iter(WSGIHeaderProxy(environ)), - enabled=enabled, source=source) + enabled=enabled, + source=source, + ) # Disable transactions for websocket connections. # Also disable autorum if this is a websocket. This is a good idea for @@ -699,21 +631,17 @@ def __init__(self, application, environ, source=None): # Check for override settings from WSGI environ. - self.background_task = _lookup_environ_setting(environ, - 'newrelic.set_background_task', False) - - self.ignore_transaction = _lookup_environ_setting(environ, - 'newrelic.ignore_transaction', False) - self.suppress_apdex = _lookup_environ_setting(environ, - 'newrelic.suppress_apdex_metric', False) - self.suppress_transaction_trace = _lookup_environ_setting(environ, - 'newrelic.suppress_transaction_trace', False) - self.capture_params = _lookup_environ_setting(environ, - 'newrelic.capture_request_params', - settings.capture_params) - self.autorum_disabled = _lookup_environ_setting(environ, - 'newrelic.disable_browser_autorum', - not settings.browser_monitoring.auto_instrument) + self.background_task = _lookup_environ_setting(environ, "newrelic.set_background_task", False) + + self.ignore_transaction = _lookup_environ_setting(environ, "newrelic.ignore_transaction", False) + self.suppress_apdex = _lookup_environ_setting(environ, "newrelic.suppress_apdex_metric", False) + self.suppress_transaction_trace = _lookup_environ_setting(environ, "newrelic.suppress_transaction_trace", False) + self.capture_params = _lookup_environ_setting( + environ, "newrelic.capture_request_params", settings.capture_params + ) + self.autorum_disabled = _lookup_environ_setting( + environ, "newrelic.disable_browser_autorum", not settings.browser_monitoring.auto_instrument + ) # Make sure that if high security mode is enabled that # capture of request params is still being disabled. @@ -740,17 +668,17 @@ def __init__(self, application, environ, source=None): # due to use of REST style URL concepts or # otherwise. - request_uri = environ.get('REQUEST_URI', None) + request_uri = environ.get("REQUEST_URI", None) if request_uri is None: # The gunicorn WSGI server uses RAW_URI instead # of the more typical REQUEST_URI used by Apache # and other web servers. - request_uri = environ.get('RAW_URI', None) + request_uri = environ.get("RAW_URI", None) - script_name = environ.get('SCRIPT_NAME', None) - path_info = environ.get('PATH_INFO', None) + script_name = environ.get("SCRIPT_NAME", None) + path_info = environ.get("PATH_INFO", None) self._request_uri = request_uri @@ -771,13 +699,13 @@ def __init__(self, application, environ, source=None): else: path = script_name + path_info - self.set_transaction_name(path, 'Uri', priority=1) + self.set_transaction_name(path, "Uri", priority=1) if self._request_uri is None: self._request_uri = path else: if self._request_uri is not None: - self.set_transaction_name(self._request_uri, 'Uri', priority=1) + self.set_transaction_name(self._request_uri, "Uri", priority=1) # mod_wsgi sets its own distinct variables for queue time # automatically. Initially it set mod_wsgi.queue_start, @@ -801,7 +729,7 @@ def __init__(self, application, environ, source=None): continue try: - if value.startswith('t='): + if value.startswith("t="): try: self.queue_start = _parse_time_stamp(float(value[2:])) except Exception: @@ -816,58 +744,40 @@ def __init__(self, application, environ, source=None): pass def __exit__(self, exc, value, tb): - self.record_custom_metric('Python/WSGI/Input/Bytes', - self._bytes_read) - self.record_custom_metric('Python/WSGI/Input/Time', - self.read_duration) - self.record_custom_metric('Python/WSGI/Input/Calls/read', - self._calls_read) - self.record_custom_metric('Python/WSGI/Input/Calls/readline', - self._calls_readline) - self.record_custom_metric('Python/WSGI/Input/Calls/readlines', - self._calls_readlines) - - self.record_custom_metric('Python/WSGI/Output/Bytes', - self._bytes_sent) - self.record_custom_metric('Python/WSGI/Output/Time', - self.sent_duration) - self.record_custom_metric('Python/WSGI/Output/Calls/yield', - self._calls_yield) - self.record_custom_metric('Python/WSGI/Output/Calls/write', - self._calls_write) + self.record_custom_metric("Python/WSGI/Input/Bytes", self._bytes_read) + self.record_custom_metric("Python/WSGI/Input/Time", self.read_duration) + self.record_custom_metric("Python/WSGI/Input/Calls/read", self._calls_read) + self.record_custom_metric("Python/WSGI/Input/Calls/readline", self._calls_readline) + self.record_custom_metric("Python/WSGI/Input/Calls/readlines", self._calls_readlines) + + self.record_custom_metric("Python/WSGI/Output/Bytes", self._bytes_sent) + self.record_custom_metric("Python/WSGI/Output/Time", self.sent_duration) + self.record_custom_metric("Python/WSGI/Output/Calls/yield", self._calls_yield) + self.record_custom_metric("Python/WSGI/Output/Calls/write", self._calls_write) return super(WSGIWebTransaction, self).__exit__(exc, value, tb) def _update_agent_attributes(self): # Add WSGI agent attributes if self.read_duration != 0: - self._add_agent_attribute('wsgi.input.seconds', - self.read_duration) + self._add_agent_attribute("wsgi.input.seconds", self.read_duration) if self._bytes_read != 0: - self._add_agent_attribute('wsgi.input.bytes', - self._bytes_read) + self._add_agent_attribute("wsgi.input.bytes", self._bytes_read) if self._calls_read != 0: - self._add_agent_attribute('wsgi.input.calls.read', - self._calls_read) + self._add_agent_attribute("wsgi.input.calls.read", self._calls_read) if self._calls_readline != 0: - self._add_agent_attribute('wsgi.input.calls.readline', - self._calls_readline) + self._add_agent_attribute("wsgi.input.calls.readline", self._calls_readline) if self._calls_readlines != 0: - self._add_agent_attribute('wsgi.input.calls.readlines', - self._calls_readlines) + self._add_agent_attribute("wsgi.input.calls.readlines", self._calls_readlines) if self.sent_duration != 0: - self._add_agent_attribute('wsgi.output.seconds', - self.sent_duration) + self._add_agent_attribute("wsgi.output.seconds", self.sent_duration) if self._bytes_sent != 0: - self._add_agent_attribute('wsgi.output.bytes', - self._bytes_sent) + self._add_agent_attribute("wsgi.output.bytes", self._bytes_sent) if self._calls_write != 0: - self._add_agent_attribute('wsgi.output.calls.write', - self._calls_write) + self._add_agent_attribute("wsgi.output.calls.write", self._calls_write) if self._calls_yield != 0: - self._add_agent_attribute('wsgi.output.calls.yield', - self._calls_yield) + self._add_agent_attribute("wsgi.output.calls.yield", self._calls_yield) return super(WSGIWebTransaction, self)._update_agent_attributes() @@ -885,20 +795,28 @@ def process_response(self, status, response_headers, *args): # would raise as a 500 for WSGI applications). try: - status = status.split(' ', 1)[0] + status = status.split(" ", 1)[0] except Exception: status = None - return super(WSGIWebTransaction, self).process_response( - status, response_headers) - - -def WebTransactionWrapper(wrapped, application=None, name=None, group=None, - scheme=None, host=None, port=None, request_method=None, - request_path=None, query_string=None, headers=None, source=None): - + return super(WSGIWebTransaction, self).process_response(status, response_headers) + + +def WebTransactionWrapper( + wrapped, + application=None, + name=None, + group=None, + scheme=None, + host=None, + port=None, + request_method=None, + request_path=None, + query_string=None, + headers=None, + source=None, +): def wrapper(wrapped, instance, args, kwargs): - if type(application) != Application: _application = application_instance(application) else: @@ -978,7 +896,6 @@ def wrapper(wrapped, instance, args, kwargs): else: _headers = headers - proxy = async_proxy(wrapped) source_arg = source or wrapped @@ -986,17 +903,37 @@ def wrapper(wrapped, instance, args, kwargs): def create_transaction(transaction): if transaction: return None - return WebTransaction( _application, _name, _group, - _scheme, _host, _port, _request_method, - _request_path, _query_string, _headers, source=source_arg) + return WebTransaction( + _application, + _name, + _group, + _scheme, + _host, + _port, + _request_method, + _request_path, + _query_string, + _headers, + source=source_arg, + ) if proxy: context_manager = TransactionContext(create_transaction) return proxy(wrapped(*args, **kwargs), context_manager) transaction = WebTransaction( - _application, _name, _group, _scheme, _host, _port, - _request_method, _request_path, _query_string, _headers, source=source_arg) + _application, + _name, + _group, + _scheme, + _host, + _port, + _request_method, + _request_path, + _query_string, + _headers, + source=source_arg, + ) transaction = create_transaction(current_transaction(active_only=False)) @@ -1009,22 +946,50 @@ def create_transaction(transaction): return FunctionWrapper(wrapped, wrapper) -def web_transaction(application=None, name=None, group=None, - scheme=None, host=None, port=None, request_method=None, - request_path=None, query_string=None, headers=None): - - return functools.partial(WebTransactionWrapper, - application=application, name=name, group=group, - scheme=scheme, host=host, port=port, request_method=request_method, - request_path=request_path, query_string=query_string, - headers=headers) - - -def wrap_web_transaction(module, object_path, - application=None, name=None, group=None, - scheme=None, host=None, port=None, request_method=None, - request_path=None, query_string=None, headers=None): - - return wrap_object(module, object_path, WebTransactionWrapper, - (application, name, group, scheme, host, port, request_method, - request_path, query_string, headers)) +def web_transaction( + application=None, + name=None, + group=None, + scheme=None, + host=None, + port=None, + request_method=None, + request_path=None, + query_string=None, + headers=None, +): + return functools.partial( + WebTransactionWrapper, + application=application, + name=name, + group=group, + scheme=scheme, + host=host, + port=port, + request_method=request_method, + request_path=request_path, + query_string=query_string, + headers=headers, + ) + + +def wrap_web_transaction( + module, + object_path, + application=None, + name=None, + group=None, + scheme=None, + host=None, + port=None, + request_method=None, + request_path=None, + query_string=None, + headers=None, +): + return wrap_object( + module, + object_path, + WebTransactionWrapper, + (application, name, group, scheme, host, port, request_method, request_path, query_string, headers), + ) diff --git a/newrelic/api/wsgi_application.py b/newrelic/api/wsgi_application.py index 67338cbddd..5d12e94f30 100644 --- a/newrelic/api/wsgi_application.py +++ b/newrelic/api/wsgi_application.py @@ -78,7 +78,6 @@ def close(self): try: with FunctionTrace(name="Finalize", group="Python/WSGI"): - if isinstance(self.generator, _WSGIApplicationMiddleware): self.generator.close() @@ -153,7 +152,6 @@ def readlines(self, *args, **kwargs): class _WSGIApplicationMiddleware(object): - # This is a WSGI middleware for automatically inserting RUM into # HTML responses. It only works for where a WSGI application is # returning response content via a iterable/generator. It does not @@ -204,16 +202,7 @@ def process_data(self, data): # works then we are done, else we move to next phase of # buffering up content until we find the body element. - def html_to_be_inserted(): - header = self.transaction.browser_timing_header() - - if not header: - return b"" - - footer = self.transaction.browser_timing_footer() - - return six.b(header) + six.b(footer) - + html_to_be_inserted = lambda: six.b(self.transaction.browser_timing_header()) if not self.response_data: modified = insert_html_snippet(data, html_to_be_inserted) @@ -340,7 +329,6 @@ def start_response(self, status, response_headers, *args): # Also check whether RUM insertion has already occurred. if self.transaction.autorum_disabled or self.transaction.rum_header_generated: - self.flush_headers() self.pass_through = True @@ -360,7 +348,7 @@ def start_response(self, status, response_headers, *args): content_encoding = None content_disposition = None - for (name, value) in response_headers: + for name, value in response_headers: _name = name.lower() if _name == "content-length": @@ -508,7 +496,6 @@ def __iter__(self): def WSGIApplicationWrapper(wrapped, application=None, name=None, group=None, framework=None, dispatcher=None): - # Python 2 does not allow rebinding nonlocal variables, so to fix this # framework must be stored in list so it can be edited by closure. _framework = [framework] @@ -649,7 +636,6 @@ def _args(environ, start_response, *args, **kwargs): transaction.set_transaction_name(name, group, priority=1) def _start_response(status, response_headers, *args): - additional_headers = transaction.process_response(status, response_headers, *args) _write = start_response(status, response_headers + additional_headers, *args) diff --git a/newrelic/hooks/framework_django.py b/newrelic/hooks/framework_django.py index 3d9f448cc2..91d6fec200 100644 --- a/newrelic/hooks/framework_django.py +++ b/newrelic/hooks/framework_django.py @@ -16,6 +16,7 @@ import logging import sys import threading +import warnings from newrelic.api.application import register_application from newrelic.api.background_task import BackgroundTaskWrapper @@ -91,7 +92,6 @@ def _setting_set(value): def should_add_browser_timing(response, transaction): - # Don't do anything if receive a streaming response which # was introduced in Django 1.5. Need to avoid this as there # will be no 'content' attribute. Alternatively there may be @@ -111,7 +111,7 @@ def should_add_browser_timing(response, transaction): if not transaction or not transaction.enabled: return False - # Only insert RUM JavaScript headers and footers if enabled + # Only insert RUM JavaScript headers if enabled # in configuration and not already likely inserted. if not transaction.settings.browser_monitoring.enabled: @@ -152,38 +152,21 @@ def should_add_browser_timing(response, transaction): return True -# Response middleware for automatically inserting RUM header and -# footer into HTML response returned by application +# Response middleware for automatically inserting RUM header into HTML response returned by application def browser_timing_insertion(response, transaction): - - # No point continuing if header is empty. This can occur if - # RUM is not enabled within the UI. It is assumed at this - # point that if header is not empty, then footer will not be - # empty. We don't want to generate the footer just yet as - # want to do that as late as possible so that application - # server time in footer is as accurate as possible. In - # particular, if the response content is generated on demand - # then the flattening of the response could take some time - # and we want to track that. We thus generate footer below - # at point of insertion. - - header = transaction.browser_timing_header() - - if not header: - return response - - def html_to_be_inserted(): - return six.b(header) + six.b(transaction.browser_timing_footer()) - - # Make sure we flatten any content first as it could be - # stored as a list of strings in the response object. We - # assign it back to the response object to avoid having - # multiple copies of the string in memory at the same time + # No point continuing if header is empty. This can occur if RUM is not enabled within the UI. We don't want to + # generate the header just yet as we want to do that as late as possible so that application server time in header + # is as accurate as possible. In particular, if the response content is generated on demand then the flattening + # of the response could take some time and we want to track that. We thus generate header below at + # the point of insertion. + + # Make sure we flatten any content first as it could be stored as a list of strings in the response object. We + # assign it back to the response object to avoid having multiple copies of the string in memory at the same time # as we progress through steps below. - result = insert_html_snippet(response.content, html_to_be_inserted) + result = insert_html_snippet(response.content, lambda: six.b(transaction.browser_timing_header())) if result is not None: if transaction.settings.debug.log_autorum_middleware: @@ -200,10 +183,8 @@ def html_to_be_inserted(): return response -# Template tag functions for manually inserting RUM header and -# footer into HTML response. A template tag library for -# 'newrelic' will be automatically inserted into set of tag -# libraries when performing step to instrument the middleware. +# Template tag functions for manually inserting RUM header into HTML response. A template tag library for 'newrelic' +# will be automatically inserted into set of tag libraries when performing step to instrument the middleware. def newrelic_browser_timing_header(): @@ -214,10 +195,11 @@ def newrelic_browser_timing_header(): def newrelic_browser_timing_footer(): - from django.utils.safestring import mark_safe - - transaction = current_transaction() - return transaction and mark_safe(transaction.browser_timing_footer()) or "" # nosec + warnings.warn( + "The newrelic_browser_timing_footer function is deprecated. Please migrate to only using the newrelic_browser_timing_header API instead.", + DeprecationWarning, + ) + return "" # nosec # Addition of instrumentation for middleware. Can only do this @@ -228,7 +210,6 @@ def newrelic_browser_timing_footer(): def wrap_leading_middleware(middleware): - # Wrapper to be applied to middleware executed prior to the # view handler being executed. Records the time spent in the # middleware as separate function node and also attempts to @@ -276,7 +257,6 @@ def wrapper(wrapped, instance, args, kwargs): # functionality, so instead of removing this instrumentation, this # will be excluded from the coverage analysis. def wrap_view_middleware(middleware): # pragma: no cover - # This is no longer being used. The changes to strip the # wrapper from the view handler when passed into the function # urlresolvers.reverse() solves most of the problems. To back @@ -342,7 +322,6 @@ def _wrapped(request, view_func, view_args, view_kwargs): def wrap_trailing_middleware(middleware): - # Wrapper to be applied to trailing middleware executed # after the view handler. Records the time spent in the # middleware as separate function node. Transaction is never @@ -358,7 +337,6 @@ def wrap_trailing_middleware(middleware): def insert_and_wrap_middleware(handler, *args, **kwargs): - # Use lock to control access by single thread but also as # flag to indicate if done the initialisation. Lock will be # None if have already done this. @@ -383,7 +361,6 @@ def insert_and_wrap_middleware(handler, *args, **kwargs): middleware_instrumentation_lock = None try: - # Wrap the middleware to undertake timing and name # the web transaction. The naming is done as lower # priority than that for view handler so view handler @@ -411,7 +388,6 @@ def insert_and_wrap_middleware(handler, *args, **kwargs): def _nr_wrapper_GZipMiddleware_process_response_(wrapped, instance, args, kwargs): - transaction = current_transaction() if transaction is None: @@ -454,7 +430,6 @@ def _nr_wrapper_BaseHandler_get_response_(wrapped, instance, args, kwargs): def instrument_django_core_handlers_base(module): - # Attach a post function to load_middleware() method of # BaseHandler to trigger insertion of browser timing # middleware and wrapping of middleware for timing etc. @@ -468,12 +443,10 @@ def instrument_django_core_handlers_base(module): def instrument_django_gzip_middleware(module): - wrap_function_wrapper(module, "GZipMiddleware.process_response", _nr_wrapper_GZipMiddleware_process_response_) def wrap_handle_uncaught_exception(middleware): - # Wrapper to be applied to handler called when exceptions # propagate up to top level from middleware. Records the # time spent in the handler as separate function node. Names @@ -506,7 +479,6 @@ def _wrapped(request, resolver, exc_info): def instrument_django_core_handlers_wsgi(module): - # Wrap the WSGI application entry point. If this is also # wrapped from the WSGI script file or by the WSGI hosting # mechanism then those will take precedence. @@ -532,7 +504,6 @@ def instrument_django_core_handlers_wsgi(module): def wrap_view_handler(wrapped, priority=3): - # Ensure we don't wrap the view handler more than once. This # looks like it may occur in cases where the resolver is # called recursively. We flag that view handler was wrapped @@ -574,7 +545,6 @@ def wrapper(wrapped, instance, args, kwargs): def wrap_url_resolver(wrapped): - # Wrap URL resolver. If resolver returns valid result then # wrap the view handler returned. The type of the result # changes across Django versions so need to check and adapt @@ -624,7 +594,6 @@ def _wrapped(path): def wrap_url_resolver_nnn(wrapped, priority=1): - # Wrapper to be applied to the URL resolver for errors. name = callable_name(wrapped) @@ -647,7 +616,6 @@ def wrapper(wrapped, instance, args, kwargs): def wrap_url_reverse(wrapped): - # Wrap the URL resolver reverse lookup. Where the view # handler is passed in we need to strip any instrumentation # wrapper to ensure that it doesn't interfere with the @@ -667,7 +635,6 @@ def execute(viewname, *args, **kwargs): def instrument_django_core_urlresolvers(module): - # Wrap method which maps a string version of a function # name as used in urls.py pattern so can capture any # exception which is raised during that process. @@ -719,7 +686,6 @@ def instrument_django_core_urlresolvers(module): def instrument_django_urls_base(module): - # Wrap function for performing reverse URL lookup to strip any # instrumentation wrapper when view handler is passed in. @@ -728,7 +694,6 @@ def instrument_django_urls_base(module): def instrument_django_template(module): - # Wrap methods for rendering of Django templates. The name # of the method changed in between Django versions so need # to check for which one we have. The name of the function @@ -753,8 +718,7 @@ def template_name(template, *args): if not hasattr(module, "libraries"): return - # Register template tags used for manual insertion of RUM - # header and footer. + # Register template tags used for manual insertion of RUM header. # # TODO This can now be installed as a separate tag library # so should possibly look at deprecating this automatic @@ -775,7 +739,6 @@ def wrapper(wrapped, instance, args, kwargs): def instrument_django_template_loader_tags(module): - # Wrap template block node for timing, naming the node after # the block name as defined in the template rather than # function name. @@ -784,7 +747,6 @@ def instrument_django_template_loader_tags(module): def instrument_django_core_servers_basehttp(module): - # Allow 'runserver' to be used with Django <= 1.3. To do # this we wrap the WSGI application argument on the way in # so that the run() method gets the wrapped instance. @@ -819,7 +781,6 @@ def wrap_wsgi_application_entry_point(server, application, **kwargs): ) if not hasattr(module, "simple_server") and hasattr(module.ServerHandler, "run"): - # Patch the server to make it work properly. def run(self, application): @@ -869,7 +830,6 @@ def instrument_django_contrib_staticfiles_handlers(module): def instrument_django_views_debug(module): - # Wrap methods for handling errors when Django debug # enabled. For 404 we give this higher naming priority over # any prior middleware or view handler to give them @@ -896,7 +856,6 @@ def resolve_view_handler(view, request): def wrap_view_dispatch(wrapped): - # Wrapper to be applied to dispatcher for class based views. def wrapper(wrapped, instance, args, kwargs): @@ -996,7 +955,6 @@ def instrument_django_core_management_base(module): @function_wrapper def _nr_wrapper_django_inclusion_tag_wrapper_(wrapped, instance, args, kwargs): - name = hasattr(wrapped, "__name__") and wrapped.__name__ if name is None: @@ -1025,13 +983,11 @@ def _bind_params(func, *args, **kwargs): def _nr_wrapper_django_template_base_Library_inclusion_tag_(wrapped, instance, args, kwargs): - return _nr_wrapper_django_inclusion_tag_decorator_(wrapped(*args, **kwargs)) @function_wrapper def _nr_wrapper_django_template_base_InclusionNode_render_(wrapped, instance, args, kwargs): - if wrapped.__self__ is None: return wrapped(*args, **kwargs) @@ -1046,7 +1002,6 @@ def _nr_wrapper_django_template_base_InclusionNode_render_(wrapped, instance, ar def _nr_wrapper_django_template_base_generic_tag_compiler_(wrapped, instance, args, kwargs): - if wrapped.__code__.co_argcount > 6: # Django > 1.3. @@ -1083,7 +1038,6 @@ def _bind_params(name=None, compile_function=None, *args, **kwargs): return wrapped(*args, **kwargs) def _get_node_class(compile_function): - node_class = None # Django >= 1.4 uses functools.partial @@ -1099,7 +1053,6 @@ def _get_node_class(compile_function): and hasattr(compile_function, "__name__") and compile_function.__name__ == "_curried" ): - # compile_function here is generic_tag_compiler(), which has been # curried. To get node_class, we first get the function obj, args, # and kwargs of the curried function from the cells in @@ -1154,7 +1107,6 @@ def instrument_django_template_base(module): settings = global_settings() if "django.instrumentation.inclusion-tags.r1" in settings.feature_flag: - if hasattr(module, "generic_tag_compiler"): wrap_function_wrapper( module, "generic_tag_compiler", _nr_wrapper_django_template_base_generic_tag_compiler_ @@ -1197,7 +1149,6 @@ def _bind_params(original_middleware, *args, **kwargs): def instrument_django_core_handlers_exception(module): - if hasattr(module, "convert_exception_to_response"): wrap_function_wrapper(module, "convert_exception_to_response", _nr_wrapper_convert_exception_to_response_) diff --git a/newrelic/hooks/middleware_flask_compress.py b/newrelic/hooks/middleware_flask_compress.py index 09e35b3cd2..078cc3d989 100644 --- a/newrelic/hooks/middleware_flask_compress.py +++ b/newrelic/hooks/middleware_flask_compress.py @@ -18,35 +18,41 @@ from newrelic.api.transaction import current_transaction from newrelic.common.object_wrapper import wrap_function_wrapper from newrelic.config import extra_settings - from newrelic.packages import six _logger = logging.getLogger(__name__) _boolean_states = { - '1': True, 'yes': True, 'true': True, 'on': True, - '0': False, 'no': False, 'false': False, 'off': False + "1": True, + "yes": True, + "true": True, + "on": True, + "0": False, + "no": False, + "false": False, + "off": False, } def _setting_boolean(value): if value.lower() not in _boolean_states: - raise ValueError('Not a boolean: %s' % value) + raise ValueError("Not a boolean: %s" % value) return _boolean_states[value.lower()] _settings_types = { - 'browser_monitoring.auto_instrument': _setting_boolean, - 'browser_monitoring.auto_instrument_passthrough': _setting_boolean, + "browser_monitoring.auto_instrument": _setting_boolean, + "browser_monitoring.auto_instrument_passthrough": _setting_boolean, } _settings_defaults = { - 'browser_monitoring.auto_instrument': True, - 'browser_monitoring.auto_instrument_passthrough': True, + "browser_monitoring.auto_instrument": True, + "browser_monitoring.auto_instrument_passthrough": True, } -flask_compress_settings = extra_settings('import-hook:flask_compress', - types=_settings_types, defaults=_settings_defaults) +flask_compress_settings = extra_settings( + "import-hook:flask_compress", types=_settings_types, defaults=_settings_defaults +) def _nr_wrapper_Compress_after_request(wrapped, instance, args, kwargs): @@ -62,7 +68,7 @@ def _params(response, *args, **kwargs): if not transaction: return wrapped(*args, **kwargs) - # Only insert RUM JavaScript headers and footers if enabled + # Only insert RUM JavaScript headers if enabled # in configuration and not already likely inserted. if not transaction.settings.browser_monitoring.enabled: @@ -83,45 +89,34 @@ def _params(response, *args, **kwargs): # a user may want to also perform insertion for # 'application/xhtml+xml'. - ctype = (response.mimetype or '').lower() + ctype = (response.mimetype or "").lower() if ctype not in transaction.settings.browser_monitoring.content_type: return wrapped(*args, **kwargs) # Don't risk it if content encoding already set. - if 'Content-Encoding' in response.headers: + if "Content-Encoding" in response.headers: return wrapped(*args, **kwargs) # Don't risk it if content is actually within an attachment. - cdisposition = response.headers.get('Content-Disposition', '').lower() + cdisposition = response.headers.get("Content-Disposition", "").lower() - if cdisposition.split(';')[0].strip() == 'attachment': + if cdisposition.split(";")[0].strip() == "attachment": return wrapped(*args, **kwargs) - # No point continuing if header is empty. This can occur if - # RUM is not enabled within the UI. It is assumed at this - # point that if header is not empty, then footer will not be - # empty. We don't want to generate the footer just yet as - # want to do that as late as possible so that application - # server time in footer is as accurate as possible. In - # particular, if the response content is generated on demand - # then the flattening of the response could take some time - # and we want to track that. We thus generate footer below - # at point of insertion. - - header = transaction.browser_timing_header() - - if not header: - return wrapped(*args, **kwargs) + # No point continuing if header is empty. This can occur if RUM is not enabled within the UI. We don't want to + # generate the header just yet as we want to do that as late as possible so that application server time in header + # is as accurate as possible. In particular, if the response content is generated on demand then the flattening + # of the response could take some time and we want to track that. We thus generate header below at + # the point of insertion. # If the response has direct_passthrough flagged, then is # likely to be streaming a file or other large response. - direct_passthrough = getattr(response, 'direct_passthrough', None) + direct_passthrough = getattr(response, "direct_passthrough", None) if direct_passthrough: - if not (flask_compress_settings. - browser_monitoring.auto_instrument_passthrough): + if not (flask_compress_settings.browser_monitoring.auto_instrument_passthrough): return wrapped(*args, **kwargs) # In those cases, if the mimetype is still a supported browser @@ -131,34 +126,31 @@ def _params(response, *args, **kwargs): # # In order to do that, we have to disable direct_passthrough on the # response since we have to immediately read the contents of the file. - elif ctype == 'text/html': + elif ctype == "text/html": response.direct_passthrough = False else: return wrapped(*args, **kwargs) - def html_to_be_inserted(): - return six.b(header) + six.b(transaction.browser_timing_footer()) - # Make sure we flatten any content first as it could be # stored as a list of strings in the response object. We # assign it back to the response object to avoid having # multiple copies of the string in memory at the same time # as we progress through steps below. - result = insert_html_snippet(response.get_data(), html_to_be_inserted) + result = insert_html_snippet(response.get_data(), lambda: six.b(transaction.browser_timing_header())) if result is not None: if transaction.settings.debug.log_autorum_middleware: - _logger.debug('RUM insertion from flask_compress ' - 'triggered. Bytes added was %r.', - len(result) - len(response.get_data())) + _logger.debug( + "RUM insertion from flask_compress " "triggered. Bytes added was %r.", + len(result) - len(response.get_data()), + ) response.set_data(result) - response.headers['Content-Length'] = str(len(response.get_data())) + response.headers["Content-Length"] = str(len(response.get_data())) return wrapped(*args, **kwargs) def instrument_flask_compress(module): - wrap_function_wrapper(module, 'Compress.after_request', - _nr_wrapper_Compress_after_request) + wrap_function_wrapper(module, "Compress.after_request", _nr_wrapper_Compress_after_request) diff --git a/tests/agent_features/test_asgi_browser.py b/tests/agent_features/test_asgi_browser.py index 281d08b967..4146d507b6 100644 --- a/tests/agent_features/test_asgi_browser.py +++ b/tests/agent_features/test_asgi_browser.py @@ -31,7 +31,6 @@ from newrelic.api.transaction import ( add_custom_attribute, disable_browser_autorum, - get_browser_timing_footer, get_browser_timing_header, ) from newrelic.common.encoding_utils import deobfuscate @@ -41,9 +40,9 @@ @asgi_application() async def target_asgi_application_manual_rum(scope, receive, send): - text = "%s

RESPONSE

%s" + text = "%s

RESPONSE

" - output = (text % (get_browser_timing_header(), get_browser_timing_footer())).encode("UTF-8") + output = (text % get_browser_timing_header()).encode("UTF-8") response_headers = [ (b"content-type", b"text/html; charset=utf-8"), @@ -56,15 +55,15 @@ async def target_asgi_application_manual_rum(scope, receive, send): target_application_manual_rum = AsgiTest(target_asgi_application_manual_rum) -_test_footer_attributes = { +_test_header_attributes = { "browser_monitoring.enabled": True, "browser_monitoring.auto_instrument": False, "js_agent_loader": "", } -@override_application_settings(_test_footer_attributes) -def test_footer_attributes(): +@override_application_settings(_test_header_attributes) +def test_header_attributes(): settings = application_settings() assert settings.browser_monitoring.enabled @@ -84,7 +83,6 @@ def test_footer_attributes(): html = BeautifulSoup(response.body, "html.parser") header = html.html.head.script.string content = html.html.body.p.string - footer = html.html.body.script.string # Validate actual body content. @@ -94,10 +92,10 @@ def test_footer_attributes(): assert header.find("NREUM HEADER") != -1 - # Now validate the various fields of the footer. The fields are + # Now validate the various fields of the header. The fields are # held by a JSON dictionary. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert data["licenseKey"] == settings.browser_key assert data["applicationID"] == settings.application_id @@ -137,8 +135,8 @@ def test_ssl_for_http_is_none(): response = target_application_manual_rum.get("/") html = BeautifulSoup(response.body, "html.parser") - footer = html.html.body.script.string - data = json.loads(footer.split("NREUM.info=")[1]) + header = html.html.head.script.string + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "sslForHttp" not in data @@ -159,8 +157,8 @@ def test_ssl_for_http_is_true(): response = target_application_manual_rum.get("/") html = BeautifulSoup(response.body, "html.parser") - footer = html.html.body.script.string - data = json.loads(footer.split("NREUM.info=")[1]) + header = html.html.head.script.string + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert data["sslForHttp"] is True @@ -181,8 +179,8 @@ def test_ssl_for_http_is_false(): response = target_application_manual_rum.get("/") html = BeautifulSoup(response.body, "html.parser") - footer = html.html.body.script.string - data = json.loads(footer.split("NREUM.info=")[1]) + header = html.html.head.script.string + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert data["sslForHttp"] is False @@ -219,7 +217,7 @@ def test_html_insertion_yield_single_no_head(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" in response.body assert b"NREUM.info" in response.body @@ -259,7 +257,7 @@ def test_html_insertion_yield_multi_no_head(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" in response.body assert b"NREUM.info" in response.body @@ -299,7 +297,7 @@ def test_html_insertion_unnamed_attachment_header(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body @@ -339,7 +337,7 @@ def test_html_insertion_named_attachment_header(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body @@ -379,7 +377,7 @@ def test_html_insertion_inline_attachment_header(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" in response.body assert b"NREUM.info" in response.body @@ -414,7 +412,7 @@ def test_html_insertion_empty(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body @@ -449,7 +447,7 @@ def test_html_insertion_single_empty_string(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body @@ -485,7 +483,7 @@ def test_html_insertion_multiple_empty_string(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body @@ -522,7 +520,7 @@ def test_html_insertion_single_large_prelude(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert "content-type" in response.headers assert "content-length" in response.headers @@ -566,7 +564,7 @@ def test_html_insertion_multi_large_prelude(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert "content-type" in response.headers assert "content-length" in response.headers @@ -884,7 +882,7 @@ def test_html_insertion_disable_autorum_via_api(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body @@ -895,13 +893,9 @@ async def target_asgi_application_manual_rum_insertion(scope, receive, send): output = b"

RESPONSE

" header = get_browser_timing_header() - footer = get_browser_timing_footer() - header = get_browser_timing_header() - footer = get_browser_timing_footer() assert header == "" - assert footer == "" response_headers = [ (b"content-type", b"text/html; charset=utf-8"), @@ -931,7 +925,7 @@ def test_html_insertion_manual_rum_insertion(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert b"NREUM HEADER" not in response.body assert b"NREUM.info" not in response.body diff --git a/tests/agent_features/test_browser.py b/tests/agent_features/test_browser.py index 735cec5c12..84ce795000 100644 --- a/tests/agent_features/test_browser.py +++ b/tests/agent_features/test_browser.py @@ -13,6 +13,7 @@ # limitations under the License. import json +import re import sys import six @@ -29,7 +30,6 @@ from newrelic.api.transaction import ( add_custom_attribute, disable_browser_autorum, - get_browser_timing_footer, get_browser_timing_header, ) from newrelic.api.web_transaction import web_transaction @@ -43,9 +43,9 @@ def target_wsgi_application_manual_rum(environ, start_response): status = "200 OK" - text = "%s

RESPONSE

%s" + text = "%s

RESPONSE

" - output = (text % (get_browser_timing_header(), get_browser_timing_footer())).encode("UTF-8") + output = (text % get_browser_timing_header()).encode("UTF-8") response_headers = [("Content-Type", "text/html; charset=utf-8"), ("Content-Length", str(len(output)))] start_response(status, response_headers) @@ -55,15 +55,15 @@ def target_wsgi_application_manual_rum(environ, start_response): target_application_manual_rum = webtest.TestApp(target_wsgi_application_manual_rum) -_test_footer_attributes = { +_test_header_attributes = { "browser_monitoring.enabled": True, "browser_monitoring.auto_instrument": False, "js_agent_loader": "", } -@override_application_settings(_test_footer_attributes) -def test_footer_attributes(): +@override_application_settings(_test_header_attributes) +def test_header_attributes(): settings = application_settings() assert settings.browser_monitoring.enabled @@ -82,7 +82,6 @@ def test_footer_attributes(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -92,10 +91,10 @@ def test_footer_attributes(): assert header.find("NREUM HEADER") != -1 - # Now validate the various fields of the footer. The fields are + # Now validate the various fields of the header. The fields are # held by a JSON dictionary. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert data["licenseKey"] == settings.browser_key assert data["applicationID"] == settings.application_id @@ -134,8 +133,8 @@ def test_ssl_for_http_is_none(): assert settings.browser_monitoring.ssl_for_http is None response = target_application_manual_rum.get("/") - footer = response.html.html.body.script.string - data = json.loads(footer.split("NREUM.info=")[1]) + header = response.html.html.head.script.string + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "sslForHttp" not in data @@ -155,8 +154,8 @@ def test_ssl_for_http_is_true(): assert settings.browser_monitoring.ssl_for_http is True response = target_application_manual_rum.get("/") - footer = response.html.html.body.script.string - data = json.loads(footer.split("NREUM.info=")[1]) + header = response.html.html.head.script.string + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert data["sslForHttp"] is True @@ -176,8 +175,8 @@ def test_ssl_for_http_is_false(): assert settings.browser_monitoring.ssl_for_http is False response = target_application_manual_rum.get("/") - footer = response.html.html.body.script.string - data = json.loads(footer.split("NREUM.info=")[1]) + header = response.html.html.head.script.string + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert data["sslForHttp"] is False @@ -212,7 +211,7 @@ def test_html_insertion_yield_single_no_head(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain("NREUM HEADER", "NREUM.info") @@ -248,7 +247,7 @@ def test_html_insertion_yield_multi_no_head(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain("NREUM HEADER", "NREUM.info") @@ -288,7 +287,7 @@ def test_html_insertion_unnamed_attachment_header(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @@ -328,7 +327,7 @@ def test_html_insertion_named_attachment_header(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @@ -368,7 +367,7 @@ def test_html_insertion_inline_attachment_header(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain("NREUM HEADER", "NREUM.info") @@ -401,7 +400,7 @@ def test_html_insertion_empty_list(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @@ -436,7 +435,7 @@ def test_html_insertion_single_empty_string(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @@ -471,7 +470,7 @@ def test_html_insertion_multiple_empty_string(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @@ -505,7 +504,7 @@ def test_html_insertion_single_large_prelude(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert "Content-Type" in response.headers assert "Content-Length" in response.headers @@ -544,7 +543,7 @@ def test_html_insertion_multi_large_prelude(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert "Content-Type" in response.headers assert "Content-Length" in response.headers @@ -589,7 +588,7 @@ def test_html_insertion_yield_before_start(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain("NREUM HEADER", "NREUM.info") @@ -627,7 +626,7 @@ def test_html_insertion_start_yield_start(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. assert "Content-Type" in response.headers assert "Content-Length" in response.headers @@ -980,7 +979,7 @@ def test_html_insertion_disable_autorum_via_api(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @@ -992,13 +991,9 @@ def target_wsgi_application_manual_rum_insertion(environ, start_response): output = b"

RESPONSE

" header = get_browser_timing_header() - footer = get_browser_timing_footer() - header = get_browser_timing_header() - footer = get_browser_timing_footer() assert header == "" - assert footer == "" response_headers = [("Content-Type", "text/html; charset=utf-8"), ("Content-Length", str(len(output)))] start_response(status, response_headers) @@ -1024,34 +1019,42 @@ def test_html_insertion_manual_rum_insertion(): # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) -_test_get_browser_timing_nonces_settings = { +_test_get_browser_timing_snippet_with_nonces = { "browser_monitoring.enabled": True, "browser_monitoring.auto_instrument": False, "js_agent_loader": "", } +_test_get_browser_timing_snippet_with_nonces_rum_info_re = re.compile(r"NREUM\.info={[^}]*}") -@override_application_settings(_test_get_browser_timing_nonces_settings) -@web_transaction(scheme="http", host="127.0.0.1", port=80, request_method="GET", - request_path="/", query_string=None, headers={}) -def test_get_browser_timing_nonces(): + +@override_application_settings(_test_get_browser_timing_snippet_with_nonces) +@web_transaction( + scheme="http", host="127.0.0.1", port=80, request_method="GET", request_path="/", query_string=None, headers={} +) +def test_get_browser_timing_snippet_with_nonces(): header = get_browser_timing_header("NONCE") - footer = get_browser_timing_footer("NONCE") - assert header == '' - assert '' + ) -@override_application_settings(_test_get_browser_timing_nonces_settings) -@web_transaction(scheme="http", host="127.0.0.1", port=80, request_method="GET", - request_path="/", query_string=None, headers={}) -def test_get_browser_timing_no_nonces(): +@override_application_settings(_test_get_browser_timing_snippet_with_nonces) +@web_transaction( + scheme="http", host="127.0.0.1", port=80, request_method="GET", request_path="/", query_string=None, headers={} +) +def test_get_browser_timing_snippet_without_nonces(): header = get_browser_timing_header() - footer = get_browser_timing_footer() - assert header == '' - assert '' + ) diff --git a/tests/agent_features/test_transaction_event_data_and_some_browser_stuff_too.py b/tests/agent_features/test_transaction_event_data_and_some_browser_stuff_too.py index 73bdfcf535..c1d9283c25 100644 --- a/tests/agent_features/test_transaction_event_data_and_some_browser_stuff_too.py +++ b/tests/agent_features/test_transaction_event_data_and_some_browser_stuff_too.py @@ -59,7 +59,6 @@ def test_capture_attributes_enabled(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -71,10 +70,10 @@ def test_capture_attributes_enabled(): assert header.find("NREUM") != -1 - # Now validate the various fields of the footer related to analytics. + # Now validate the various fields of the header related to analytics. # The fields are held by a JSON dictionary. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) obfuscation_key = settings.license_key[:13] @@ -116,7 +115,6 @@ def test_no_attributes_recorded(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -128,13 +126,13 @@ def test_no_attributes_recorded(): assert header.find("NREUM") != -1 - # Now validate the various fields of the footer related to analytics. + # Now validate the various fields of the header related to analytics. # The fields are held by a JSON dictionary. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) # As we are not recording any user or agent attributes, we should not - # actually have an entry at all in the footer. + # actually have an entry at all in the header. assert "atts" not in data @@ -163,7 +161,6 @@ def test_analytic_events_capture_attributes_disabled(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -178,7 +175,7 @@ def test_analytic_events_capture_attributes_disabled(): # Now validate that attributes are present, since browser monitoring should # be enabled. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "atts" in data @@ -196,7 +193,6 @@ def test_capture_attributes_default(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -211,7 +207,7 @@ def test_capture_attributes_default(): # Now validate that attributes are not present, since should # be disabled. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "atts" not in data @@ -258,7 +254,6 @@ def test_capture_attributes_disabled(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -273,7 +268,7 @@ def test_capture_attributes_disabled(): # Now validate that attributes are not present, since should # be disabled. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "atts" not in data @@ -307,7 +302,6 @@ def test_collect_analytic_events_disabled(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -322,7 +316,7 @@ def test_collect_analytic_events_disabled(): # Now validate that attributes are present, since should # be enabled. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "atts" in data @@ -351,7 +345,6 @@ def test_analytic_events_disabled(): header = response.html.html.head.script.string content = response.html.html.body.p.string - footer = response.html.html.body.script.string # Validate actual body content. @@ -366,7 +359,7 @@ def test_analytic_events_disabled(): # Now validate that attributes are present, since should # be enabled. - data = json.loads(footer.split("NREUM.info=")[1]) + data = json.loads(header.split("NREUM.info=")[1].split(";\n")[0]) assert "atts" in data diff --git a/tests/cross_agent/fixtures/rum_client_config.json b/tests/cross_agent/fixtures/rum_client_config.json deleted file mode 100644 index 8f6e7cbbbe..0000000000 --- a/tests/cross_agent/fixtures/rum_client_config.json +++ /dev/null @@ -1,91 +0,0 @@ -[ - { - "testname":"all fields present", - - "apptime_milliseconds":5, - "queuetime_milliseconds":3, - "browser_monitoring.attributes.enabled":true, - "transaction_name":"WebTransaction/brink/of/glory", - "license_key":"0000111122223333444455556666777788889999", - "connect_reply": - { - "beacon":"my_beacon", - "browser_key":"my_browser_key", - "application_id":"my_application_id", - "error_beacon":"my_error_beacon", - "js_agent_file":"my_js_agent_file" - }, - "user_attributes":{"alpha":"beta"}, - "expected": - { - "beacon":"my_beacon", - "licenseKey":"my_browser_key", - "applicationID":"my_application_id", - "transactionName":"Z1VSZENQX0JTUUZbXF4fUkJYX1oeXVQdVV9fQkk=", - "queueTime":3, - "applicationTime":5, - "atts":"SxJFEgtKE1BeQlpTEQoSUlVFUBNMTw==", - "errorBeacon":"my_error_beacon", - "agent":"my_js_agent_file" - } - }, - { - "testname":"browser_monitoring.attributes.enabled disabled", - - "apptime_milliseconds":5, - "queuetime_milliseconds":3, - "browser_monitoring.attributes.enabled":false, - "transaction_name":"WebTransaction/brink/of/glory", - "license_key":"0000111122223333444455556666777788889999", - "connect_reply": - { - "beacon":"my_beacon", - "browser_key":"my_browser_key", - "application_id":"my_application_id", - "error_beacon":"my_error_beacon", - "js_agent_file":"my_js_agent_file" - }, - "user_attributes":{"alpha":"beta"}, - "expected": - { - "beacon":"my_beacon", - "licenseKey":"my_browser_key", - "applicationID":"my_application_id", - "transactionName":"Z1VSZENQX0JTUUZbXF4fUkJYX1oeXVQdVV9fQkk=", - "queueTime":3, - "applicationTime":5, - "atts":"", - "errorBeacon":"my_error_beacon", - "agent":"my_js_agent_file" - } - }, - { - "testname":"empty js_agent_file", - "apptime_milliseconds":5, - "queuetime_milliseconds":3, - "browser_monitoring.attributes.enabled":true, - "transaction_name":"WebTransaction/brink/of/glory", - "license_key":"0000111122223333444455556666777788889999", - "connect_reply": - { - "beacon":"my_beacon", - "browser_key":"my_browser_key", - "application_id":"my_application_id", - "error_beacon":"my_error_beacon", - "js_agent_file":"" - }, - "user_attributes":{"alpha":"beta"}, - "expected": - { - "beacon":"my_beacon", - "licenseKey":"my_browser_key", - "applicationID":"my_application_id", - "transactionName":"Z1VSZENQX0JTUUZbXF4fUkJYX1oeXVQdVV9fQkk=", - "queueTime":3, - "applicationTime":5, - "atts":"SxJFEgtKE1BeQlpTEQoSUlVFUBNMTw==", - "errorBeacon":"my_error_beacon", - "agent":"" - } - } -] diff --git a/tests/cross_agent/fixtures/rum_footer_insertion_location/close-body-in-comment.html b/tests/cross_agent/fixtures/rum_footer_insertion_location/close-body-in-comment.html deleted file mode 100644 index e32df24204..0000000000 --- a/tests/cross_agent/fixtures/rum_footer_insertion_location/close-body-in-comment.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - Comment contains a close body tag - - -

The quick brown fox jumps over the lazy dog.

- - EXPECTED_RUM_FOOTER_LOCATION - diff --git a/tests/cross_agent/fixtures/rum_footer_insertion_location/dynamic-iframe.html b/tests/cross_agent/fixtures/rum_footer_insertion_location/dynamic-iframe.html deleted file mode 100644 index 5e1acc86b5..0000000000 --- a/tests/cross_agent/fixtures/rum_footer_insertion_location/dynamic-iframe.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - Dynamic iframe Generation - - -

The quick brown fox jumps over the lazy dog.

- - - EXPECTED_RUM_FOOTER_LOCATION - diff --git a/tests/cross_agent/test_cat_map.py b/tests/cross_agent/test_cat_map.py index 6e7ac63d6d..ea011990a8 100644 --- a/tests/cross_agent/test_cat_map.py +++ b/tests/cross_agent/test_cat_map.py @@ -43,7 +43,6 @@ from newrelic.api.external_trace import ExternalTrace from newrelic.api.transaction import ( current_transaction, - get_browser_timing_footer, get_browser_timing_header, set_background_task, set_transaction_name, @@ -134,9 +133,9 @@ def target_wsgi_application(environ, start_response): set_background_task(True) set_transaction_name(txn_name[2], group=txn_name[1]) - text = "%s

RESPONSE

%s" + text = "%s

RESPONSE

" - output = (text % (get_browser_timing_header(), get_browser_timing_footer())).encode("UTF-8") + output = (text % get_browser_timing_header()).encode("UTF-8") response_headers = [("Content-type", "text/html; charset=utf-8"), ("Content-Length", str(len(output)))] start_response(status, response_headers) @@ -193,7 +192,6 @@ def test_cat_map( @override_application_settings(_custom_settings) @override_application_name(appName) def run_cat_test(): - if six.PY2: txn_name = transactionName.encode("UTF-8") guid = transactionGuid.encode("UTF-8") diff --git a/tests/cross_agent/test_rum_client_config.py b/tests/cross_agent/test_rum_client_config.py deleted file mode 100644 index 5b8da4b84c..0000000000 --- a/tests/cross_agent/test_rum_client_config.py +++ /dev/null @@ -1,145 +0,0 @@ -# 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 json -import os - -import pytest -import webtest -from testing_support.fixtures import override_application_settings - -from newrelic.api.transaction import ( - add_custom_attribute, - get_browser_timing_footer, - set_transaction_name, -) -from newrelic.api.wsgi_application import wsgi_application - -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "rum_client_config.json") - -def _load_tests(): - with open(FIXTURE, "r") as fh: - js = fh.read() - return json.loads(js) - - -fields = [ - "testname", - "apptime_milliseconds", - "queuetime_milliseconds", - "browser_monitoring.attributes.enabled", - "transaction_name", - "license_key", - "connect_reply", - "user_attributes", - "expected", -] - -# Replace . as not a valid character in python argument names - -field_names = ",".join([f.replace(".", "_") for f in fields]) - - -def _parametrize_test(test): - return tuple([test.get(f, None) for f in fields]) - - -_rum_tests = [_parametrize_test(t) for t in _load_tests()] - - -@wsgi_application() -def target_wsgi_application(environ, start_response): - status = "200 OK" - - txn_name = environ.get("txn_name") - set_transaction_name(txn_name, group="") - - user_attrs = json.loads(environ.get("user_attrs")) - for key, value in user_attrs.items(): - add_custom_attribute(key, value) - - text = "%s

RESPONSE

" - - output = (text % get_browser_timing_footer()).encode("UTF-8") - - response_headers = [("Content-Type", "text/html; charset=utf-8"), ("Content-Length", str(len(output)))] - start_response(status, response_headers) - - return [output] - - -target_application = webtest.TestApp(target_wsgi_application) - - -@pytest.mark.parametrize(field_names, _rum_tests) -def test_browser_montioring( - testname, - apptime_milliseconds, - queuetime_milliseconds, - browser_monitoring_attributes_enabled, - transaction_name, - license_key, - connect_reply, - user_attributes, - expected, -): - - settings = { - "browser_monitoring.attributes.enabled": browser_monitoring_attributes_enabled, - "license_key": license_key, - "js_agent_loader": "", - } - settings.update(connect_reply) - - @override_application_settings(settings) - def run_browser_data_test(): - - response = target_application.get( - "/", extra_environ={"txn_name": str(transaction_name), "user_attrs": json.dumps(user_attributes)} - ) - - # We actually put the "footer" in the header, the first script is the - # agent "header", the second one is where the data lives, hence the [1]. - - footer = response.html.html.head.find_all("script")[1] - footer_data = json.loads(footer.string.split("NREUM.info=")[1]) - - # Not feasible to test the time metric values in testing - - expected.pop("queueTime") - expected.pop("applicationTime") - assert footer_data["applicationTime"] >= 0 - assert footer_data["queueTime"] >= 0 - - # Python always prepends stuff to the transaction name, so this - # doesn't match the obscured value. - - expected.pop("transactionName") - - # Check that all other values are correct - - for key, value in expected.items(): - - # If there are no attributes, the spec allows us to omit the - # 'atts' field altogether, so we do. But, the cross agent tests - # don't omit it, so we need to special case 'atts' when we compare - # to 'expected'. - - if key == "atts" and value == "": - assert key not in footer_data - else: - assert footer_data[key] == value - - run_browser_data_test() diff --git a/tests/framework_bottle/test_application.py b/tests/framework_bottle/test_application.py index 28619d5eb5..cdcd90e3f3 100644 --- a/tests/framework_bottle/test_application.py +++ b/tests/framework_bottle/test_application.py @@ -12,218 +12,238 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest import base64 +import pytest +import webtest +from bottle import __version__ as version from testing_support.fixtures import ( + override_application_settings, override_ignore_status_codes, - override_application_settings) -from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics +) +from testing_support.validators.validate_code_level_metrics import ( + validate_code_level_metrics, +) +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_tuple from newrelic.packages import six -from testing_support.validators.validate_code_level_metrics import validate_code_level_metrics -from testing_support.validators.validate_transaction_errors import validate_transaction_errors - -import webtest -from bottle import __version__ as version - -version = [int(x) for x in version.split('-')[0].split('.')] +version = list(get_package_version_tuple("bottle")) if len(version) == 2: version.append(0) version = tuple(version) +assert version > (0, 1), "version information not found" -requires_auth_basic = pytest.mark.skipif(version < (0, 9, 0), - reason="Bottle only added auth_basic in 0.9.0.") -requires_plugins = pytest.mark.skipif(version < (0, 9, 0), - reason="Bottle only added auth_basic in 0.9.0.") +requires_auth_basic = pytest.mark.skipif(version < (0, 9, 0), reason="Bottle only added auth_basic in 0.9.0.") +requires_plugins = pytest.mark.skipif(version < (0, 9, 0), reason="Bottle only added auth_basic in 0.9.0.") _test_application_index_scoped_metrics = [ - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_target_application:index_page', 1)] + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_target_application:index_page", 1), +] if version >= (0, 9, 0): - _test_application_index_scoped_metrics.extend([ - ('Function/bottle:Bottle.wsgi', 1)]) + _test_application_index_scoped_metrics.extend([("Function/bottle:Bottle.wsgi", 1)]) else: - _test_application_index_scoped_metrics.extend([ - ('Function/bottle:Bottle.__call__', 1)]) + _test_application_index_scoped_metrics.extend([("Function/bottle:Bottle.__call__", 1)]) + +_test_application_index_custom_metrics = [("Python/Framework/Bottle/%s.%s.%s" % version, 1)] -_test_application_index_custom_metrics = [ - ('Python/Framework/Bottle/%s.%s.%s' % version, 1)] @validate_code_level_metrics("_target_application", "index_page") @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_target_application:index_page', - scoped_metrics=_test_application_index_scoped_metrics, - custom_metrics=_test_application_index_custom_metrics) +@validate_transaction_metrics( + "_target_application:index_page", + scoped_metrics=_test_application_index_scoped_metrics, + custom_metrics=_test_application_index_custom_metrics, +) def test_application_index(target_application): - response = target_application.get('/index') - response.mustcontain('INDEX RESPONSE') + response = target_application.get("/index") + response.mustcontain("INDEX RESPONSE") + _test_application_error_scoped_metrics = [ - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_target_application:error_page', 1)] + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_target_application:error_page", 1), +] if version >= (0, 9, 0): - _test_application_error_scoped_metrics.extend([ - ('Function/bottle:Bottle.wsgi', 1)]) + _test_application_error_scoped_metrics.extend([("Function/bottle:Bottle.wsgi", 1)]) else: - _test_application_error_scoped_metrics.extend([ - ('Function/bottle:Bottle.__call__', 1)]) + _test_application_error_scoped_metrics.extend([("Function/bottle:Bottle.__call__", 1)]) -_test_application_error_custom_metrics = [ - ('Python/Framework/Bottle/%s.%s.%s' % version, 1)] +_test_application_error_custom_metrics = [("Python/Framework/Bottle/%s.%s.%s" % version, 1)] if six.PY3: - _test_application_error_errors = ['builtins:RuntimeError'] + _test_application_error_errors = ["builtins:RuntimeError"] else: - _test_application_error_errors = ['exceptions:RuntimeError'] + _test_application_error_errors = ["exceptions:RuntimeError"] + @validate_code_level_metrics("_target_application", "error_page") @validate_transaction_errors(errors=_test_application_error_errors) -@validate_transaction_metrics('_target_application:error_page', - scoped_metrics=_test_application_error_scoped_metrics, - custom_metrics=_test_application_error_custom_metrics) +@validate_transaction_metrics( + "_target_application:error_page", + scoped_metrics=_test_application_error_scoped_metrics, + custom_metrics=_test_application_error_custom_metrics, +) def test_application_error(target_application): - response = target_application.get('/error', status=500, expect_errors=True) + response = target_application.get("/error", status=500, expect_errors=True) + _test_application_not_found_scoped_metrics = [ - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_target_application:error404_page', 1)] + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_target_application:error404_page", 1), +] if version >= (0, 9, 0): - _test_application_not_found_scoped_metrics.extend([ - ('Function/bottle:Bottle.wsgi', 1)]) + _test_application_not_found_scoped_metrics.extend([("Function/bottle:Bottle.wsgi", 1)]) else: - _test_application_not_found_scoped_metrics.extend([ - ('Function/bottle:Bottle.__call__', 1)]) + _test_application_not_found_scoped_metrics.extend([("Function/bottle:Bottle.__call__", 1)]) + +_test_application_not_found_custom_metrics = [("Python/Framework/Bottle/%s.%s.%s" % version, 1)] -_test_application_not_found_custom_metrics = [ - ('Python/Framework/Bottle/%s.%s.%s' % version, 1)] @validate_code_level_metrics("_target_application", "error404_page") @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_target_application:error404_page', - scoped_metrics=_test_application_not_found_scoped_metrics, - custom_metrics=_test_application_not_found_custom_metrics) +@validate_transaction_metrics( + "_target_application:error404_page", + scoped_metrics=_test_application_not_found_scoped_metrics, + custom_metrics=_test_application_not_found_custom_metrics, +) def test_application_not_found(target_application): - response = target_application.get('/missing', status=404) - response.mustcontain('NOT FOUND') + response = target_application.get("/missing", status=404) + response.mustcontain("NOT FOUND") + _test_application_auth_basic_fail_scoped_metrics = [ - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_target_application:auth_basic_page', 1)] + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_target_application:auth_basic_page", 1), +] if version >= (0, 9, 0): - _test_application_auth_basic_fail_scoped_metrics.extend([ - ('Function/bottle:Bottle.wsgi', 1)]) + _test_application_auth_basic_fail_scoped_metrics.extend([("Function/bottle:Bottle.wsgi", 1)]) else: - _test_application_auth_basic_fail_scoped_metrics.extend([ - ('Function/bottle:Bottle.__call__', 1)]) + _test_application_auth_basic_fail_scoped_metrics.extend([("Function/bottle:Bottle.__call__", 1)]) + +_test_application_auth_basic_fail_custom_metrics = [("Python/Framework/Bottle/%s.%s.%s" % version, 1)] -_test_application_auth_basic_fail_custom_metrics = [ - ('Python/Framework/Bottle/%s.%s.%s' % version, 1)] @requires_auth_basic @validate_code_level_metrics("_target_application", "auth_basic_page") @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_target_application:auth_basic_page', - scoped_metrics=_test_application_auth_basic_fail_scoped_metrics, - custom_metrics=_test_application_auth_basic_fail_custom_metrics) +@validate_transaction_metrics( + "_target_application:auth_basic_page", + scoped_metrics=_test_application_auth_basic_fail_scoped_metrics, + custom_metrics=_test_application_auth_basic_fail_custom_metrics, +) def test_application_auth_basic_fail(target_application): - response = target_application.get('/auth', status=401) + response = target_application.get("/auth", status=401) + _test_application_auth_basic_okay_scoped_metrics = [ - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_target_application:auth_basic_page', 1)] + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_target_application:auth_basic_page", 1), +] if version >= (0, 9, 0): - _test_application_auth_basic_okay_scoped_metrics.extend([ - ('Function/bottle:Bottle.wsgi', 1)]) + _test_application_auth_basic_okay_scoped_metrics.extend([("Function/bottle:Bottle.wsgi", 1)]) else: - _test_application_auth_basic_okay_scoped_metrics.extend([ - ('Function/bottle:Bottle.__call__', 1)]) + _test_application_auth_basic_okay_scoped_metrics.extend([("Function/bottle:Bottle.__call__", 1)]) + +_test_application_auth_basic_okay_custom_metrics = [("Python/Framework/Bottle/%s.%s.%s" % version, 1)] -_test_application_auth_basic_okay_custom_metrics = [ - ('Python/Framework/Bottle/%s.%s.%s' % version, 1)] @requires_auth_basic @validate_code_level_metrics("_target_application", "auth_basic_page") @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_target_application:auth_basic_page', - scoped_metrics=_test_application_auth_basic_okay_scoped_metrics, - custom_metrics=_test_application_auth_basic_okay_custom_metrics) +@validate_transaction_metrics( + "_target_application:auth_basic_page", + scoped_metrics=_test_application_auth_basic_okay_scoped_metrics, + custom_metrics=_test_application_auth_basic_okay_custom_metrics, +) def test_application_auth_basic_okay(target_application): - authorization_value = base64.b64encode(b'user:password') + authorization_value = base64.b64encode(b"user:password") if six.PY3: - authorization_value = authorization_value.decode('Latin-1') - environ = { 'HTTP_AUTHORIZATION': 'Basic ' + authorization_value } - response = target_application.get('/auth', extra_environ=environ) - response.mustcontain('AUTH OKAY') + authorization_value = authorization_value.decode("Latin-1") + environ = {"HTTP_AUTHORIZATION": "Basic " + authorization_value} + response = target_application.get("/auth", extra_environ=environ) + response.mustcontain("AUTH OKAY") + _test_application_plugin_error_scoped_metrics = [ - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_target_application:plugin_error_page', 1)] + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_target_application:plugin_error_page", 1), +] if version >= (0, 9, 0): - _test_application_plugin_error_scoped_metrics.extend([ - ('Function/bottle:Bottle.wsgi', 1)]) + _test_application_plugin_error_scoped_metrics.extend([("Function/bottle:Bottle.wsgi", 1)]) else: - _test_application_plugin_error_scoped_metrics.extend([ - ('Function/bottle:Bottle.__call__', 1)]) + _test_application_plugin_error_scoped_metrics.extend([("Function/bottle:Bottle.__call__", 1)]) + +_test_application_plugin_error_custom_metrics = [("Python/Framework/Bottle/%s.%s.%s" % version, 1)] -_test_application_plugin_error_custom_metrics = [ - ('Python/Framework/Bottle/%s.%s.%s' % version, 1)] @requires_plugins @validate_code_level_metrics("_target_application", "plugin_error_page") @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_target_application:plugin_error_page', - scoped_metrics=_test_application_plugin_error_scoped_metrics, - custom_metrics=_test_application_plugin_error_custom_metrics) +@validate_transaction_metrics( + "_target_application:plugin_error_page", + scoped_metrics=_test_application_plugin_error_scoped_metrics, + custom_metrics=_test_application_plugin_error_custom_metrics, +) @override_ignore_status_codes([403]) def test_application_plugin_error_ignore(target_application): - response = target_application.get('/plugin_error', status=403, - expect_errors=True) + response = target_application.get("/plugin_error", status=403, expect_errors=True) + @requires_plugins @validate_code_level_metrics("_target_application", "plugin_error_page") -@validate_transaction_errors(errors=['bottle:HTTPError']) -@validate_transaction_metrics('_target_application:plugin_error_page', - scoped_metrics=_test_application_plugin_error_scoped_metrics, - custom_metrics=_test_application_plugin_error_custom_metrics) +@validate_transaction_errors(errors=["bottle:HTTPError"]) +@validate_transaction_metrics( + "_target_application:plugin_error_page", + scoped_metrics=_test_application_plugin_error_scoped_metrics, + custom_metrics=_test_application_plugin_error_custom_metrics, +) def test_application_plugin_error_capture(target_application): import newrelic.agent - response = target_application.get('/plugin_error', status=403, - expect_errors=True) + + response = target_application.get("/plugin_error", status=403, expect_errors=True) + _test_html_insertion_settings = { - 'browser_monitoring.enabled': True, - 'browser_monitoring.auto_instrument': True, - 'js_agent_loader': u'', + "browser_monitoring.enabled": True, + "browser_monitoring.auto_instrument": True, + "js_agent_loader": "", } + @override_application_settings(_test_html_insertion_settings) def test_html_insertion(target_application): - response = target_application.get('/html_insertion') + response = target_application.get("/html_insertion") # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. - - response.mustcontain('NREUM HEADER', 'NREUM.info') + # header added by the agent. + response.mustcontain("NREUM HEADER", "NREUM.info") diff --git a/tests/framework_cherrypy/test_application.py b/tests/framework_cherrypy/test_application.py index 39f8b5c16d..dd4595c0b8 100644 --- a/tests/framework_cherrypy/test_application.py +++ b/tests/framework_cherrypy/test_application.py @@ -12,31 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. +import cherrypy import pytest import webtest - -from newrelic.packages import six - from testing_support.fixtures import ( - override_application_settings, - override_ignore_status_codes) -from testing_support.validators.validate_code_level_metrics import validate_code_level_metrics -from testing_support.validators.validate_transaction_errors import validate_transaction_errors + override_application_settings, + override_ignore_status_codes, +) +from testing_support.validators.validate_code_level_metrics import ( + validate_code_level_metrics, +) +from testing_support.validators.validate_transaction_errors import ( + validate_transaction_errors, +) -import cherrypy +from newrelic.packages import six -CHERRYPY_VERSION = tuple(int(v) for v in cherrypy.__version__.split('.')) +CHERRYPY_VERSION = tuple(int(v) for v in cherrypy.__version__.split(".")) class Application(object): - @cherrypy.expose def index(self): - return 'INDEX RESPONSE' + return "INDEX RESPONSE" @cherrypy.expose def error(self): - raise RuntimeError('error') + raise RuntimeError("error") @cherrypy.expose def not_found(self): @@ -48,35 +50,37 @@ def not_found_as_http_error(self): @cherrypy.expose def not_found_as_str_http_error(self): - raise cherrypy.HTTPError('404 Not Found') + raise cherrypy.HTTPError("404 Not Found") @cherrypy.expose def bad_http_error(self): # this will raise HTTPError with status code 500 because 10 is not a # valid status code - raise cherrypy.HTTPError('10 Invalid status code') + raise cherrypy.HTTPError("10 Invalid status code") @cherrypy.expose def internal_redirect(self): - raise cherrypy.InternalRedirect('/') + raise cherrypy.InternalRedirect("/") @cherrypy.expose def external_redirect(self): - raise cherrypy.HTTPRedirect('/') + raise cherrypy.HTTPRedirect("/") @cherrypy.expose def upload_files(self, files): - return 'UPLOAD FILES RESPONSE' + return "UPLOAD FILES RESPONSE" @cherrypy.expose def encode_multipart(self, field, files): - return 'ENCODE MULTIPART RESPONSE' + return "ENCODE MULTIPART RESPONSE" @cherrypy.expose def html_insertion(self): - return ('Some header' - '

My First Heading

My first paragraph.

' - '') + return ( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) application = cherrypy.Application(Application()) @@ -86,99 +90,97 @@ def html_insertion(self): @validate_code_level_metrics("test_application.Application", "index") @validate_transaction_errors(errors=[]) def test_application_index(): - response = test_application.get('') - response.mustcontain('INDEX RESPONSE') + response = test_application.get("") + response.mustcontain("INDEX RESPONSE") @validate_transaction_errors(errors=[]) def test_application_index_agent_disabled(): - environ = {'newrelic.enabled': False} - response = test_application.get('', extra_environ=environ) - response.mustcontain('INDEX RESPONSE') + environ = {"newrelic.enabled": False} + response = test_application.get("", extra_environ=environ) + response.mustcontain("INDEX RESPONSE") @validate_transaction_errors(errors=[]) def test_application_missing(): - test_application.get('/missing', status=404) + test_application.get("/missing", status=404) if six.PY3: - _test_application_unexpected_exception_errors = ['builtins:RuntimeError'] + _test_application_unexpected_exception_errors = ["builtins:RuntimeError"] else: - _test_application_unexpected_exception_errors = ['exceptions:RuntimeError'] + _test_application_unexpected_exception_errors = ["exceptions:RuntimeError"] -@validate_transaction_errors( - errors=_test_application_unexpected_exception_errors) +@validate_transaction_errors(errors=_test_application_unexpected_exception_errors) def test_application_unexpected_exception(): - test_application.get('/error', status=500) + test_application.get("/error", status=500) @validate_transaction_errors(errors=[]) def test_application_not_found(): - test_application.get('/not_found', status=404) + test_application.get("/not_found", status=404) @validate_transaction_errors(errors=[]) def test_application_not_found_as_http_error(): - test_application.get('/not_found_as_http_error', status=404) + test_application.get("/not_found_as_http_error", status=404) @validate_transaction_errors(errors=[]) def test_application_internal_redirect(): - response = test_application.get('/internal_redirect') - response.mustcontain('INDEX RESPONSE') + response = test_application.get("/internal_redirect") + response.mustcontain("INDEX RESPONSE") @validate_transaction_errors(errors=[]) def test_application_external_redirect(): - test_application.get('/external_redirect', status=302) + test_application.get("/external_redirect", status=302) @validate_transaction_errors(errors=[]) def test_application_upload_files(): - test_application.post('/upload_files', upload_files=[('files', __file__)]) + test_application.post("/upload_files", upload_files=[("files", __file__)]) @validate_transaction_errors(errors=[]) def test_application_encode_multipart(): - content_type, body = test_application.encode_multipart( - params=[('field', 'value')], files=[('files', __file__)]) - test_application.request('/encode_multipart', method='POST', - content_type=content_type, body=body) + content_type, body = test_application.encode_multipart(params=[("field", "value")], files=[("files", __file__)]) + test_application.request("/encode_multipart", method="POST", content_type=content_type, body=body) _test_html_insertion_settings = { - 'browser_monitoring.enabled': True, - 'browser_monitoring.auto_instrument': True, - 'js_agent_loader': u'', + "browser_monitoring.enabled": True, + "browser_monitoring.auto_instrument": True, + "js_agent_loader": "", } @override_application_settings(_test_html_insertion_settings) def test_html_insertion(): - response = test_application.get('/html_insertion') + response = test_application.get("/html_insertion") # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. - response.mustcontain('NREUM HEADER', 'NREUM.info') + response.mustcontain("NREUM HEADER", "NREUM.info") -_error_endpoints = ['/not_found_as_http_error'] +_error_endpoints = ["/not_found_as_http_error"] if CHERRYPY_VERSION >= (3, 2): - _error_endpoints.extend(['/not_found_as_str_http_error', - '/bad_http_error']) + _error_endpoints.extend(["/not_found_as_str_http_error", "/bad_http_error"]) -@pytest.mark.parametrize('endpoint', _error_endpoints) -@pytest.mark.parametrize('ignore_overrides,expected_errors', [ - ([], ['cherrypy._cperror:HTTPError']), - ([404, 500], []), -]) +@pytest.mark.parametrize("endpoint", _error_endpoints) +@pytest.mark.parametrize( + "ignore_overrides,expected_errors", + [ + ([], ["cherrypy._cperror:HTTPError"]), + ([404, 500], []), + ], +) def test_ignore_status_code(endpoint, ignore_overrides, expected_errors): - @validate_transaction_errors(errors=expected_errors) @override_ignore_status_codes(ignore_overrides) def _test(): @@ -189,5 +191,5 @@ def _test(): @validate_transaction_errors(errors=[]) def test_ignore_status_unexpected_param(): - response = test_application.get('/?arg=1', status=404) - response.mustcontain(no=['INDEX RESPONSE']) + response = test_application.get("/?arg=1", status=404) + response.mustcontain(no=["INDEX RESPONSE"]) diff --git a/tests/framework_django/templates/main.html b/tests/framework_django/templates/main.html index bcf5afda39..5de5a534a3 100644 --- a/tests/framework_django/templates/main.html +++ b/tests/framework_django/templates/main.html @@ -26,6 +26,5 @@

My First Heading

My first paragraph.

{% show_results %} - {% newrelic_browser_timing_footer %} diff --git a/tests/framework_django/test_application.py b/tests/framework_django/test_application.py index 1f2616b0fa..82501707b2 100644 --- a/tests/framework_django/test_application.py +++ b/tests/framework_django/test_application.py @@ -12,24 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -from testing_support.fixtures import ( - override_application_settings, - override_generic_settings, override_ignore_status_codes) -from testing_support.validators.validate_code_level_metrics import validate_code_level_metrics -from newrelic.hooks.framework_django import django_settings -from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics -from testing_support.validators.validate_transaction_errors import validate_transaction_errors - import os import django +from testing_support.fixtures import ( + override_application_settings, + override_generic_settings, + override_ignore_status_codes, +) +from testing_support.validators.validate_code_level_metrics import ( + validate_code_level_metrics, +) +from testing_support.validators.validate_transaction_errors import ( + validate_transaction_errors, +) +from testing_support.validators.validate_transaction_metrics import ( + validate_transaction_metrics, +) + +from newrelic.hooks.framework_django import django_settings -DJANGO_VERSION = tuple(map(int, django.get_version().split('.')[:2])) -DJANGO_SETTINGS_MODULE = os.environ.get('DJANGO_SETTINGS_MODULE', None) +DJANGO_VERSION = tuple(map(int, django.get_version().split(".")[:2])) +DJANGO_SETTINGS_MODULE = os.environ.get("DJANGO_SETTINGS_MODULE", None) def target_application(): from _target_application import _target_application + return _target_application @@ -37,272 +46,233 @@ def target_application(): # MIDDLEWARE defined in the version-specific Django settings.py file. _test_django_pre_1_10_middleware_scoped_metrics = [ - (('Function/django.middleware.common:' - 'CommonMiddleware.process_request'), 1), - (('Function/django.contrib.sessions.middleware:' - 'SessionMiddleware.process_request'), 1), - (('Function/django.contrib.auth.middleware:' - 'AuthenticationMiddleware.process_request'), 1), - (('Function/django.contrib.messages.middleware:' - 'MessageMiddleware.process_request'), 1), - (('Function/django.middleware.csrf:' - 'CsrfViewMiddleware.process_view'), 1), - (('Function/django.contrib.messages.middleware:' - 'MessageMiddleware.process_response'), 1), - (('Function/django.middleware.csrf:' - 'CsrfViewMiddleware.process_response'), 1), - (('Function/django.contrib.sessions.middleware:' - 'SessionMiddleware.process_response'), 1), - (('Function/django.middleware.common:' - 'CommonMiddleware.process_response'), 1), - (('Function/django.middleware.gzip:' - 'GZipMiddleware.process_response'), 1), - (('Function/newrelic.hooks.framework_django:' - 'browser_timing_insertion'), 1), + (("Function/django.middleware.common:" "CommonMiddleware.process_request"), 1), + (("Function/django.contrib.sessions.middleware:" "SessionMiddleware.process_request"), 1), + (("Function/django.contrib.auth.middleware:" "AuthenticationMiddleware.process_request"), 1), + (("Function/django.contrib.messages.middleware:" "MessageMiddleware.process_request"), 1), + (("Function/django.middleware.csrf:" "CsrfViewMiddleware.process_view"), 1), + (("Function/django.contrib.messages.middleware:" "MessageMiddleware.process_response"), 1), + (("Function/django.middleware.csrf:" "CsrfViewMiddleware.process_response"), 1), + (("Function/django.contrib.sessions.middleware:" "SessionMiddleware.process_response"), 1), + (("Function/django.middleware.common:" "CommonMiddleware.process_response"), 1), + (("Function/django.middleware.gzip:" "GZipMiddleware.process_response"), 1), + (("Function/newrelic.hooks.framework_django:" "browser_timing_insertion"), 1), ] _test_django_post_1_10_middleware_scoped_metrics = [ - ('Function/django.middleware.security:SecurityMiddleware', 1), - ('Function/django.contrib.sessions.middleware:SessionMiddleware', 1), - ('Function/django.middleware.common:CommonMiddleware', 1), - ('Function/django.middleware.csrf:CsrfViewMiddleware', 1), - ('Function/django.contrib.auth.middleware:AuthenticationMiddleware', 1), - ('Function/django.contrib.messages.middleware:MessageMiddleware', 1), - ('Function/django.middleware.clickjacking:XFrameOptionsMiddleware', 1), - ('Function/django.middleware.gzip:GZipMiddleware', 1), + ("Function/django.middleware.security:SecurityMiddleware", 1), + ("Function/django.contrib.sessions.middleware:SessionMiddleware", 1), + ("Function/django.middleware.common:CommonMiddleware", 1), + ("Function/django.middleware.csrf:CsrfViewMiddleware", 1), + ("Function/django.contrib.auth.middleware:AuthenticationMiddleware", 1), + ("Function/django.contrib.messages.middleware:MessageMiddleware", 1), + ("Function/django.middleware.clickjacking:XFrameOptionsMiddleware", 1), + ("Function/django.middleware.gzip:GZipMiddleware", 1), ] _test_django_pre_1_10_url_resolver_scoped_metrics = [ - ('Function/django.core.urlresolvers:RegexURLResolver.resolve', 'present'), + ("Function/django.core.urlresolvers:RegexURLResolver.resolve", "present"), ] _test_django_post_1_10_url_resolver_scoped_metrics = [ - ('Function/django.urls.resolvers:RegexURLResolver.resolve', 'present'), + ("Function/django.urls.resolvers:RegexURLResolver.resolve", "present"), ] _test_django_post_2_0_url_resolver_scoped_metrics = [ - ('Function/django.urls.resolvers:URLResolver.resolve', 'present'), + ("Function/django.urls.resolvers:URLResolver.resolve", "present"), ] _test_application_index_scoped_metrics = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/views:index', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:index", 1), ] if DJANGO_VERSION >= (1, 5): - _test_application_index_scoped_metrics.extend([ - ('Function/django.http.response:HttpResponse.close', 1)]) + _test_application_index_scoped_metrics.extend([("Function/django.http.response:HttpResponse.close", 1)]) if DJANGO_VERSION < (1, 10): - _test_application_index_scoped_metrics.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_application_index_scoped_metrics.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_application_index_scoped_metrics.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_application_index_scoped_metrics.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_application_index_scoped_metrics.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) - -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_application_index_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_application_index_scoped_metrics.extend( - _test_django_post_1_10_middleware_scoped_metrics) + _test_application_index_scoped_metrics.extend(_test_django_post_1_10_url_resolver_scoped_metrics) + +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_application_index_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_application_index_scoped_metrics.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_application_index_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_application_index_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:index', - scoped_metrics=_test_application_index_scoped_metrics) +@validate_transaction_metrics("views:index", scoped_metrics=_test_application_index_scoped_metrics) @validate_code_level_metrics("views", "index") def test_application_index(): test_application = target_application() - response = test_application.get('') - response.mustcontain('INDEX RESPONSE') + response = test_application.get("") + response.mustcontain("INDEX RESPONSE") -@validate_transaction_metrics('views:exception') +@validate_transaction_metrics("views:exception") @validate_code_level_metrics("views", "exception") def test_application_exception(): test_application = target_application() - test_application.get('/exception', status=500) + test_application.get("/exception", status=500) _test_application_not_found_scoped_metrics = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), ] if DJANGO_VERSION >= (1, 5): - _test_application_not_found_scoped_metrics.extend([ - ('Function/django.http.response:HttpResponseNotFound.close', 1)]) + _test_application_not_found_scoped_metrics.extend([("Function/django.http.response:HttpResponseNotFound.close", 1)]) if DJANGO_VERSION < (1, 10): - _test_application_not_found_scoped_metrics.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_application_not_found_scoped_metrics.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_application_not_found_scoped_metrics.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_application_not_found_scoped_metrics.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_application_not_found_scoped_metrics.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) + _test_application_not_found_scoped_metrics.extend(_test_django_post_1_10_url_resolver_scoped_metrics) -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_application_not_found_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_application_not_found_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) # The `CsrfViewMiddleware.process_view` isn't called for 404 Not Found. _test_application_not_found_scoped_metrics.remove( - ('Function/django.middleware.csrf:CsrfViewMiddleware.process_view', 1)) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_application_not_found_scoped_metrics.extend( - _test_django_post_1_10_middleware_scoped_metrics) + ("Function/django.middleware.csrf:CsrfViewMiddleware.process_view", 1) + ) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_application_not_found_scoped_metrics.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_application_not_found_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_application_not_found_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) # The `CsrfViewMiddleware.process_view` isn't called for 404 Not Found. _test_application_not_found_scoped_metrics.remove( - ('Function/django.middleware.csrf:CsrfViewMiddleware.process_view', 1)) + ("Function/django.middleware.csrf:CsrfViewMiddleware.process_view", 1) + ) @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('django.views.debug:technical_404_response', - scoped_metrics=_test_application_not_found_scoped_metrics) +@validate_transaction_metrics( + "django.views.debug:technical_404_response", scoped_metrics=_test_application_not_found_scoped_metrics +) def test_application_not_found(): test_application = target_application() - test_application.get('/not_found', status=404) + test_application.get("/not_found", status=404) @override_ignore_status_codes([403]) @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:permission_denied') +@validate_transaction_metrics("views:permission_denied") @validate_code_level_metrics("views", "permission_denied") def test_ignored_status_code(): test_application = target_application() - test_application.get('/permission_denied', status=403) + test_application.get("/permission_denied", status=403) @override_ignore_status_codes([410]) @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:middleware_410') +@validate_transaction_metrics("views:middleware_410") @validate_code_level_metrics("views", "middleware_410") def test_middleware_ignore_status_codes(): test_application = target_application() - test_application.get('/middleware_410', status=410) + test_application.get("/middleware_410", status=410) _test_application_cbv_scoped_metrics = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/views:MyView', 1), - ('Function/views:MyView.get', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:MyView", 1), + ("Function/views:MyView.get", 1), ] if DJANGO_VERSION >= (1, 5): - _test_application_cbv_scoped_metrics.extend([ - ('Function/django.http.response:HttpResponse.close', 1)]) + _test_application_cbv_scoped_metrics.extend([("Function/django.http.response:HttpResponse.close", 1)]) if DJANGO_VERSION < (1, 10): - _test_application_cbv_scoped_metrics.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_application_cbv_scoped_metrics.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_application_cbv_scoped_metrics.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_application_cbv_scoped_metrics.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_application_cbv_scoped_metrics.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) - -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_application_cbv_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_application_cbv_scoped_metrics.extend( - _test_django_post_1_10_middleware_scoped_metrics) + _test_application_cbv_scoped_metrics.extend(_test_django_post_1_10_url_resolver_scoped_metrics) + +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_application_cbv_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_application_cbv_scoped_metrics.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_application_cbv_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_application_cbv_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:MyView.get', - scoped_metrics=_test_application_cbv_scoped_metrics) +@validate_transaction_metrics("views:MyView.get", scoped_metrics=_test_application_cbv_scoped_metrics) @validate_code_level_metrics("views.MyView", "get") def test_application_cbv(): test_application = target_application() - response = test_application.get('/cbv') - response.mustcontain('CBV RESPONSE') + response = test_application.get("/cbv") + response.mustcontain("CBV RESPONSE") _test_application_deferred_cbv_scoped_metrics = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/views:deferred_cbv', 1), - ('Function/views:MyView.get', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:deferred_cbv", 1), + ("Function/views:MyView.get", 1), ] if DJANGO_VERSION >= (1, 5): - _test_application_deferred_cbv_scoped_metrics.extend([ - ('Function/django.http.response:HttpResponse.close', 1)]) + _test_application_deferred_cbv_scoped_metrics.extend([("Function/django.http.response:HttpResponse.close", 1)]) if DJANGO_VERSION < (1, 10): - _test_application_deferred_cbv_scoped_metrics.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_application_deferred_cbv_scoped_metrics.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_application_deferred_cbv_scoped_metrics.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_application_deferred_cbv_scoped_metrics.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_application_deferred_cbv_scoped_metrics.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) - -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_application_deferred_cbv_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_application_deferred_cbv_scoped_metrics.extend( - _test_django_post_1_10_middleware_scoped_metrics) + _test_application_deferred_cbv_scoped_metrics.extend(_test_django_post_1_10_url_resolver_scoped_metrics) + +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_application_deferred_cbv_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_application_deferred_cbv_scoped_metrics.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_application_deferred_cbv_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_application_deferred_cbv_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:deferred_cbv', - scoped_metrics=_test_application_deferred_cbv_scoped_metrics) +@validate_transaction_metrics("views:deferred_cbv", scoped_metrics=_test_application_deferred_cbv_scoped_metrics) @validate_code_level_metrics("views", "deferred_cbv") def test_application_deferred_cbv(): test_application = target_application() - response = test_application.get('/deferred_cbv') - response.mustcontain('CBV RESPONSE') + response = test_application.get("/deferred_cbv") + response.mustcontain("CBV RESPONSE") _test_html_insertion_settings = { - 'browser_monitoring.enabled': True, - 'browser_monitoring.auto_instrument': True, - 'js_agent_loader': u'', + "browser_monitoring.enabled": True, + "browser_monitoring.auto_instrument": True, + "js_agent_loader": "", } @override_application_settings(_test_html_insertion_settings) def test_html_insertion_django_middleware(): test_application = target_application() - response = test_application.get('/html_insertion', status=200) + response = test_application.get("/html_insertion", status=200) # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. - response.mustcontain('NREUM HEADER', 'NREUM.info') + response.mustcontain("NREUM HEADER", "NREUM.info") @override_application_settings(_test_html_insertion_settings) @@ -311,23 +281,22 @@ def test_html_insertion_django_gzip_middleware_enabled(): # GZipMiddleware only fires if given the following header. - gzip_header = {'Accept-Encoding': 'gzip'} - response = test_application.get('/gzip_html_insertion', status=200, - headers=gzip_header) + gzip_header = {"Accept-Encoding": "gzip"} + response = test_application.get("/gzip_html_insertion", status=200, headers=gzip_header) # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. # The response.text will already be gunzipped - response.mustcontain('NREUM HEADER', 'NREUM.info') + response.mustcontain("NREUM HEADER", "NREUM.info") _test_html_insertion_settings_disabled = { - 'browser_monitoring.enabled': False, - 'browser_monitoring.auto_instrument': False, - 'js_agent_loader': u'', + "browser_monitoring.enabled": False, + "browser_monitoring.auto_instrument": False, + "js_agent_loader": "", } @@ -337,264 +306,238 @@ def test_html_insertion_django_gzip_middleware_disabled(): # GZipMiddleware only fires if given the following header. - gzip_header = {'Accept-Encoding': 'gzip'} - response = test_application.get('/gzip_html_insertion', status=200, - headers=gzip_header) + gzip_header = {"Accept-Encoding": "gzip"} + response = test_application.get("/gzip_html_insertion", status=200, headers=gzip_header) # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. # The response.text will already be gunzipped - response.mustcontain(no=['NREUM HEADER', 'NREUM.info']) + response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) _test_html_insertion_manual_settings = { - 'browser_monitoring.enabled': True, - 'browser_monitoring.auto_instrument': True, - 'js_agent_loader': u'', + "browser_monitoring.enabled": True, + "browser_monitoring.auto_instrument": True, + "js_agent_loader": "", } @override_application_settings(_test_html_insertion_manual_settings) def test_html_insertion_manual_django_middleware(): test_application = target_application() - response = test_application.get('/html_insertion_manual', status=200) + response = test_application.get("/html_insertion_manual", status=200) # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. - response.mustcontain(no=['NREUM HEADER', 'NREUM.info']) + response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @override_application_settings(_test_html_insertion_settings) def test_html_insertion_unnamed_attachment_header_django_middleware(): test_application = target_application() - response = test_application.get( - '/html_insertion_unnamed_attachment_header', status=200) + response = test_application.get("/html_insertion_unnamed_attachment_header", status=200) # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. - response.mustcontain(no=['NREUM HEADER', 'NREUM.info']) + response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) @override_application_settings(_test_html_insertion_settings) def test_html_insertion_named_attachment_header_django_middleware(): test_application = target_application() - response = test_application.get( - '/html_insertion_named_attachment_header', status=200) + response = test_application.get("/html_insertion_named_attachment_header", status=200) # The 'NREUM HEADER' value comes from our override for the header. # The 'NREUM.info' value comes from the programmatically generated - # footer added by the agent. + # header added by the agent. - response.mustcontain(no=['NREUM HEADER', 'NREUM.info']) + response.mustcontain(no=["NREUM HEADER", "NREUM.info"]) _test_html_insertion_settings = { - 'browser_monitoring.enabled': True, - 'browser_monitoring.auto_instrument': False, - 'js_agent_loader': u'', + "browser_monitoring.enabled": True, + "browser_monitoring.auto_instrument": False, + "js_agent_loader": "", } @override_application_settings(_test_html_insertion_settings) def test_html_insertion_manual_tag_instrumentation(): test_application = target_application() - response = test_application.get('/template_tags') + response = test_application.get("/template_tags") # Assert that the instrumentation is not inappropriately escaped - response.mustcontain('', - no=['<!-- NREUM HEADER -->']) + response.mustcontain("", no=["<!-- NREUM HEADER -->"]) _test_application_inclusion_tag_scoped_metrics = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/views:inclusion_tag', 1), - ('Template/Render/main.html', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:inclusion_tag", 1), + ("Template/Render/main.html", 1), ] if DJANGO_VERSION < (1, 9): - _test_application_inclusion_tag_scoped_metrics.extend([ - ('Template/Include/results.html', 1)]) + _test_application_inclusion_tag_scoped_metrics.extend([("Template/Include/results.html", 1)]) if DJANGO_VERSION < (1, 10): - _test_application_inclusion_tag_scoped_metrics.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_application_inclusion_tag_scoped_metrics.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_application_inclusion_tag_scoped_metrics.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_application_inclusion_tag_scoped_metrics.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_application_inclusion_tag_scoped_metrics.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) - -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_application_inclusion_tag_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_application_inclusion_tag_scoped_metrics.extend( - _test_django_post_1_10_middleware_scoped_metrics) + _test_application_inclusion_tag_scoped_metrics.extend(_test_django_post_1_10_url_resolver_scoped_metrics) + +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_application_inclusion_tag_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_application_inclusion_tag_scoped_metrics.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_application_inclusion_tag_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_application_inclusion_tag_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) try: _test_application_inclusion_tag_scoped_metrics.remove( - (('Function/newrelic.hooks.framework_django:' - 'browser_timing_insertion'), 1) + (("Function/newrelic.hooks.framework_django:" "browser_timing_insertion"), 1) ) except ValueError: pass @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:inclusion_tag', - scoped_metrics=_test_application_inclusion_tag_scoped_metrics) +@validate_transaction_metrics("views:inclusion_tag", scoped_metrics=_test_application_inclusion_tag_scoped_metrics) @validate_code_level_metrics("views", "inclusion_tag") def test_application_inclusion_tag(): test_application = target_application() - response = test_application.get('/inclusion_tag') - response.mustcontain('Inclusion tag') + response = test_application.get("/inclusion_tag") + response.mustcontain("Inclusion tag") _test_inclusion_tag_template_tags_scoped_metrics = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/views:inclusion_tag', 1), - ('Template/Render/main.html', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:inclusion_tag", 1), + ("Template/Render/main.html", 1), ] if DJANGO_VERSION < (1, 9): - _test_inclusion_tag_template_tags_scoped_metrics.extend([ - ('Template/Include/results.html', 1), - ('Template/Tag/show_results', 1)]) + _test_inclusion_tag_template_tags_scoped_metrics.extend( + [("Template/Include/results.html", 1), ("Template/Tag/show_results", 1)] + ) -_test_inclusion_tag_settings = { - 'instrumentation.templates.inclusion_tag': '*' -} +_test_inclusion_tag_settings = {"instrumentation.templates.inclusion_tag": "*"} if DJANGO_VERSION < (1, 10): - _test_inclusion_tag_template_tags_scoped_metrics.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_inclusion_tag_template_tags_scoped_metrics.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_inclusion_tag_template_tags_scoped_metrics.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_inclusion_tag_template_tags_scoped_metrics.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_inclusion_tag_template_tags_scoped_metrics.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) + _test_inclusion_tag_template_tags_scoped_metrics.extend(_test_django_post_1_10_url_resolver_scoped_metrics) -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_inclusion_tag_template_tags_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_inclusion_tag_template_tags_scoped_metrics.extend( - _test_django_post_1_10_middleware_scoped_metrics) +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_inclusion_tag_template_tags_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_inclusion_tag_template_tags_scoped_metrics.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_inclusion_tag_template_tags_scoped_metrics.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_inclusion_tag_template_tags_scoped_metrics.extend(_test_django_pre_1_10_middleware_scoped_metrics) try: _test_inclusion_tag_template_tags_scoped_metrics.remove( - (('Function/newrelic.hooks.framework_django:' - 'browser_timing_insertion'), 1) + (("Function/newrelic.hooks.framework_django:" "browser_timing_insertion"), 1) ) except ValueError: pass @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('views:inclusion_tag', - scoped_metrics=_test_inclusion_tag_template_tags_scoped_metrics) +@validate_transaction_metrics("views:inclusion_tag", scoped_metrics=_test_inclusion_tag_template_tags_scoped_metrics) @override_generic_settings(django_settings, _test_inclusion_tag_settings) @validate_code_level_metrics("views", "inclusion_tag") def test_inclusion_tag_template_tag_metric(): test_application = target_application() - response = test_application.get('/inclusion_tag') - response.mustcontain('Inclusion tag') + response = test_application.get("/inclusion_tag") + response.mustcontain("Inclusion tag") _test_template_render_exception_scoped_metrics_base = [ - ('Function/django.core.handlers.wsgi:WSGIHandler.__call__', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), ] if DJANGO_VERSION < (1, 5): _test_template_render_exception_scoped_metrics_base.append( - ('Function/django.http:HttpResponseServerError.close', 1)) + ("Function/django.http:HttpResponseServerError.close", 1) + ) elif DJANGO_VERSION < (1, 8): _test_template_render_exception_scoped_metrics_base.append( - ('Function/django.http.response:HttpResponseServerError.close', 1)) + ("Function/django.http.response:HttpResponseServerError.close", 1) + ) else: - _test_template_render_exception_scoped_metrics_base.append( - ('Function/django.http.response:HttpResponse.close', 1)) + _test_template_render_exception_scoped_metrics_base.append(("Function/django.http.response:HttpResponse.close", 1)) if DJANGO_VERSION < (1, 10): - _test_template_render_exception_scoped_metrics_base.extend( - _test_django_pre_1_10_url_resolver_scoped_metrics) + _test_template_render_exception_scoped_metrics_base.extend(_test_django_pre_1_10_url_resolver_scoped_metrics) elif DJANGO_VERSION >= (2, 0): - _test_template_render_exception_scoped_metrics_base.extend( - _test_django_post_2_0_url_resolver_scoped_metrics) + _test_template_render_exception_scoped_metrics_base.extend(_test_django_post_2_0_url_resolver_scoped_metrics) else: - _test_template_render_exception_scoped_metrics_base.extend( - _test_django_post_1_10_url_resolver_scoped_metrics) - -if DJANGO_SETTINGS_MODULE == 'settings_0110_old': - _test_template_render_exception_scoped_metrics_base.extend( - _test_django_pre_1_10_middleware_scoped_metrics) -elif DJANGO_SETTINGS_MODULE == 'settings_0110_new': - _test_template_render_exception_scoped_metrics_base.extend( - _test_django_post_1_10_middleware_scoped_metrics) + _test_template_render_exception_scoped_metrics_base.extend(_test_django_post_1_10_url_resolver_scoped_metrics) + +if DJANGO_SETTINGS_MODULE == "settings_0110_old": + _test_template_render_exception_scoped_metrics_base.extend(_test_django_pre_1_10_middleware_scoped_metrics) +elif DJANGO_SETTINGS_MODULE == "settings_0110_new": + _test_template_render_exception_scoped_metrics_base.extend(_test_django_post_1_10_middleware_scoped_metrics) elif DJANGO_VERSION < (1, 10): - _test_template_render_exception_scoped_metrics_base.extend( - _test_django_pre_1_10_middleware_scoped_metrics) + _test_template_render_exception_scoped_metrics_base.extend(_test_django_pre_1_10_middleware_scoped_metrics) if DJANGO_VERSION < (1, 9): - _test_template_render_exception_errors = [ - 'django.template.base:TemplateSyntaxError'] + _test_template_render_exception_errors = ["django.template.base:TemplateSyntaxError"] else: - _test_template_render_exception_errors = [ - 'django.template.exceptions:TemplateSyntaxError'] + _test_template_render_exception_errors = ["django.template.exceptions:TemplateSyntaxError"] -_test_template_render_exception_function_scoped_metrics = list( - _test_template_render_exception_scoped_metrics_base) -_test_template_render_exception_function_scoped_metrics.extend([ - ('Function/views:render_exception_function', 1), -]) +_test_template_render_exception_function_scoped_metrics = list(_test_template_render_exception_scoped_metrics_base) +_test_template_render_exception_function_scoped_metrics.extend( + [ + ("Function/views:render_exception_function", 1), + ] +) @validate_transaction_errors(errors=_test_template_render_exception_errors) -@validate_transaction_metrics('views:render_exception_function', - scoped_metrics=_test_template_render_exception_function_scoped_metrics) +@validate_transaction_metrics( + "views:render_exception_function", scoped_metrics=_test_template_render_exception_function_scoped_metrics +) @validate_code_level_metrics("views", "render_exception_function") def test_template_render_exception_function(): test_application = target_application() - test_application.get('/render_exception_function', status=500) + test_application.get("/render_exception_function", status=500) -_test_template_render_exception_class_scoped_metrics = list( - _test_template_render_exception_scoped_metrics_base) -_test_template_render_exception_class_scoped_metrics.extend([ - ('Function/views:RenderExceptionClass', 1), - ('Function/views:RenderExceptionClass.get', 1), -]) +_test_template_render_exception_class_scoped_metrics = list(_test_template_render_exception_scoped_metrics_base) +_test_template_render_exception_class_scoped_metrics.extend( + [ + ("Function/views:RenderExceptionClass", 1), + ("Function/views:RenderExceptionClass.get", 1), + ] +) @validate_transaction_errors(errors=_test_template_render_exception_errors) -@validate_transaction_metrics('views:RenderExceptionClass.get', - scoped_metrics=_test_template_render_exception_class_scoped_metrics) +@validate_transaction_metrics( + "views:RenderExceptionClass.get", scoped_metrics=_test_template_render_exception_class_scoped_metrics +) @validate_code_level_metrics("views.RenderExceptionClass", "get") def test_template_render_exception_class(): test_application = target_application() - test_application.get('/render_exception_class', status=500) + test_application.get("/render_exception_class", status=500) diff --git a/tests/framework_django/views.py b/tests/framework_django/views.py index c5ce1526c7..e97e273ded 100644 --- a/tests/framework_django/views.py +++ b/tests/framework_django/views.py @@ -12,22 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +from django.core.exceptions import PermissionDenied from django.http import HttpResponse -from django.views.generic.base import View, TemplateView from django.shortcuts import render -from django.core.exceptions import PermissionDenied +from django.views.generic.base import TemplateView, View from middleware import Custom410 -from newrelic.api.transaction import (get_browser_timing_header, - get_browser_timing_footer) +from newrelic.api.transaction import get_browser_timing_header def index(request): - return HttpResponse('INDEX RESPONSE') + return HttpResponse("INDEX RESPONSE") def exception(request): - raise RuntimeError('exception') + raise RuntimeError("exception") def permission_denied(request): @@ -40,7 +39,7 @@ def middleware_410(request): class MyView(View): def get(self, request): - return HttpResponse('CBV RESPONSE') + return HttpResponse("CBV RESPONSE") def deferred_cbv(request): @@ -48,69 +47,77 @@ def deferred_cbv(request): def html_insertion(request): - return HttpResponse('Some header' - '

My First Heading

My first paragraph.

' - '') + return HttpResponse( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) def html_insertion_content_length(request): - content = ('Some header' - '

My First Heading

My first paragraph.

' - '') + content = ( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) response = HttpResponse(content) - response['Content-Length'] = len(content) + response["Content-Length"] = len(content) return response def html_insertion_manual(request): header = get_browser_timing_header() - footer = get_browser_timing_footer() - header = get_browser_timing_header() - footer = get_browser_timing_footer() - assert header == '' - assert footer == '' + assert header == "" - return HttpResponse('Some header' - '

My First Heading

My first paragraph.

' - '') + return HttpResponse( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) def html_insertion_unnamed_attachment_header(request): - response = HttpResponse('Some header' - '

My First Heading

My first paragraph.

' - '') - response['Content-Disposition'] = 'attachment' + response = HttpResponse( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) + response["Content-Disposition"] = "attachment" return response def html_insertion_named_attachment_header(request): - response = HttpResponse('Some header' - '

My First Heading

My first paragraph.

' - '') - response['Content-Disposition'] = 'Attachment; filename="X"' + response = HttpResponse( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) + response["Content-Disposition"] = 'Attachment; filename="X"' return response def inclusion_tag(request): - return render(request, 'main.html', {}, content_type="text/html") + return render(request, "main.html", {}, content_type="text/html") def template_tags(request): - return render(request, 'main.html', {}, content_type="text/html") + return render(request, "main.html", {}, content_type="text/html") def render_exception_function(request): - return render(request, 'render_exception.html') + return render(request, "render_exception.html") class RenderExceptionClass(TemplateView): - template_name = 'render_exception.html' + template_name = "render_exception.html" def gzip_html_insertion(request): # contents must be at least 200 bytes for gzip middleware to work - contents = '*' * 200 - return HttpResponse('Some header' - '

My First Heading

%s

' % contents) + contents = "*" * 200 + return HttpResponse( + "Some header" + "

My First Heading

%s

" % contents + ) diff --git a/tests/framework_flask/_test_compress.py b/tests/framework_flask/_test_compress.py index f3c9fbf2be..1fbf207689 100644 --- a/tests/framework_flask/_test_compress.py +++ b/tests/framework_flask/_test_compress.py @@ -18,14 +18,10 @@ import StringIO as IO import webtest - -from flask import Flask -from flask import Response -from flask import send_file +from flask import Flask, Response, send_file from flask_compress import Compress -from newrelic.api.transaction import (get_browser_timing_header, - get_browser_timing_footer) +from newrelic.api.transaction import get_browser_timing_header application = Flask(__name__) @@ -33,57 +29,57 @@ compress.init_app(application) -@application.route('/compress') +@application.route("/compress") def index_page(): - return '' + 500 * 'X' + '' + return "" + 500 * "X" + "" -@application.route('/html_insertion') +@application.route("/html_insertion") def html_insertion(): - return ('Some header' - '

My First Heading

My first paragraph.

' - '') + return ( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) -@application.route('/html_insertion_manual') +@application.route("/html_insertion_manual") def html_insertion_manual(): header = get_browser_timing_header() - footer = get_browser_timing_footer() - header = get_browser_timing_header() - footer = get_browser_timing_footer() - assert header == '' - assert footer == '' + assert header == "" - return ('Some header' - '

My First Heading

My first paragraph.

' - '') + return ( + "Some header" + "

My First Heading

My first paragraph.

" + "" + ) -@application.route('/html_insertion_unnamed_attachment_header') +@application.route("/html_insertion_unnamed_attachment_header") def html_insertion_unnamed_attachment_header(): response = Response( - response='Some header' - '

My First Heading

My first paragraph.

' - '') - response.headers.add('Content-Disposition', - 'attachment') + response="Some header" + "

My First Heading

My first paragraph.

" + "" + ) + response.headers.add("Content-Disposition", "attachment") return response -@application.route('/html_insertion_named_attachment_header') +@application.route("/html_insertion_named_attachment_header") def html_insertion_named_attachment_header(): response = Response( - response='Some header' - '

My First Heading

My first paragraph.

' - '') - response.headers.add('Content-Disposition', - 'attachment; filename="X"') + response="Some header" + "

My First Heading

My first paragraph.

" + "" + ) + response.headers.add("Content-Disposition", 'attachment; filename="X"') return response -@application.route('/html_served_from_file') +@application.route("/html_served_from_file") def html_served_from_file(): file = IO() contents = b""" @@ -93,10 +89,10 @@ def html_served_from_file(): """ file.write(contents) file.seek(0) - return send_file(file, mimetype='text/html') + return send_file(file, mimetype="text/html") -@application.route('/text_served_from_file') +@application.route("/text_served_from_file") def text_served_from_file(): file = IO() contents = b""" @@ -106,17 +102,19 @@ def text_served_from_file(): """ file.write(contents) file.seek(0) - return send_file(file, mimetype='text/plain') + return send_file(file, mimetype="text/plain") _test_application = webtest.TestApp(application) -@application.route('/empty_content_type') +@application.route("/empty_content_type") def empty_content_type(): response = Response( - response='Some header' - '

My First Heading

My first paragraph.

' - '', mimetype='') + response="Some header" + "

My First Heading

My first paragraph.

" + "", + mimetype="", + ) assert response.mimetype is None return response diff --git a/tests/framework_flask/test_application.py b/tests/framework_flask/test_application.py index de7a430191..508fb68934 100644 --- a/tests/framework_flask/test_application.py +++ b/tests/framework_flask/test_application.py @@ -13,23 +13,29 @@ # limitations under the License. import pytest - +from conftest import async_handler_support, skip_if_not_async_handler_support from testing_support.fixtures import ( override_application_settings, - validate_tt_parenting) -from testing_support.validators.validate_code_level_metrics import validate_code_level_metrics -from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics -from testing_support.validators.validate_transaction_errors import validate_transaction_errors + validate_tt_parenting, +) +from testing_support.validators.validate_code_level_metrics import ( + validate_code_level_metrics, +) +from testing_support.validators.validate_transaction_errors import ( + validate_transaction_errors, +) +from testing_support.validators.validate_transaction_metrics import ( + validate_transaction_metrics, +) from newrelic.packages import six -from conftest import async_handler_support, skip_if_not_async_handler_support - try: # The __version__ attribute was only added in 0.7.0. # Flask team does not use semantic versioning during development. from flask import __version__ as flask_version - flask_version = tuple([int(v) for v in flask_version.split('.')]) + + flask_version = tuple([int(v) for v in flask_version.split(".")]) is_gt_flask060 = True is_dev_version = False except ValueError: @@ -39,8 +45,7 @@ is_gt_flask060 = False is_dev_version = False -requires_endpoint_decorator = pytest.mark.skipif(not is_gt_flask060, - reason="The endpoint decorator is not supported.") +requires_endpoint_decorator = pytest.mark.skipif(not is_gt_flask060, reason="The endpoint decorator is not supported.") def target_application(): @@ -61,226 +66,254 @@ def target_application(): _test_application_index_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application:index_page', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_test_application:index_page", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), +] _test_application_index_tt_parenting = ( - 'TransactionNode', [ - ('FunctionNode', [ - ('FunctionNode', [ - ('FunctionNode', []), - ('FunctionNode', []), - ('FunctionNode', []), - # some flask versions have more FunctionNodes here, as appended - # below - ]), - ]), - ('FunctionNode', []), - ('FunctionNode', [ - ('FunctionNode', []), - ]), - ] + "TransactionNode", + [ + ( + "FunctionNode", + [ + ( + "FunctionNode", + [ + ("FunctionNode", []), + ("FunctionNode", []), + ("FunctionNode", []), + # some flask versions have more FunctionNodes here, as appended + # below + ], + ), + ], + ), + ("FunctionNode", []), + ( + "FunctionNode", + [ + ("FunctionNode", []), + ], + ), + ], ) if is_dev_version or (is_gt_flask060 and flask_version >= (0, 7)): _test_application_index_tt_parenting[1][0][1][0][1].append( - ('FunctionNode', []), + ("FunctionNode", []), ) if is_dev_version or (is_gt_flask060 and flask_version >= (0, 9)): _test_application_index_tt_parenting[1][0][1][0][1].append( - ('FunctionNode', []), + ("FunctionNode", []), ) + @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_application:index_page', - scoped_metrics=_test_application_index_scoped_metrics) +@validate_transaction_metrics("_test_application:index_page", scoped_metrics=_test_application_index_scoped_metrics) @validate_tt_parenting(_test_application_index_tt_parenting) @validate_code_level_metrics("_test_application", "index_page") def test_application_index(): application = target_application() - response = application.get('/index') - response.mustcontain('INDEX RESPONSE') + response = application.get("/index") + response.mustcontain("INDEX RESPONSE") + _test_application_async_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application_async:async_page', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_test_application_async:async_page", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), +] + @skip_if_not_async_handler_support @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_application_async:async_page', - scoped_metrics=_test_application_async_scoped_metrics) +@validate_transaction_metrics( + "_test_application_async:async_page", scoped_metrics=_test_application_async_scoped_metrics +) @validate_tt_parenting(_test_application_index_tt_parenting) @validate_code_level_metrics("_test_application_async", "async_page") def test_application_async(): application = target_application() - response = application.get('/async') - response.mustcontain('ASYNC RESPONSE') + response = application.get("/async") + response.mustcontain("ASYNC RESPONSE") + _test_application_endpoint_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application:endpoint_page', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_test_application:endpoint_page", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), +] @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_application:endpoint_page', - scoped_metrics=_test_application_endpoint_scoped_metrics) +@validate_transaction_metrics( + "_test_application:endpoint_page", scoped_metrics=_test_application_endpoint_scoped_metrics +) @validate_code_level_metrics("_test_application", "endpoint_page") def test_application_endpoint(): application = target_application() - response = application.get('/endpoint') - response.mustcontain('ENDPOINT RESPONSE') + response = application.get("/endpoint") + response.mustcontain("ENDPOINT RESPONSE") _test_application_error_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application:error_page', 1), - ('Function/flask.app:Flask.handle_exception', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1), - ('Function/flask.app:Flask.handle_user_exception', 1), - ('Function/flask.app:Flask.handle_user_exception', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_test_application:error_page", 1), + ("Function/flask.app:Flask.handle_exception", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), + ("Function/flask.app:Flask.handle_user_exception", 1), + ("Function/flask.app:Flask.handle_user_exception", 1), +] if six.PY3: - _test_application_error_errors = ['builtins:RuntimeError'] + _test_application_error_errors = ["builtins:RuntimeError"] else: - _test_application_error_errors = ['exceptions:RuntimeError'] + _test_application_error_errors = ["exceptions:RuntimeError"] @validate_transaction_errors(errors=_test_application_error_errors) -@validate_transaction_metrics('_test_application:error_page', - scoped_metrics=_test_application_error_scoped_metrics) +@validate_transaction_metrics("_test_application:error_page", scoped_metrics=_test_application_error_scoped_metrics) @validate_code_level_metrics("_test_application", "error_page") def test_application_error(): application = target_application() - application.get('/error', status=500, expect_errors=True) + application.get("/error", status=500, expect_errors=True) _test_application_abort_404_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application:abort_404_page', 1), - ('Function/flask.app:Flask.handle_http_exception', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1), - ('Function/flask.app:Flask.handle_user_exception', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_test_application:abort_404_page", 1), + ("Function/flask.app:Flask.handle_http_exception", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), + ("Function/flask.app:Flask.handle_user_exception", 1), +] @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_application:abort_404_page', - scoped_metrics=_test_application_abort_404_scoped_metrics) +@validate_transaction_metrics( + "_test_application:abort_404_page", scoped_metrics=_test_application_abort_404_scoped_metrics +) @validate_code_level_metrics("_test_application", "abort_404_page") def test_application_abort_404(): application = target_application() - application.get('/abort_404', status=404) + application.get("/abort_404", status=404) _test_application_exception_404_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application:exception_404_page', 1), - ('Function/flask.app:Flask.handle_http_exception', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1), - ('Function/flask.app:Flask.handle_user_exception', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/_test_application:exception_404_page", 1), + ("Function/flask.app:Flask.handle_http_exception", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), + ("Function/flask.app:Flask.handle_user_exception", 1), +] @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_application:exception_404_page', - scoped_metrics=_test_application_exception_404_scoped_metrics) +@validate_transaction_metrics( + "_test_application:exception_404_page", scoped_metrics=_test_application_exception_404_scoped_metrics +) @validate_code_level_metrics("_test_application", "exception_404_page") def test_application_exception_404(): application = target_application() - application.get('/exception_404', status=404) + application.get("/exception_404", status=404) _test_application_not_found_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/flask.app:Flask.handle_http_exception', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1), - ('Function/flask.app:Flask.handle_user_exception', 1)] + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/flask.app:Flask.handle_http_exception", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), + ("Function/flask.app:Flask.handle_user_exception", 1), +] @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('flask.app:Flask.handle_http_exception', - scoped_metrics=_test_application_not_found_scoped_metrics) +@validate_transaction_metrics( + "flask.app:Flask.handle_http_exception", scoped_metrics=_test_application_not_found_scoped_metrics +) def test_application_not_found(): application = target_application() - application.get('/missing', status=404) + application.get("/missing", status=404) _test_application_render_template_string_scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/_test_application:template_string', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1), - ('Template/Compile/