330 lines
11 KiB
Python
330 lines
11 KiB
Python
import os
|
|
import shutil
|
|
import sys
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from . import make_dsn, run, Envelope
|
|
from .assertions import (
|
|
assert_crashpad_upload,
|
|
assert_session,
|
|
assert_gzip_file_header,
|
|
)
|
|
from .conditions import has_crashpad
|
|
|
|
pytestmark = pytest.mark.skipif(not has_crashpad, reason="tests need crashpad backend")
|
|
|
|
# Windows and Linux are currently able to flush all the state on crash
|
|
flushes_state = sys.platform != "darwin"
|
|
|
|
|
|
def test_crashpad_capture(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "capture-event"],
|
|
check=True,
|
|
env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)),
|
|
)
|
|
|
|
assert len(httpserver.log) == 2
|
|
|
|
|
|
def test_crashpad_reinstall(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")
|
|
|
|
with httpserver.wait(timeout=10) as waiting:
|
|
child = run(tmp_path, "sentry_example", ["log", "reinstall", "crash"], env=env)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
assert waiting.result
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
assert len(httpserver.log) == 1
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform != "win32",
|
|
reason="Test covers Windows-specific crashes which can only be covered via the Crashpad WER module",
|
|
)
|
|
# this test currently can't run on CI because the Windows-image doesn't properly support WER, if you want to run the
|
|
# test locally, invoke pytest with the --with_crashpad_wer option which is matched with this marker in the runtest setup
|
|
@pytest.mark.with_crashpad_wer
|
|
@pytest.mark.parametrize(
|
|
"run_args",
|
|
[
|
|
# discarding via before-send or on-crash has no consequence for fast-fail crashes because they by-pass SEH and
|
|
# thus the crash-handler gets no chance to invoke the FirstChanceHandler which in turn would trigger our hooks.
|
|
(["stack-buffer-overrun"]),
|
|
(["stack-buffer-overrun", "discarding-before-send"]),
|
|
(["stack-buffer-overrun", "discarding-on-crash"]),
|
|
(["fastfail"]),
|
|
(["fastfail", "discarding-before-send"]),
|
|
(["fastfail", "discarding-on-crash"]),
|
|
],
|
|
)
|
|
def test_crashpad_wer_crash(cmake, httpserver, run_args):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# If we are building on a Windows without WER enabled this test doesn't make sense
|
|
if not os.path.exists(tmp_path / "crashpad_wer.dll"):
|
|
return
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")
|
|
httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
|
|
|
|
with httpserver.wait(timeout=10) as waiting:
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "attachment", "overflow-breadcrumbs"] + run_args,
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
assert waiting.result
|
|
|
|
# the session crash heuristic on Mac uses timestamps, so make sure we have
|
|
# a small delay here
|
|
time.sleep(1)
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
assert len(httpserver.log) == 2
|
|
outputs = (httpserver.log[0][0], httpserver.log[1][0])
|
|
session, multipart = (
|
|
(outputs[0].get_data(), outputs[1])
|
|
if b'"type":"session"' in outputs[0].get_data()
|
|
else (outputs[1].get_data(), outputs[0])
|
|
)
|
|
|
|
envelope = Envelope.deserialize(session)
|
|
|
|
assert_session(envelope, {"status": "crashed", "errors": 1})
|
|
assert_crashpad_upload(multipart)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"run_args,build_args",
|
|
[
|
|
# if we crash, we want a dump
|
|
([], {"SENTRY_TRANSPORT_COMPRESSION": "Off"}),
|
|
([], {"SENTRY_TRANSPORT_COMPRESSION": "On"}),
|
|
# if we crash and before-send doesn't discard, we want a dump
|
|
pytest.param(
|
|
["before-send"],
|
|
{},
|
|
marks=pytest.mark.skipif(
|
|
sys.platform == "darwin",
|
|
reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS",
|
|
),
|
|
),
|
|
# if on_crash() is non-discarding, a discarding before_send() is overruled, so we get a dump
|
|
pytest.param(
|
|
["discarding-before-send", "on-crash"],
|
|
{},
|
|
marks=pytest.mark.skipif(
|
|
sys.platform == "darwin",
|
|
reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS",
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args):
|
|
build_args.update({"SENTRY_BACKEND": "crashpad"})
|
|
tmp_path = cmake(["sentry_example"], build_args)
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")
|
|
httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
|
|
|
|
with httpserver.wait(timeout=10) as waiting:
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "attachment", "overflow-breadcrumbs", "crash"]
|
|
+ run_args,
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
assert waiting.result
|
|
|
|
# the session crash heuristic on Mac uses timestamps, so make sure we have
|
|
# a small delay here
|
|
time.sleep(1)
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
assert len(httpserver.log) == 2
|
|
session, multipart = (
|
|
(httpserver.log[0][0], httpserver.log[1][0])
|
|
if is_session_envelope(httpserver.log[0][0].get_data())
|
|
else (httpserver.log[1][0], httpserver.log[0][0])
|
|
)
|
|
|
|
if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On":
|
|
assert_gzip_file_header(session.get_data())
|
|
|
|
envelope = Envelope.deserialize(session.get_data())
|
|
assert_session(envelope, {"status": "crashed", "errors": 1})
|
|
assert_crashpad_upload(multipart)
|
|
|
|
|
|
def is_session_envelope(data):
|
|
return b'"type":"session"' in data
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "darwin",
|
|
reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS",
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"run_args",
|
|
[(["discarding-before-send"]), (["discarding-on-crash"])],
|
|
)
|
|
def test_crashpad_non_dumping_crash(cmake, httpserver, run_args):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
|
|
|
|
with httpserver.wait(timeout=5, raise_assertions=False) as waiting:
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
[
|
|
"log",
|
|
"start-session",
|
|
"attachment",
|
|
"overflow-breadcrumbs",
|
|
"crash",
|
|
]
|
|
+ run_args,
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
assert waiting.result is False
|
|
|
|
# the session crash heuristic on Mac uses timestamps, so make sure we have
|
|
# a small delay here
|
|
time.sleep(1)
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
assert len(httpserver.log) == 1
|
|
output = httpserver.log[0][0]
|
|
session = output.get_data()
|
|
envelope = Envelope.deserialize(session)
|
|
|
|
assert_session(envelope, {"status": "abnormal", "errors": 0})
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "linux", reason="linux clears the signal handlers on shutdown"
|
|
)
|
|
def test_crashpad_crash_after_shutdown(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")
|
|
|
|
with httpserver.wait(timeout=10) as waiting:
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "crash-after-shutdown"],
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
assert waiting.result
|
|
|
|
# the session crash heuristic on Mac uses timestamps, so make sure we have
|
|
# a small delay here
|
|
time.sleep(1)
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
assert len(httpserver.log) == 1
|
|
|
|
assert_crashpad_upload(httpserver.log[0][0])
|
|
|
|
|
|
@pytest.mark.skipif(not flushes_state, reason="test needs state flushing")
|
|
def test_crashpad_dump_inflight(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")
|
|
httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
|
|
|
|
with httpserver.wait(timeout=10) as waiting:
|
|
child = run(
|
|
tmp_path, "sentry_example", ["log", "capture-multiple", "crash"], env=env
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
assert waiting.result
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
# we trigger 10 normal events, and 1 crash
|
|
assert len(httpserver.log) >= 11
|
|
|
|
|
|
def test_disable_backend(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
|
|
|
|
# make sure we are isolated from previous runs
|
|
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
with httpserver.wait(timeout=5, raise_assertions=False) as waiting:
|
|
child = run(
|
|
tmp_path, "sentry_example", ["disable-backend", "log", "crash"], env=env
|
|
)
|
|
# we crash so process should return non-zero
|
|
assert child.returncode
|
|
|
|
# crashpad is disabled, and we are only crashing, so we expect the wait to timeout
|
|
assert waiting.result is False
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
# crashpad is disabled, and we are only crashing, so we expect no requests
|
|
assert len(httpserver.log) == 0
|