From b65f76dee1b7c176a9e9028b0a0425929931843b Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Mon, 21 Jul 2025 11:23:06 -0700 Subject: [PATCH 1/7] Convert username lookup to more portable option --- newrelic/admin/record_deploy.py | 5 ++--- tests/testing_support/db_settings.py | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/newrelic/admin/record_deploy.py b/newrelic/admin/record_deploy.py index 7851253c59..116fde5326 100644 --- a/newrelic/admin/record_deploy.py +++ b/newrelic/admin/record_deploy.py @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import pwd +import getpass from newrelic.admin import command, usage from newrelic.common import agent_http, encoding_utils @@ -79,7 +78,7 @@ def record_deploy( path = f"/v2/applications/{app_id}/deployments.json" if user is None: - user = pwd.getpwuid(os.getuid()).pw_gecos + user = getpass.getuser() deployment = {} deployment["revision"] = revision diff --git a/tests/testing_support/db_settings.py b/tests/testing_support/db_settings.py index cb51a01e23..f11e876cbf 100644 --- a/tests/testing_support/db_settings.py +++ b/tests/testing_support/db_settings.py @@ -13,9 +13,6 @@ # limitations under the License. import os -import pwd - -USER = pwd.getpwuid(os.getuid()).pw_name def postgresql_settings(): From 8fcc0a951fffd017c3c0f2bd62441cfe72288e20 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Tue, 22 Jul 2025 12:02:52 -0700 Subject: [PATCH 2/7] Fix handling of health dir paths on Windows --- newrelic/core/agent_control_health.py | 132 ++++++++++++------ .../test_agent_control_health_check.py | 32 ++++- 2 files changed, 111 insertions(+), 53 deletions(-) diff --git a/newrelic/core/agent_control_health.py b/newrelic/core/agent_control_health.py index 33e49a8a16..dd7d3fff7b 100644 --- a/newrelic/core/agent_control_health.py +++ b/newrelic/core/agent_control_health.py @@ -15,12 +15,14 @@ import logging import os import sched +import sys import threading import time import uuid from enum import IntEnum from pathlib import Path from urllib.parse import urlparse +from urllib.request import url2pathname from newrelic.core.config import _environ_as_bool, _environ_as_int @@ -66,43 +68,6 @@ class HealthStatus(IntEnum): NR_CONNECTION_ERROR_CODES = frozenset([HealthStatus.FAILED_NR_CONNECTION.value, HealthStatus.FORCED_DISCONNECT.value]) -def is_valid_file_delivery_location(file_uri): - # Verify whether file directory provided to agent via env var is a valid file URI to determine whether health - # check should run - try: - parsed_uri = urlparse(file_uri) - if not parsed_uri.scheme or not parsed_uri.path: - _logger.warning( - "Configured Agent Control health delivery location is not a complete file URI. Health check will not be " - "enabled. " - ) - return False - - if parsed_uri.scheme != "file": - _logger.warning( - "Configured Agent Control health delivery location does not have a valid scheme. Health check will not be " - "enabled." - ) - return False - - path = Path(parsed_uri.path) - - # Check if the path exists - if not path.exists(): - _logger.warning( - "Configured Agent Control health delivery location does not exist. Health check will not be enabled." - ) - return False - - return True - - except Exception: - _logger.warning( - "Configured Agent Control health delivery location is not valid. Health check will not be enabled." - ) - return False - - class AgentControlHealth: _instance_lock = threading.Lock() _instance = None @@ -127,6 +92,7 @@ def __init__(self): self.status_message = HEALTHY_STATUS_MESSAGE self.start_time_unix_nano = None self.pid_file_id_map = {} + self._health_delivery_location_cache = {} @property def health_check_enabled(self): @@ -135,16 +101,87 @@ def health_check_enabled(self): if not agent_control_enabled: return False - return is_valid_file_delivery_location(self.health_delivery_location) + return self.health_delivery_location_is_valid @property def health_delivery_location(self): - # Set a default file path if env var is not set or set to an empty string - health_file_location = ( + file_uri = ( os.environ.get("NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION", "") or "file:///newrelic/apm/health" ) - return health_file_location + # Return from cache if already parsed + if file_uri in self._health_delivery_location_cache: + return self._health_delivery_location_cache[file_uri] + + # Parse and add to cache + path = self.parse_health_delivery_location(file_uri) + if path is not None: + self._health_delivery_location_cache[file_uri] = path + + return path + + @property + def health_delivery_location_is_valid(self): + # Verify whether file directory provided to agent via env var is a valid file URI to determine whether health + # check should run + try: + path = self.health_delivery_location + if path is None: + # Warning already logged in parse_health_delivery_location() + return False + + # Check if the path exists + if not path.exists(): + _logger.warning( + "Configured Agent Control health delivery location does not exist. Health check will not be enabled." + ) + return False + + return True + + except Exception: + _logger.warning( + "Configured Agent Control health delivery location is not valid. Health check will not be enabled." + ) + return False + + @classmethod + def parse_health_delivery_location(cls, file_uri): + """Parse the health delivery location and return it as a Path object.""" + + # No built in method to correctly parse file URI to a path on Python < 3.13. + # In the future, Path.from_uri() can be used directly. + + # For now, parse with urllib.parse.urlparse and convert to a Path object. + parsed_uri = urlparse(file_uri) + + # Ensure URI has at least a scheme and path + if not parsed_uri.scheme or not parsed_uri.path: + _logger.warning( + "Configured Agent Control health delivery location is not a complete file URI. Health check will not be enabled." + ) + return None + + # Ensure URI has a file scheme + if parsed_uri.scheme != "file": + _logger.warning( + "Configured Agent Control health delivery location does not have a valid scheme. Health check will not be enabled." + ) + return None + + # Handle Windows systems carefully due to inconsistent path handling + if sys.platform == "win32": + if parsed_uri.netloc: + # Matching behavior of pip where netloc is prepended with a double backslash + # https://github.com/pypa/pip/blob/022248f6484fe87dc0ef5aec3437f4c7971fd14b/pip/download.py#L442 + urlpathname = url2pathname(rf"\\\\{parsed_uri.netloc}{parsed_uri.path}") + return Path(urlpathname) + else: + # If there's no netloc, we use url2pathname to fix leading slashes + return Path(url2pathname(parsed_uri.path)) + else: + # On non-Windows systems we can use the parsed path directly + return Path(parsed_uri.path) @property def is_healthy(self): @@ -185,13 +222,16 @@ def write_to_health_file(self): status_time_unix_nano = time.time_ns() try: - file_path = urlparse(self.health_delivery_location).path + health_dir_path = self.health_delivery_location + if health_dir_path is None: + # Allow except block to handle logging a warning + raise ValueError("Health delivery location is not valid.") + file_id = self.get_file_id() - file_name = f"health-{file_id}.yml" - full_path = Path(file_path) / file_name - is_healthy = self.is_healthy + health_file_path = health_dir_path / f"health-{file_id}.yml" + is_healthy = self.is_healthy # Cache property value to avoid multiple calls - with full_path.open("w") as f: + with health_file_path.open("w") as f: f.write(f"healthy: {is_healthy}\n") f.write(f"status: {self.status_message}\n") f.write(f"start_time_unix_nano: {self.start_time_unix_nano}\n") diff --git a/tests/agent_features/test_agent_control_health_check.py b/tests/agent_features/test_agent_control_health_check.py index a8ceb893e4..92b8d3c5ad 100644 --- a/tests/agent_features/test_agent_control_health_check.py +++ b/tests/agent_features/test_agent_control_health_check.py @@ -17,15 +17,12 @@ from pathlib import Path import pytest +import sys from testing_support.fixtures import initialize_agent from testing_support.http_client_recorder import HttpClientRecorder from newrelic.config import _reset_configuration_done, initialize -from newrelic.core.agent_control_health import ( - HealthStatus, - agent_control_health_instance, - is_valid_file_delivery_location, -) +from newrelic.core.agent_control_health import HealthStatus, agent_control_health_instance from newrelic.core.agent_protocol import AgentProtocol from newrelic.core.application import Application from newrelic.core.config import finalize_application_settings, global_settings @@ -41,8 +38,29 @@ def get_health_file_contents(tmp_path): @pytest.mark.parametrize("file_uri", ["", "file://", "/test/dir", "foo:/test/dir"]) -def test_invalid_file_directory_supplied(file_uri): - assert not is_valid_file_delivery_location(file_uri) +def test_invalid_file_directory_supplied(monkeypatch, file_uri): + # Setup expected env vars to run agent control health check + monkeypatch.setenv("NEW_RELIC_AGENT_CONTROL_ENABLED", "True") + monkeypatch.setenv("NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION", file_uri) + + agent_control_instance = agent_control_health_instance() + assert not agent_control_instance.health_delivery_location_is_valid + + +@pytest.mark.skipif(sys.platform != "win32", reason="Only valid for Windows") +@pytest.mark.parametrize("leading_slash", [True, False], ids=["leading_slash", "no_leading_slash"]) +def test_inconsistent_paths_on_windows(monkeypatch, tmp_path, leading_slash): + file_uri = tmp_path.as_uri() + if not leading_slash: + assert file_uri.startswith("file:///") + file_uri.replace("file:///", "file://") + + # Setup expected env vars to run agent control health check + monkeypatch.setenv("NEW_RELIC_AGENT_CONTROL_ENABLED", "True") + monkeypatch.setenv("NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION", file_uri) + + agent_control_instance = agent_control_health_instance() + assert agent_control_instance.health_delivery_location_is_valid def test_agent_control_not_enabled(monkeypatch, tmp_path): From 47c1e1049d06fd21ecc3977a92796bf6bd35b4fc Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Mon, 28 Jul 2025 11:31:54 -0700 Subject: [PATCH 3/7] Fix issues related to tempfile on Windows --- .../test_agent_control_health_check.py | 2 +- tests/agent_features/test_configuration.py | 26 +++++++++---------- tests/agent_unittests/conftest.py | 4 +-- tests/agent_unittests/test_agent_protocol.py | 3 ++- .../test_utilization_settings.py | 6 ++--- .../test_boot_id_utilization_data.py | 6 ++--- tests/cross_agent/test_collector_hostname.py | 11 ++++---- tests/cross_agent/test_utilization_configs.py | 26 +++++++++---------- tests/testing_support/util.py | 26 +++++++++++++++++++ 9 files changed, 68 insertions(+), 42 deletions(-) diff --git a/tests/agent_features/test_agent_control_health_check.py b/tests/agent_features/test_agent_control_health_check.py index 92b8d3c5ad..4c6263362c 100644 --- a/tests/agent_features/test_agent_control_health_check.py +++ b/tests/agent_features/test_agent_control_health_check.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import re +import sys import threading import time from pathlib import Path import pytest -import sys from testing_support.fixtures import initialize_agent from testing_support.http_client_recorder import HttpClientRecorder diff --git a/tests/agent_features/test_configuration.py b/tests/agent_features/test_configuration.py index 9586613bf8..41d929f131 100644 --- a/tests/agent_features/test_configuration.py +++ b/tests/agent_features/test_configuration.py @@ -17,11 +17,11 @@ import logging import pathlib import sys -import tempfile import urllib.parse as urlparse import pytest from testing_support.fixtures import override_generic_settings +from testing_support.util import NamedTemporaryFile from newrelic.api.exceptions import ConfigurationError from newrelic.common.object_names import callable_name @@ -637,7 +637,7 @@ def test_initialize(): def test_initialize_raises_if_config_does_not_match_previous(): error_message = "Configuration has already been done against differing configuration file or environment.*" with pytest.raises(ConfigurationError, match=error_message): - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -646,7 +646,7 @@ def test_initialize_raises_if_config_does_not_match_previous(): def test_initialize_via_config_file(): _reset_configuration_done() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -667,7 +667,7 @@ def test_initialize_config_file_does_not_exist(): def test_initialize_environment(): _reset_configuration_done() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -676,7 +676,7 @@ def test_initialize_environment(): def test_initialize_log_level(): _reset_configuration_done() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -685,7 +685,7 @@ def test_initialize_log_level(): def test_initialize_log_file(): _reset_configuration_done() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -700,7 +700,7 @@ def test_initialize_config_file_feature_flag(feature_flag, expect_warning, logge apply_config_setting(settings, "feature_flag", feature_flag) _reset_configuration_done() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -759,7 +759,7 @@ def test_initialize_config_file_with_traces(setting_name, setting_value, expect_ apply_config_setting(settings, setting_name, setting_value) _reset_configuration_done() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.seek(0) @@ -951,7 +951,7 @@ def test_initialize_developer_mode(section, expect_error, logger): _reset_instrumentation_done() _reset_config_parser() - with tempfile.NamedTemporaryFile() as f: + with NamedTemporaryFile() as f: f.write(newrelic_ini_contents) f.write(section) f.seek(0) @@ -1019,7 +1019,7 @@ def test_toml_parse_development(): _reset_config_parser() _reset_instrumentation_done() - with tempfile.NamedTemporaryFile(suffix=".toml") as f: + with NamedTemporaryFile(suffix=".toml") as f: f.write(newrelic_toml_contents) f.seek(0) @@ -1041,7 +1041,7 @@ def test_toml_parse_production(): _reset_config_parser() _reset_instrumentation_done() - with tempfile.NamedTemporaryFile(suffix=".toml") as f: + with NamedTemporaryFile(suffix=".toml") as f: f.write(newrelic_toml_contents) f.seek(0) @@ -1061,7 +1061,7 @@ def test_config_file_path_types_ini(pathtype): _reset_config_parser() _reset_instrumentation_done() - with tempfile.NamedTemporaryFile(suffix=".ini") as f: + with NamedTemporaryFile(suffix=".ini") as f: f.write(newrelic_ini_contents) f.seek(0) @@ -1081,7 +1081,7 @@ def test_config_file_path_types_toml(pathtype): _reset_config_parser() _reset_instrumentation_done() - with tempfile.NamedTemporaryFile(suffix=".toml") as f: + with NamedTemporaryFile(suffix=".toml") as f: f.write(newrelic_toml_contents) f.seek(0) diff --git a/tests/agent_unittests/conftest.py b/tests/agent_unittests/conftest.py index 65f22b2079..aee203a783 100644 --- a/tests/agent_unittests/conftest.py +++ b/tests/agent_unittests/conftest.py @@ -13,12 +13,12 @@ # limitations under the License. import sys -import tempfile from importlib import reload import pytest from testing_support.fixtures import collector_agent_registration_fixture, collector_available_fixture from testing_support.fixtures import newrelic_caplog as caplog +from testing_support.util import NamedTemporaryFile from newrelic.core.agent import agent_instance @@ -69,7 +69,7 @@ def global_settings(request, monkeypatch): reload(core_config) reload(config) - with tempfile.NamedTemporaryFile() as ini_file: + with NamedTemporaryFile() as ini_file: ini_file.write(ini_contents) ini_file.seek(0) diff --git a/tests/agent_unittests/test_agent_protocol.py b/tests/agent_unittests/test_agent_protocol.py index 4bcf7dc579..972d8be5bf 100644 --- a/tests/agent_unittests/test_agent_protocol.py +++ b/tests/agent_unittests/test_agent_protocol.py @@ -522,7 +522,8 @@ def test_audit_logging(): protocol = AgentProtocol(settings, client_cls=HttpClientRecorder) protocol.send("preconnect") - with Path(f.name).open() as f: + audit_log_path = Path(f.name) + with audit_log_path.open() as f: audit_log_contents = f.read() assert audit_log_contents.startswith("*\n") diff --git a/tests/agent_unittests/test_utilization_settings.py b/tests/agent_unittests/test_utilization_settings.py index 11ded562c4..40c3bd1d94 100644 --- a/tests/agent_unittests/test_utilization_settings.py +++ b/tests/agent_unittests/test_utilization_settings.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import tempfile from importlib import reload import pytest from testing_support.fixtures import Environ +from testing_support.util import NamedTemporaryFile # these will be reloaded for each test import newrelic.config @@ -73,8 +72,7 @@ def reset_agent_config(ini_contents, env_dict): @function_wrapper def reset(wrapped, instance, args, kwargs): - with Environ(env_dict): - ini_file = tempfile.NamedTemporaryFile() + with Environ(env_dict), NamedTemporaryFile() as ini_file: ini_file.write(ini_contents) ini_file.seek(0) diff --git a/tests/cross_agent/test_boot_id_utilization_data.py b/tests/cross_agent/test_boot_id_utilization_data.py index 3d0f6dec58..14af6cc6a0 100644 --- a/tests/cross_agent/test_boot_id_utilization_data.py +++ b/tests/cross_agent/test_boot_id_utilization_data.py @@ -14,10 +14,10 @@ import json import sys -import tempfile from pathlib import Path import pytest +from testing_support.util import NamedTemporaryFile from testing_support.validators.validate_internal_metrics import validate_internal_metrics from newrelic.common.system_info import BootIdUtilization @@ -57,7 +57,7 @@ def __init__(self, boot_id): def __enter__(self): if self.boot_id is not None: - self.boot_id_file = tempfile.NamedTemporaryFile() + self.boot_id_file = NamedTemporaryFile().__enter__() self.boot_id_file.write(self.boot_id.encode("utf8")) self.boot_id_file.seek(0) BootIdUtilization.METADATA_URL = self.boot_id_file.name @@ -68,7 +68,7 @@ def __enter__(self): def __exit__(self, *args, **kwargs): sys.platform = SYS_PLATFORM if self.boot_id: - del self.boot_id_file # close and thus delete the tempfile + self.boot_id_file.__exit__(*args, **kwargs) # close and thus delete the tempfile @pytest.mark.parametrize(_parameters, _boot_id_tests) diff --git a/tests/cross_agent/test_collector_hostname.py b/tests/cross_agent/test_collector_hostname.py index 57f0654b60..9154f05aea 100644 --- a/tests/cross_agent/test_collector_hostname.py +++ b/tests/cross_agent/test_collector_hostname.py @@ -16,11 +16,11 @@ import multiprocessing import os import sys -import tempfile from importlib import reload from pathlib import Path import pytest +from testing_support.util import NamedTemporaryFile FIXTURE = Path(__file__).parent / "fixtures" / "collector_hostname.json" @@ -70,11 +70,12 @@ def _test_collector_hostname( reload(core_config) reload(config) - ini_file = tempfile.NamedTemporaryFile() - ini_file.write(ini_contents.encode("utf-8")) - ini_file.seek(0) + with NamedTemporaryFile() as ini_file: + ini_file.write(ini_contents.encode("utf-8")) + ini_file.seek(0) + + config.initialize(ini_file.name) - config.initialize(ini_file.name) settings = core_config.global_settings() assert settings.host == hostname diff --git a/tests/cross_agent/test_utilization_configs.py b/tests/cross_agent/test_utilization_configs.py index 9534d65756..47482c0f94 100644 --- a/tests/cross_agent/test_utilization_configs.py +++ b/tests/cross_agent/test_utilization_configs.py @@ -15,7 +15,6 @@ import json import os import sys -import tempfile from importlib import reload from pathlib import Path @@ -24,6 +23,7 @@ # NOTE: the test_utilization_settings_from_env_vars test mocks several of the # methods in newrelic.core.data_collector and does not put them back! from testing_support.mock_http_client import create_client_cls +from testing_support.util import NamedTemporaryFile import newrelic.core.config from newrelic.common.object_wrapper import function_wrapper @@ -132,19 +132,19 @@ def _patch_boot_id_file(wrapped, instance, args, kwargs): boot_id_file = None initial_sys_platform = sys.platform - if test.get("input_boot_id"): - boot_id_file = tempfile.NamedTemporaryFile() - boot_id_file.write(test.get("input_boot_id")) - boot_id_file.seek(0) - BootIdUtilization.METADATA_URL = boot_id_file.name - sys.platform = "linux-mock-testing" # ensure boot_id is gathered - else: - # do not gather boot_id at all, this will ensure there is nothing - # extra in the gathered utilizations data - sys.platform = "not-linux" - try: - return wrapped(*args, **kwargs) + if test.get("input_boot_id"): + with NamedTemporaryFile() as boot_id_file: + boot_id_file.write(test.get("input_boot_id")) + boot_id_file.seek(0) + BootIdUtilization.METADATA_URL = boot_id_file.name + sys.platform = "linux-mock-testing" # ensure boot_id is gathered + return wrapped(*args, **kwargs) + else: + # do not gather boot_id at all, this will ensure there is nothing + # extra in the gathered utilizations data + sys.platform = "not-linux" + return wrapped(*args, **kwargs) finally: del boot_id_file # close and thus delete the tempfile sys.platform = initial_sys_platform diff --git a/tests/testing_support/util.py b/tests/testing_support/util.py index 86d5505ea3..e3438294cf 100644 --- a/tests/testing_support/util.py +++ b/tests/testing_support/util.py @@ -14,8 +14,11 @@ import re import socket +import sys +import tempfile import time from functools import wraps +from pathlib import Path def _to_int(version_str): @@ -83,3 +86,26 @@ def wrapper(*args, **kwargs): return wrapper return decorator + + +def NamedTemporaryFile(*args, **kwargs): + """A wrapper around tempfile.NamedTemporaryFile that fixes issues with file flags on Windows.""" + if sys.platform == "win32": + # Set delete=False to prevent file flags being set incorrectly on Windows. + kwargs["delete"] = False + + # Create the temporary file + temp_file = tempfile.NamedTemporaryFile(*args, **kwargs) + temp_file.path = Path(temp_file.name) # Add path attribute for convenience + + # Patch the __exit__ method to manually remove the file on exit + original_exit = temp_file.__exit__ + + def remove_on_exit(*args, **kwargs): + original_exit(*args, **kwargs) + # Clean up the file manually + temp_file.path.unlink(missing_ok=True) + + temp_file.__exit__ = remove_on_exit + + return temp_file From 74a43ab74b58149fcef0f7f73d55ed3ea65fad92 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Thu, 31 Jul 2025 09:28:47 -0700 Subject: [PATCH 4/7] Fix encoding in test fixtures --- tests/agent_unittests/test_aws_utilization_caching.py | 2 +- tests/cross_agent/test_agent_attributes.py | 2 +- tests/cross_agent/test_aws_utilization_data.py | 2 +- tests/cross_agent/test_azure_utilization_data.py | 2 +- tests/cross_agent/test_boot_id_utilization_data.py | 2 +- tests/cross_agent/test_cat_map.py | 2 +- tests/cross_agent/test_collector_hostname.py | 2 +- tests/cross_agent/test_datastore_instance.py | 2 +- tests/cross_agent/test_distributed_tracing.py | 2 +- tests/cross_agent/test_docker_container_id.py | 2 +- tests/cross_agent/test_docker_container_id_v2.py | 2 +- tests/cross_agent/test_gcp_utilization_data.py | 2 +- tests/cross_agent/test_labels_and_rollups.py | 2 +- tests/cross_agent/test_lambda_event_source.py | 4 ++-- tests/cross_agent/test_pcf_utilization_data.py | 2 +- tests/cross_agent/test_rules.py | 2 +- tests/cross_agent/test_sql_obfuscation.py | 2 +- tests/cross_agent/test_transaction_segment_terms.py | 2 +- tests/cross_agent/test_utilization_configs.py | 2 +- tests/cross_agent/test_w3c_trace_context.py | 2 +- 20 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/agent_unittests/test_aws_utilization_caching.py b/tests/agent_unittests/test_aws_utilization_caching.py index e62eaca406..885aa6463a 100644 --- a/tests/agent_unittests/test_aws_utilization_caching.py +++ b/tests/agent_unittests/test_aws_utilization_caching.py @@ -39,7 +39,7 @@ def no_token(cls): def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_agent_attributes.py b/tests/cross_agent/test_agent_attributes.py index 40f1baa36e..778c816c0f 100644 --- a/tests/cross_agent/test_agent_attributes.py +++ b/tests/cross_agent/test_agent_attributes.py @@ -44,7 +44,7 @@ def _default_settings(): def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_aws_utilization_data.py b/tests/cross_agent/test_aws_utilization_data.py index 643735e71c..27f0405d57 100644 --- a/tests/cross_agent/test_aws_utilization_data.py +++ b/tests/cross_agent/test_aws_utilization_data.py @@ -29,7 +29,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_azure_utilization_data.py b/tests/cross_agent/test_azure_utilization_data.py index baabd5a7b0..d99d9ea0c9 100644 --- a/tests/cross_agent/test_azure_utilization_data.py +++ b/tests/cross_agent/test_azure_utilization_data.py @@ -29,7 +29,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_boot_id_utilization_data.py b/tests/cross_agent/test_boot_id_utilization_data.py index 14af6cc6a0..bbc988c79b 100644 --- a/tests/cross_agent/test_boot_id_utilization_data.py +++ b/tests/cross_agent/test_boot_id_utilization_data.py @@ -39,7 +39,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_cat_map.py b/tests/cross_agent/test_cat_map.py index 01ede0b19a..3322fc7d97 100644 --- a/tests/cross_agent/test_cat_map.py +++ b/tests/cross_agent/test_cat_map.py @@ -67,7 +67,7 @@ def server(): def load_tests(): result = [] - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_collector_hostname.py b/tests/cross_agent/test_collector_hostname.py index 9154f05aea..6da387fac6 100644 --- a/tests/cross_agent/test_collector_hostname.py +++ b/tests/cross_agent/test_collector_hostname.py @@ -29,7 +29,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_datastore_instance.py b/tests/cross_agent/test_datastore_instance.py index e6e3f61c31..a35b3e65dd 100644 --- a/tests/cross_agent/test_datastore_instance.py +++ b/tests/cross_agent/test_datastore_instance.py @@ -40,7 +40,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_distributed_tracing.py b/tests/cross_agent/test_distributed_tracing.py index ae3097db2f..3c7314b31d 100644 --- a/tests/cross_agent/test_distributed_tracing.py +++ b/tests/cross_agent/test_distributed_tracing.py @@ -52,7 +52,7 @@ def load_tests(): result = [] - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_docker_container_id.py b/tests/cross_agent/test_docker_container_id.py index a54c982c88..804bd2afad 100644 --- a/tests/cross_agent/test_docker_container_id.py +++ b/tests/cross_agent/test_docker_container_id.py @@ -28,7 +28,7 @@ def _load_docker_test_attributes(): """ test_cases = DOCKER_FIXTURE / "cases.json" - with test_cases.open() as fh: + with test_cases.open(encoding="utf-8") as fh: json_list = json.load(fh) docker_test_attributes = [(json_record["filename"], json_record["containerId"]) for json_record in json_list] return docker_test_attributes diff --git a/tests/cross_agent/test_docker_container_id_v2.py b/tests/cross_agent/test_docker_container_id_v2.py index 5603584668..217fcc03a5 100644 --- a/tests/cross_agent/test_docker_container_id_v2.py +++ b/tests/cross_agent/test_docker_container_id_v2.py @@ -28,7 +28,7 @@ def _load_docker_test_attributes(): """ test_cases = DOCKER_FIXTURE / "cases.json" - with test_cases.open() as fh: + with test_cases.open(encoding="utf-8") as fh: json_list = json.load(fh) docker_test_attributes = [(json_record["filename"], json_record["containerId"]) for json_record in json_list] return docker_test_attributes diff --git a/tests/cross_agent/test_gcp_utilization_data.py b/tests/cross_agent/test_gcp_utilization_data.py index 1439c92e2b..3d79eba20b 100644 --- a/tests/cross_agent/test_gcp_utilization_data.py +++ b/tests/cross_agent/test_gcp_utilization_data.py @@ -29,7 +29,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_labels_and_rollups.py b/tests/cross_agent/test_labels_and_rollups.py index a16aac9c5a..ba74f8ca58 100644 --- a/tests/cross_agent/test_labels_and_rollups.py +++ b/tests/cross_agent/test_labels_and_rollups.py @@ -24,7 +24,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_lambda_event_source.py b/tests/cross_agent/test_lambda_event_source.py index 74ca28e5d7..325a920f6c 100644 --- a/tests/cross_agent/test_lambda_event_source.py +++ b/tests/cross_agent/test_lambda_event_source.py @@ -28,13 +28,13 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: for test in json.loads(fh.read()): test_name = test.pop("name") test_file = f"{test_name}.json" path = FIXTURE_DIR / "lambda_event_source" / test_file - with path.open() as fh: + with path.open(encoding="utf-8") as fh: events[test_name] = json.loads(fh.read()) tests[test_name] = test diff --git a/tests/cross_agent/test_pcf_utilization_data.py b/tests/cross_agent/test_pcf_utilization_data.py index 4594a55a66..d25a7b3257 100644 --- a/tests/cross_agent/test_pcf_utilization_data.py +++ b/tests/cross_agent/test_pcf_utilization_data.py @@ -29,7 +29,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_rules.py b/tests/cross_agent/test_rules.py index ca1eb8d354..e4d2fd1b0a 100644 --- a/tests/cross_agent/test_rules.py +++ b/tests/cross_agent/test_rules.py @@ -27,7 +27,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_sql_obfuscation.py b/tests/cross_agent/test_sql_obfuscation.py index 2d166b395f..178625cdf3 100644 --- a/tests/cross_agent/test_sql_obfuscation.py +++ b/tests/cross_agent/test_sql_obfuscation.py @@ -27,7 +27,7 @@ def load_tests(): result = [] - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_transaction_segment_terms.py b/tests/cross_agent/test_transaction_segment_terms.py index 49dc6a19e5..1463bea7b6 100644 --- a/tests/cross_agent/test_transaction_segment_terms.py +++ b/tests/cross_agent/test_transaction_segment_terms.py @@ -32,7 +32,7 @@ def load_tests(): result = [] - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_utilization_configs.py b/tests/cross_agent/test_utilization_configs.py index 47482c0f94..ffabbb625b 100644 --- a/tests/cross_agent/test_utilization_configs.py +++ b/tests/cross_agent/test_utilization_configs.py @@ -37,7 +37,7 @@ def _load_tests(): - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_w3c_trace_context.py b/tests/cross_agent/test_w3c_trace_context.py index 72745d3b5b..bc0de4f003 100644 --- a/tests/cross_agent/test_w3c_trace_context.py +++ b/tests/cross_agent/test_w3c_trace_context.py @@ -62,7 +62,7 @@ def load_tests(): result = [] - with FIXTURE.open() as fh: + with FIXTURE.open(encoding="utf-8") as fh: tests = json.load(fh) for test in tests: From d67dce97e6a436e049c8934e90c44009625297fe Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Thu, 31 Jul 2025 09:53:26 -0700 Subject: [PATCH 5/7] Fix globbing on Windows --- newrelic/config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/newrelic/config.py b/newrelic/config.py index 0b2ad73567..c8352c7999 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -1194,11 +1194,11 @@ def _module_function_glob(module, object_path): # Skip adding all class methods on failure pass - # Under the hood uses fnmatch, which uses os.path.normcase - # On windows this would cause issues with case insensitivity, - # but on all other operating systems there should be no issues. - return fnmatch.filter(available_functions, object_path) - + # Globbing must be done using fnmatch.fnmatchcase as + # fnmatch.filter and fnmatch.fnmatch use os.path.normcase + # which cause case insensitivity issues on Windows. + + return [func for func in available_functions if fnmatch.fnmatchcase(func, object_path)] # Setup wsgi application wrapper defined in configuration file. From 6093c27175cb637f9378ab5129cf746943c99a86 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Thu, 31 Jul 2025 10:39:37 -0700 Subject: [PATCH 6/7] Format --- newrelic/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/newrelic/config.py b/newrelic/config.py index c8352c7999..76b3e4dd2c 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -1197,9 +1197,10 @@ def _module_function_glob(module, object_path): # Globbing must be done using fnmatch.fnmatchcase as # fnmatch.filter and fnmatch.fnmatch use os.path.normcase # which cause case insensitivity issues on Windows. - + return [func for func in available_functions if fnmatch.fnmatchcase(func, object_path)] + # Setup wsgi application wrapper defined in configuration file. From 0baa6f1bf58f292989584f63e7c92d052d071d3d Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Mon, 4 Aug 2025 13:59:05 -0700 Subject: [PATCH 7/7] Fix Python 3.7 compat --- tests/testing_support/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/testing_support/util.py b/tests/testing_support/util.py index e3438294cf..6d31338ee4 100644 --- a/tests/testing_support/util.py +++ b/tests/testing_support/util.py @@ -104,7 +104,8 @@ def NamedTemporaryFile(*args, **kwargs): def remove_on_exit(*args, **kwargs): original_exit(*args, **kwargs) # Clean up the file manually - temp_file.path.unlink(missing_ok=True) + if temp_file.path.exists(): + temp_file.path.unlink() temp_file.__exit__ = remove_on_exit