570 lines
16 KiB
Python
570 lines
16 KiB
Python
import itertools
|
|
import json
|
|
import os
|
|
import time
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from . import make_dsn, run, Envelope
|
|
from .assertions import (
|
|
assert_attachment,
|
|
assert_meta,
|
|
assert_breadcrumb,
|
|
assert_stacktrace,
|
|
assert_event,
|
|
assert_exception,
|
|
assert_inproc_crash,
|
|
assert_session,
|
|
assert_user_feedback,
|
|
assert_minidump,
|
|
assert_breakpad_crash,
|
|
assert_gzip_content_encoding,
|
|
assert_gzip_file_header,
|
|
)
|
|
from .conditions import has_http, has_breakpad, has_files
|
|
|
|
pytestmark = pytest.mark.skipif(not has_http, reason="tests need http")
|
|
|
|
auth_header = (
|
|
"Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.2"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"build_args",
|
|
[
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "Off"}),
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "On"}),
|
|
],
|
|
)
|
|
def test_capture_http(cmake, httpserver, build_args):
|
|
build_args.update({"SENTRY_BACKEND": "none"})
|
|
tmp_path = cmake(["sentry_example"], build_args)
|
|
|
|
httpserver.expect_oneshot_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver), SENTRY_RELEASE="🤮🚀")
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "release-env", "capture-event", "add-stacktrace"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
req = httpserver.log[0][0]
|
|
body = req.get_data()
|
|
|
|
if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On":
|
|
assert_gzip_content_encoding(req)
|
|
assert_gzip_file_header(body)
|
|
|
|
envelope = Envelope.deserialize(body)
|
|
|
|
assert_meta(envelope, "🤮🚀")
|
|
assert_breadcrumb(envelope)
|
|
assert_stacktrace(envelope)
|
|
|
|
assert_event(envelope)
|
|
|
|
|
|
def test_session_http(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
# start once without a release, but with a session
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "release-env", "start-session"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
output = httpserver.log[0][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
|
|
assert_session(envelope, {"init": True, "status": "exited", "errors": 0})
|
|
|
|
|
|
def test_capture_and_session_http(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "capture-event"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 2
|
|
output = httpserver.log[0][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
|
|
assert_event(envelope)
|
|
assert_session(envelope, {"init": True, "status": "ok", "errors": 0})
|
|
|
|
output = httpserver.log[1][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
assert_session(envelope, {"status": "exited", "errors": 0})
|
|
|
|
|
|
def test_user_feedback_http(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "capture-user-feedback"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 2
|
|
output = httpserver.log[0][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
|
|
assert_event(envelope, "Hello user feedback!")
|
|
|
|
output = httpserver.log[1][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
|
|
assert_user_feedback(envelope)
|
|
|
|
|
|
def test_exception_and_session_http(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "capture-exception", "add-stacktrace"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 2
|
|
output = httpserver.log[0][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
|
|
assert_exception(envelope)
|
|
assert_stacktrace(envelope, inside_exception=True)
|
|
assert_session(envelope, {"init": True, "status": "ok", "errors": 1})
|
|
|
|
output = httpserver.log[1][0].get_data()
|
|
envelope = Envelope.deserialize(output)
|
|
assert_session(envelope, {"status": "exited", "errors": 1})
|
|
|
|
|
|
@pytest.mark.skipif(not has_files, reason="test needs a local filesystem")
|
|
def test_abnormal_session(cmake, httpserver):
|
|
tmp_path = cmake(
|
|
["sentry_example"],
|
|
{"SENTRY_BACKEND": "none"},
|
|
)
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
# create a bogus session file
|
|
session = json.dumps(
|
|
{
|
|
"sid": "00000000-0000-0000-0000-000000000000",
|
|
"did": "42",
|
|
"status": "started",
|
|
"errors": 0,
|
|
"started": "2020-06-02T10:04:53.680Z",
|
|
"duration": 10,
|
|
"attrs": {"release": "test-example-release", "environment": "development"},
|
|
}
|
|
)
|
|
db_dir = tmp_path.joinpath(".sentry-native")
|
|
db_dir.mkdir(exist_ok=True)
|
|
# 15 exceeds the max envelope items
|
|
for i in range(15):
|
|
run_dir = db_dir.joinpath(f"foo-{i}.run")
|
|
run_dir.mkdir()
|
|
with open(run_dir.joinpath("session.json"), "w") as session_file:
|
|
session_file.write(session)
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "no-setup"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 2
|
|
envelope1 = Envelope.deserialize(httpserver.log[0][0].get_data())
|
|
envelope2 = Envelope.deserialize(httpserver.log[1][0].get_data())
|
|
|
|
session_count = 0
|
|
for item in itertools.chain(envelope1, envelope2):
|
|
if item.headers.get("type") == "session":
|
|
session_count += 1
|
|
assert session_count == 15
|
|
|
|
assert_session(envelope1, {"status": "abnormal", "errors": 0, "duration": 10})
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"build_args",
|
|
[
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "Off"}),
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "On"}),
|
|
],
|
|
)
|
|
def test_inproc_crash_http(cmake, httpserver, build_args):
|
|
build_args.update({"SENTRY_BACKEND": "inproc"})
|
|
tmp_path = cmake(["sentry_example"], build_args)
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "attachment", "crash"],
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "no-setup"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
req = httpserver.log[0][0]
|
|
body = req.get_data()
|
|
|
|
if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On":
|
|
assert_gzip_content_encoding(req)
|
|
assert_gzip_file_header(body)
|
|
|
|
envelope = Envelope.deserialize(body)
|
|
|
|
assert_session(envelope, {"init": True, "status": "crashed", "errors": 1})
|
|
|
|
assert_meta(envelope, integration="inproc")
|
|
assert_breadcrumb(envelope)
|
|
assert_attachment(envelope)
|
|
|
|
assert_inproc_crash(envelope)
|
|
|
|
|
|
def test_inproc_reinstall(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"})
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "reinstall", "crash"],
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "no-setup"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
|
|
|
|
def test_inproc_dump_inflight(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"})
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
child = run(
|
|
tmp_path, "sentry_example", ["log", "capture-multiple", "crash"], env=env
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
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
|
|
|
|
|
|
@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend")
|
|
@pytest.mark.parametrize(
|
|
"build_args",
|
|
[
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "Off"}),
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "On"}),
|
|
],
|
|
)
|
|
def test_breakpad_crash_http(cmake, httpserver, build_args):
|
|
build_args.update({"SENTRY_BACKEND": "breakpad"})
|
|
tmp_path = cmake(["sentry_example"], build_args)
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "start-session", "attachment", "crash"],
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "no-setup"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
req = httpserver.log[0][0]
|
|
body = req.get_data()
|
|
|
|
if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On":
|
|
assert_gzip_content_encoding(req)
|
|
assert_gzip_file_header(body)
|
|
|
|
envelope = Envelope.deserialize(body)
|
|
|
|
assert_session(envelope, {"init": True, "status": "crashed", "errors": 1})
|
|
|
|
assert_meta(envelope, integration="breakpad")
|
|
assert_breadcrumb(envelope)
|
|
assert_attachment(envelope)
|
|
|
|
assert_breakpad_crash(envelope)
|
|
assert_minidump(envelope)
|
|
|
|
|
|
@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend")
|
|
def test_breakpad_reinstall(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "breakpad"})
|
|
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "reinstall", "crash"],
|
|
env=env,
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "no-setup"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
|
|
|
|
@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend")
|
|
def test_breakpad_dump_inflight(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "breakpad"})
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
child = run(
|
|
tmp_path, "sentry_example", ["log", "capture-multiple", "crash"], env=env
|
|
)
|
|
assert child.returncode # well, it's a crash after all
|
|
|
|
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_shutdown_timeout(cmake, httpserver):
|
|
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
|
|
|
|
# the timings here are:
|
|
# * the process waits 2s for the background thread to shut down, which fails
|
|
# * it then dumps everything and waits another 1s before terminating the process
|
|
# * the python runner waits for 2.4s in total to close the request, which
|
|
# will cleanly terminate the background worker.
|
|
# the assumption here is that 2s < 2.4s < 2s+1s. but since those timers
|
|
# run in different processes, this has the potential of being flaky
|
|
|
|
def delayed(req):
|
|
time.sleep(2.4)
|
|
return "{}"
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_handler(delayed)
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
|
|
|
|
# Using `sleep-after-shutdown` here means that the background worker will
|
|
# deref/free itself, so we will not leak in that case!
|
|
child = run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "capture-multiple", "sleep-after-shutdown"],
|
|
env=env,
|
|
check=True,
|
|
)
|
|
assert child.returncode == 0
|
|
|
|
httpserver.clear_all_handlers()
|
|
httpserver.clear_log()
|
|
|
|
httpserver.expect_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
|
|
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)
|
|
|
|
assert len(httpserver.log) == 10
|
|
|
|
|
|
RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"build_args",
|
|
[
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "Off"}),
|
|
({"SENTRY_TRANSPORT_COMPRESSION": "On"}),
|
|
],
|
|
)
|
|
def test_transaction_only(cmake, httpserver, build_args):
|
|
build_args.update({"SENTRY_BACKEND": "none"})
|
|
tmp_path = cmake(["sentry_example"], build_args)
|
|
|
|
httpserver.expect_oneshot_request(
|
|
"/api/123456/envelope/",
|
|
headers={"x-sentry-auth": auth_header},
|
|
).respond_with_data("OK")
|
|
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver), SENTRY_RELEASE="🤮🚀")
|
|
|
|
run(
|
|
tmp_path,
|
|
"sentry_example",
|
|
["log", "capture-transaction"],
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
assert len(httpserver.log) == 1
|
|
req = httpserver.log[0][0]
|
|
body = req.get_data()
|
|
|
|
if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On":
|
|
assert_gzip_content_encoding(req)
|
|
assert_gzip_file_header(body)
|
|
|
|
envelope = Envelope.deserialize(body)
|
|
|
|
# Show what the envelope looks like if the test fails.
|
|
envelope.print_verbose()
|
|
|
|
# The transaction is overwritten.
|
|
assert_meta(envelope, transaction="little.teapot")
|
|
|
|
# Extract the one-and-only-item
|
|
(event,) = envelope.items
|
|
|
|
assert event.headers["type"] == "transaction"
|
|
payload = event.payload.json
|
|
|
|
# See https://develop.sentry.dev/sdk/performance/trace-context/#trace-context
|
|
trace_context = payload["contexts"]["trace"]
|
|
|
|
assert (
|
|
trace_context["op"] == "Short and stout here is my handle and here is my spout"
|
|
)
|
|
|
|
assert trace_context["trace_id"]
|
|
trace_id = uuid.UUID(hex=trace_context["trace_id"])
|
|
assert trace_id
|
|
|
|
# TODO: currently missing
|
|
# assert trace_context['public_key']
|
|
|
|
assert trace_context["span_id"]
|
|
assert trace_context["status"] == "ok"
|
|
|
|
start_timestamp = time.strptime(payload["start_timestamp"], RFC3339_FORMAT)
|
|
assert start_timestamp
|
|
timestamp = time.strptime(payload["timestamp"], RFC3339_FORMAT)
|
|
assert timestamp >= start_timestamp
|