From dd2839a3d04423ccc23ba60e523b264b97c8a4e7 Mon Sep 17 00:00:00 2001 From: Oren Jacobian Pana Date: Sun, 18 Oct 2026 22:28:09 +0300 Subject: [PATCH 1/6] test: cover an empty proxy against env and system proxy settings The existing empty-proxy tests set only the `http_proxy`/`https_proxy` environment variables, which the WinHTTP transport and the Windows and macOS crashpad handlers never read, so they pass whether or not an empty proxy turns proxy discovery off. Add a `system_proxy` helper that points the current user's system proxy at the test proxy (WinINet on Windows, `networksetup` on macOS) and restores it afterwards. The tests run each backend once with the system proxy and once with the system proxy plus an empty `proxy` option: the first checks that the system proxy is in use, the second that the empty option bypasses it. The native backend also gets the environment-variable test that the other backends have. --- tests/proxy.py | 159 +++++++++++++++++++++++++++++ tests/test_integration_crashpad.py | 33 ++++++ tests/test_integration_native.py | 64 ++++++++++++ tests/test_integration_proxy.py | 30 ++++++ 4 files changed, 286 insertions(+) diff --git a/tests/proxy.py b/tests/proxy.py index ac215e7191..155e527759 100644 --- a/tests/proxy.py +++ b/tests/proxy.py @@ -31,6 +31,165 @@ def cleanup_proxy_env_vars(): os.environ.pop("https_proxy", None) +@contextlib.contextmanager +def system_proxy(port): + """Set the current user's system HTTP proxy to the test proxy and restore + the previous settings afterwards. WinHTTP proxy discovery and NSURLSession + read these settings, unlike the environment variables.""" + if sys.platform == "win32": + with _wininet_proxy(f"127.0.0.1:{port}"): + yield + elif sys.platform == "darwin": + with _networksetup_proxy(port): + yield + else: + pytest.skip("no system proxy settings on this platform") + + +@contextlib.contextmanager +def _wininet_proxy(server): + import ctypes + from ctypes import wintypes + + INTERNET_OPTION_REFRESH = 37 + INTERNET_OPTION_SETTINGS_CHANGED = 39 + INTERNET_OPTION_PER_CONNECTION_OPTION = 75 + INTERNET_PER_CONN_FLAGS = 1 + INTERNET_PER_CONN_PROXY_SERVER = 2 + PROXY_TYPE_DIRECT = 0x1 + PROXY_TYPE_PROXY = 0x2 + + class Value(ctypes.Union): + _fields_ = [ + ("dwValue", wintypes.DWORD), + ("pszValue", wintypes.LPWSTR), + ("pValue", ctypes.c_void_p), + ("ftValue", wintypes.FILETIME), + ] + + class Option(ctypes.Structure): + _fields_ = [("dwOption", wintypes.DWORD), ("Value", Value)] + + class OptionList(ctypes.Structure): + _fields_ = [ + ("dwSize", wintypes.DWORD), + ("pszConnection", wintypes.LPWSTR), + ("dwOptionCount", wintypes.DWORD), + ("dwOptionError", wintypes.DWORD), + ("pOptions", ctypes.POINTER(Option)), + ] + + wininet = ctypes.WinDLL("wininet", use_last_error=True) + wininet.InternetQueryOptionW.argtypes = [ + ctypes.c_void_p, + wintypes.DWORD, + ctypes.c_void_p, + ctypes.POINTER(wintypes.DWORD), + ] + wininet.InternetSetOptionW.argtypes = [ + ctypes.c_void_p, + wintypes.DWORD, + ctypes.c_void_p, + wintypes.DWORD, + ] + kernel32 = ctypes.WinDLL("kernel32") + kernel32.GlobalFree.argtypes = [ctypes.c_void_p] + + def make_list(flags=None, proxy_server=None): + options = (Option * 2)() + options[0].dwOption = INTERNET_PER_CONN_FLAGS + options[0].Value.dwValue = flags or 0 + options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER + options[1].Value.pszValue = proxy_server + option_list = OptionList(ctypes.sizeof(OptionList), None, 2, 0, options) + return option_list, options + + def set_options(flags, proxy_server): + option_list, _ = make_list(flags, proxy_server) + if not wininet.InternetSetOptionW( + None, + INTERNET_OPTION_PER_CONNECTION_OPTION, + ctypes.byref(option_list), + ctypes.sizeof(option_list), + ): + raise ctypes.WinError(ctypes.get_last_error()) + wininet.InternetSetOptionW(None, INTERNET_OPTION_SETTINGS_CHANGED, None, 0) + wininet.InternetSetOptionW(None, INTERNET_OPTION_REFRESH, None, 0) + + option_list, options = make_list() + size = wintypes.DWORD(ctypes.sizeof(option_list)) + if not wininet.InternetQueryOptionW( + None, + INTERNET_OPTION_PER_CONNECTION_OPTION, + ctypes.byref(option_list), + ctypes.byref(size), + ): + raise ctypes.WinError(ctypes.get_last_error()) + previous_flags = options[0].Value.dwValue + previous_server = options[1].Value.pszValue + if options[1].Value.pValue: + kernel32.GlobalFree(options[1].Value.pValue) + + set_options(PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY, server) + try: + yield + finally: + set_options(previous_flags, previous_server) + + +@contextlib.contextmanager +def _networksetup_proxy(port): + if subprocess.run(["sudo", "-n", "true"]).returncode != 0: + pytest.skip("setting the system proxy needs sudo") + + services = [ + service + for service in subprocess.check_output( + ["networksetup", "-listallnetworkservices"], text=True + ).splitlines()[1:] + if not service.startswith("*") + ] + previous = {} + for service in services: + output = subprocess.check_output( + ["networksetup", "-getwebproxy", service], text=True + ) + previous[service] = dict( + line.split(": ", 1) for line in output.splitlines() if ": " in line + ) + + try: + for service in services: + subprocess.check_call( + [ + "sudo", + "networksetup", + "-setwebproxy", + service, + "127.0.0.1", + str(port), + ] + ) + yield + finally: + for service, settings in previous.items(): + if settings.get("Server"): + subprocess.check_call( + [ + "sudo", + "networksetup", + "-setwebproxy", + service, + settings["Server"], + settings["Port"], + ] + ) + if settings.get("Enabled") != "Yes": + subprocess.check_call( + ["sudo", "networksetup", "-setwebproxystate", service, "off"] + ) + + def _wait_for_output_file(process, output_file, timeout=10): deadline = time.monotonic() + timeout while time.monotonic() < deadline: diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index e3b488606b..3e4976acdd 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -27,6 +27,7 @@ cleanup_proxy_env_vars, start_proxy, proxy_test_finally, + system_proxy, ) from .assertions import ( assert_breadcrumb, @@ -305,6 +306,38 @@ def test_crashpad_proxy_set_empty(cmake, httpserver): proxy_test_finally(1, httpserver, proxy_process, expected_proxy_logsize=0) +@pytest.mark.skipif( + sys.platform not in ["win32", "darwin"], + reason="only the Windows and macOS handlers read the system proxy", +) +@pytest.mark.parametrize("proxy_empty", [False, True]) +def test_crashpad_proxy_system(cmake, httpserver, proxy_empty): + proxy_process = None # store the proxy process to terminate it later + try: + env, proxy_process, tmp_path, port = _setup_crashpad_proxy_test( + cmake, httpserver, "http-proxy" + ) + + with system_proxy(port): + with httpserver.wait(timeout=10) as waiting: + run( + tmp_path, + "sentry_test_integration", + ["log", "crash"] + (["proxy-empty"] if proxy_empty else []), + expect_failure=True, + env=env, + ) + assert waiting.result + + finally: + proxy_test_finally( + 1, + httpserver, + proxy_process, + expected_proxy_logsize=0 if proxy_empty else None, + ) + + def test_crashpad_proxy_https_not_http(cmake, httpserver): proxy_process = None # store the proxy process to terminate it later # we start the proxy but expect it to remain unused (dsn is http, so shouldn't use https proxy) diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index b97d1c44e8..daf49e0aa6 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -46,6 +46,7 @@ ) from .conditions import has_files, is_kcov from .conditions import has_native, has_oom, is_asan, is_tsan, is_qemu, is_wine +from .proxy import proxy_test_finally, start_proxy, system_proxy pytestmark = pytest.mark.skipif( not has_native or is_qemu, @@ -1846,3 +1847,66 @@ def test_native_symbolication(cmake, httpserver, crash_mode, symbolize): assert any( "function" in frame for frame in exc["stacktrace"]["frames"] ), "missing symbolicated function names" + + +def test_native_proxy_set_empty(cmake, httpserver): + proxy_process = None # store the proxy process to terminate it later + try: + proxy_process, port = start_proxy("http-proxy") + tmp_path = cmake(["sentry_test_integration"], {"SENTRY_BACKEND": "native"}) + httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data( + "OK" + ) + # we start the proxy but expect it to remain unused + env = dict( + os.environ, + SENTRY_DSN=make_dsn(httpserver, proxy_host=True), + http_proxy=f"http://127.0.0.1:{port}", + https_proxy=f"http://127.0.0.1:{port}", + ) + + with httpserver.wait(timeout=10) as waiting: + run_crash( + tmp_path, + "sentry_test_integration", + ["log", "crash", "proxy-empty"], + env=env, + ) + assert waiting.result + + finally: + proxy_test_finally(1, httpserver, proxy_process, expected_proxy_logsize=0) + + +@pytest.mark.skipif( + sys.platform != "win32" or is_wine, + reason="only the WinHTTP transport reads the system proxy", +) +@pytest.mark.parametrize("proxy_empty", [False, True]) +def test_native_proxy_system(cmake, httpserver, proxy_empty): + proxy_process = None # store the proxy process to terminate it later + try: + proxy_process, port = start_proxy("http-proxy") + tmp_path = cmake(["sentry_test_integration"], {"SENTRY_BACKEND": "native"}) + httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data( + "OK" + ) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver, proxy_host=True)) + + with system_proxy(port): + with httpserver.wait(timeout=10) as waiting: + run_crash( + tmp_path, + "sentry_test_integration", + ["log", "crash"] + (["proxy-empty"] if proxy_empty else []), + env=env, + ) + assert waiting.result + + finally: + proxy_test_finally( + 1, + httpserver, + proxy_process, + expected_proxy_logsize=0 if proxy_empty else None, + ) diff --git a/tests/test_integration_proxy.py b/tests/test_integration_proxy.py index 72f536e881..4158d1fba7 100644 --- a/tests/test_integration_proxy.py +++ b/tests/test_integration_proxy.py @@ -15,6 +15,7 @@ closed_port, start_proxy, proxy_test_finally, + system_proxy, ) pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") @@ -171,6 +172,35 @@ def test_proxy_set_empty(cmake, httpserver): proxy_test_finally(1, httpserver, proxy_process, expected_proxy_logsize=0) +@pytest.mark.skipif( + sys.platform != "win32" or is_wine, + reason="only the WinHTTP transport reads the system proxy", +) +@pytest.mark.parametrize("proxy_empty", [False, True]) +def test_proxy_system(cmake, httpserver, proxy_empty): + proxy_process = None # store the proxy process to terminate it later + try: + env, proxy_process, tmp_path, port = _setup_http_proxy_test( + cmake, httpserver, "http-proxy" + ) + + with system_proxy(port): + run( + tmp_path, + "sentry_test_integration", + ["log", "capture-event"] + (["proxy-empty"] if proxy_empty else []), + env=env, + ) + + finally: + proxy_test_finally( + 1, + httpserver, + proxy_process, + expected_proxy_logsize=0 if proxy_empty else None, + ) + + def test_proxy_https_not_http(cmake, httpserver): proxy_process = None # store the proxy process to terminate it later try: From fd9828582e42af4442ff579688101de55e317edb Mon Sep 17 00:00:00 2001 From: Oren Jacobian Pana Date: Sun, 18 Oct 2026 22:28:09 +0300 Subject: [PATCH 2/6] fix: an empty proxy means a direct connection on all platforms `sentry_options_set_proxy(options, "")` turned off the proxy only where libcurl is used. Elsewhere it fell back to proxy discovery: - WinHTTP transport: opened the session with WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, which reads the system settings. - crashpad backend: passed `` to the handler only on Linux, so the Windows and macOS handlers used the system settings. - native backend: the daemon applied the proxy only when it was not empty, so it fell back to the environment variables and system settings. Open the WinHTTP session with WINHTTP_ACCESS_TYPE_NO_PROXY for an empty proxy, pass `` to the crashpad handler on every platform, and tell the native daemon whether a proxy was set. The crashpad submodule update makes the Windows and macOS handlers treat `` as a direct connection. --- external/crashpad | 2 +- include/sentry.h | 4 ++++ src/backends/native/sentry_crash_context.h | 1 + src/backends/native/sentry_crash_daemon.c | 2 +- src/backends/sentry_backend_crashpad.cpp | 5 ++--- src/backends/sentry_backend_native.c | 1 + src/transports/sentry_http_transport_winhttp.c | 4 ++++ 7 files changed, 14 insertions(+), 5 deletions(-) diff --git a/external/crashpad b/external/crashpad index 493151b224..78d52fb055 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 493151b224a6536bcedc3467d042863a063addc4 +Subproject commit 78d52fb055d2749558df9551d3bf56e89e865091 diff --git a/include/sentry.h b/include/sentry.h index 6205af91e0..29ae701fd9 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1777,6 +1777,10 @@ SENTRY_API const char *sentry_options_get_dist(const sentry_options_t *opts); * transport on macOS for a SOCKS proxy, but not for an HTTP proxy. * All transports that use libcurl (Linux and the Native SDK transport on macOS) * will honor the proxy settings and not fall back. + * + * An empty string disables the proxy: no transport then reads the + * `http_proxy` and `https_proxy` environment variables or the system proxy + * settings. */ SENTRY_API void sentry_options_set_proxy( sentry_options_t *opts, const char *proxy); diff --git a/src/backends/native/sentry_crash_context.h b/src/backends/native/sentry_crash_context.h index 1f840aa1d6..b0d5543ef2 100644 --- a/src/backends/native/sentry_crash_context.h +++ b/src/backends/native/sentry_crash_context.h @@ -337,6 +337,7 @@ typedef struct { // Transport configuration (passed from parent options to daemon) char ca_certs[SENTRY_CRASH_MAX_PATH]; // CA certificates file path for SSL char proxy[SENTRY_CRASH_MAX_PATH]; // HTTP proxy URL + bool has_proxy; // Proxy was set, possibly to "" to disable it char user_agent[256]; // User-Agent header for HTTP requests // Minidump output path (filled by daemon) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index f960855297..96f6a4f053 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -4611,7 +4611,7 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, sentry_options_set_ca_certs(options, ipc->shmem->ca_certs); } - if (ipc->shmem->proxy[0] != '\0') { + if (ipc->shmem->has_proxy) { SENTRY_DEBUGF("Setting proxy: %s", ipc->shmem->proxy); sentry_options_set_proxy(options, ipc->shmem->proxy); } diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 1f7967b0a3..08c9a3a3c5 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -1132,12 +1132,11 @@ crashpad_backend_startup( const char *proxy_url = options->proxy ? options->proxy : env_proxy ? env_proxy : ""; -#ifdef SENTRY_PLATFORM_LINUX - // explicitly set an empty proxy to avoid reading from env. vars. on Linux + // explicitly set an empty proxy to avoid reading from env. vars. or the + // system proxy settings if (options->proxy && strcmp(options->proxy, "") == 0) { proxy_url = ""; } -#endif bool success = data->client->StartHandler(handler, database, database, minidump_url ? minidump_url : "", proxy_url, annotations, arguments, /* restartable */ true, diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index ff8f97e3d7..2cf53feaa2 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -826,6 +826,7 @@ native_backend_startup( } if (options->proxy) { + ctx->has_proxy = true; #ifdef _WIN32 strncpy_s(ctx->proxy, sizeof(ctx->proxy), options->proxy, _TRUNCATE); #else diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index cc9db472d2..7562585f18 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -122,6 +122,10 @@ winhttp_client_start(void *_client, const sentry_options_t *opts) client->session = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_NAMED_PROXY, client->proxy, WINHTTP_NO_PROXY_BYPASS, 0); + } else if (opts->proxy && opts->proxy[0] == '\0') { + // an explicitly empty proxy disables proxy discovery + client->session = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); } else { #if _WIN32_WINNT >= 0x0603 client->session From 79a2a948612a4540db2e317486a903b9fb8403e0 Mon Sep 17 00:00:00 2001 From: Oren Jacobian Pana Date: Sun, 18 Oct 2026 22:36:18 +0300 Subject: [PATCH 3/6] docs: changelog entry for an empty proxy --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c9349c0f3..4fa27e4564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Fixes**: + +- An empty proxy (`sentry_options_set_proxy(options, "")`) now means a direct connection with every transport and backend. Before, the WinHTTP transport, the Windows and macOS crashpad handlers and the native backend's daemon fell back to the proxy environment variables or the system proxy settings. ([#2218](https://github.com/getsentry/sentry-native/pull/2218), [crashpad#175](https://github.com/getsentry/crashpad/pull/175)) + ## 0.17.4 **Features**: From 8ad7bc92c05d66c55dc3dcad9589617e4686c0a8 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 19 Oct 2026 13:59:40 +0200 Subject: [PATCH 4/6] Update tests/test_integration_crashpad.py --- tests/test_integration_crashpad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 3e4976acdd..6a49508eb4 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -307,8 +307,8 @@ def test_crashpad_proxy_set_empty(cmake, httpserver): @pytest.mark.skipif( - sys.platform not in ["win32", "darwin"], - reason="only the Windows and macOS handlers read the system proxy", + sys.platform not in ["win32", "darwin"] or bool(os.environ.get("TEST_MINGW")), + reason="system proxy discovery requires macOS or non-MinGW Windows builds", ) @pytest.mark.parametrize("proxy_empty", [False, True]) def test_crashpad_proxy_system(cmake, httpserver, proxy_empty): From 0ba2f8f754458c4d73adace1a5ded9a3ceb261ac Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 19 Oct 2026 15:35:48 +0200 Subject: [PATCH 5/6] Bump crashpad --- external/crashpad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/crashpad b/external/crashpad index 78d52fb055..0bd6ca43a0 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 78d52fb055d2749558df9551d3bf56e89e865091 +Subproject commit 0bd6ca43a0958a86939a37569b61162f92f9a022 From 6fc6f24e2025e4f57843e3504c85e45d7e8ab155 Mon Sep 17 00:00:00 2001 From: Oren Jacobian Pana Date: Mon, 19 Oct 2026 23:32:30 +0300 Subject: [PATCH 6/6] test: run the system proxy tests only with TEST_SYSTEM_PROXY The tests change the user's system proxy settings, and on macOS they need sudo. Only the Windows (latest) and macOS 15 (xcode llvm) CI jobs opt in. --- .github/workflows/ci.yml | 3 +++ CONTRIBUTING.md | 3 +++ tests/conditions.py | 2 ++ tests/test_integration_crashpad.py | 3 ++- tests/test_integration_native.py | 3 ++- tests/test_integration_proxy.py | 3 ++- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e2762d01a..879c6e37fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,6 +168,7 @@ jobs: os: macos-15 ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 + TEST_SYSTEM_PROXY: 1 - name: macOS 26 (xcode llvm) os: macos-26 ERROR_ON_WARNINGS: 1 @@ -194,6 +195,7 @@ jobs: TEST_X86: 1 - name: Windows (latest) os: windows-latest + TEST_SYSTEM_PROXY: 1 - name: Windows (MSVC analyzer) os: windows-2022 RUN_ANALYZER: msvc @@ -259,6 +261,7 @@ jobs: TEST_WINE: ${{ matrix.TEST_WINE }} TEST_ARM32: ${{ matrix.TEST_ARM32 }} TEST_QEMU: ${{ matrix.TEST_QEMU }} + TEST_SYSTEM_PROXY: ${{ matrix.TEST_SYSTEM_PROXY }} USE_SCCACHE: ${{ matrix.USE_SCCACHE }} SCCACHE_GHA_ENABLED: ${{ matrix.USE_SCCACHE && 'true' || '' }} ERROR_ON_WARNINGS: ${{ matrix.ERROR_ON_WARNINGS }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b55a68afa..37639d00da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,9 @@ Some of its behavior is controlled by env-variables: switches to the Ninja generator (`choco install ninja`) and uses `/Z7` debug info format for compatibility. - `TEST_X86`: Passes flags to CMake to enable a 32-bit (cross-)compile. +- `TEST_SYSTEM_PROXY`: Runs the tests that point the user's system proxy + settings at a local test proxy, and restores them afterwards. On macOS this + needs passwordless `sudo`. - `ANDROID_API` / `ANDROID_NDK` / `ANDROID_ARCH`: Instructs the test runner to build using the given Android `NDK` version, targeting the given `API` and `ARCH`. The test runner assumes an already running simulator matching the diff --git a/tests/conditions.py b/tests/conditions.py index 12dd22f66e..04e21514fc 100644 --- a/tests/conditions.py +++ b/tests/conditions.py @@ -8,6 +8,8 @@ is_x86 = os.environ.get("TEST_X86") is_arm32 = bool(os.environ.get("TEST_ARM32")) is_qemu = bool(os.environ.get("TEST_QEMU")) +# opt-in because the tests change the user's system proxy settings (with sudo on macOS) +has_system_proxy = bool(os.environ.get("TEST_SYSTEM_PROXY")) is_asan = "asan" in os.environ.get("RUN_ANALYZER", "") is_tsan = "tsan" in os.environ.get("RUN_ANALYZER", "") is_kcov = "kcov" in os.environ.get("RUN_ANALYZER", "") diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 6a49508eb4..c7e3620f63 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -21,7 +21,7 @@ lib_name, REPLAY_ID, ) -from .conditions import has_crashpad, has_files, has_oom +from .conditions import has_crashpad, has_files, has_oom, has_system_proxy from .proxy import ( setup_proxy_env_vars, cleanup_proxy_env_vars, @@ -306,6 +306,7 @@ def test_crashpad_proxy_set_empty(cmake, httpserver): proxy_test_finally(1, httpserver, proxy_process, expected_proxy_logsize=0) +@pytest.mark.skipif(not has_system_proxy, reason="needs TEST_SYSTEM_PROXY") @pytest.mark.skipif( sys.platform not in ["win32", "darwin"] or bool(os.environ.get("TEST_MINGW")), reason="system proxy discovery requires macOS or non-MinGW Windows builds", diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index daf49e0aa6..70257cc039 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -44,7 +44,7 @@ wait_for_file, assert_user_feedback, ) -from .conditions import has_files, is_kcov +from .conditions import has_files, has_system_proxy, is_kcov from .conditions import has_native, has_oom, is_asan, is_tsan, is_qemu, is_wine from .proxy import proxy_test_finally, start_proxy, system_proxy @@ -1878,6 +1878,7 @@ def test_native_proxy_set_empty(cmake, httpserver): proxy_test_finally(1, httpserver, proxy_process, expected_proxy_logsize=0) +@pytest.mark.skipif(not has_system_proxy, reason="needs TEST_SYSTEM_PROXY") @pytest.mark.skipif( sys.platform != "win32" or is_wine, reason="only the WinHTTP transport reads the system proxy", diff --git a/tests/test_integration_proxy.py b/tests/test_integration_proxy.py index 4158d1fba7..c846ee0a31 100644 --- a/tests/test_integration_proxy.py +++ b/tests/test_integration_proxy.py @@ -10,7 +10,7 @@ from .assertions import ( assert_failed_proxy_auth_request, ) -from .conditions import has_http, is_wine +from .conditions import has_http, has_system_proxy, is_wine from .proxy import ( closed_port, start_proxy, @@ -172,6 +172,7 @@ def test_proxy_set_empty(cmake, httpserver): proxy_test_finally(1, httpserver, proxy_process, expected_proxy_logsize=0) +@pytest.mark.skipif(not has_system_proxy, reason="needs TEST_SYSTEM_PROXY") @pytest.mark.skipif( sys.platform != "win32" or is_wine, reason="only the WinHTTP transport reads the system proxy",