Skip to content

Commit

Permalink
test: Add integration test for envelope with transaction (#671)
Browse files Browse the repository at this point in the history
This adds an integration test which inspects the envelope of a
transaction sent by the native SDK.

NATIVE-407
  • Loading branch information
Floris Bruynooghe authored Jan 27, 2022
1 parent 98cefcf commit c191937
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 18 deletions.
50 changes: 48 additions & 2 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import sys
import urllib
import pytest
import pprint
import textwrap

sourcedir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))

Expand Down Expand Up @@ -165,6 +167,19 @@ def deserialize(
# type: (...) -> Envelope
return cls.deserialize_from(io.BytesIO(bytes))

def print_verbose(self, indent=0):
"""Pretty prints the envelope."""
print(" " * indent + "Envelope:")
indent += 2
print(" " * indent + "Headers:")
indent += 2
print(textwrap.indent(pprint.pformat(self.headers), " " * indent))
indent -= 2
print(" " * indent + "Items:")
indent += 2
for item in self.items:
item.print_verbose(indent)

def __repr__(self):
# type: (...) -> str
return "<Envelope headers=%r items=%r>" % (self.headers, self.items)
Expand All @@ -180,6 +195,21 @@ def __init__(
self.json = json
self.bytes = bytes

def print_verbose(self, indent=0):
"""Pretty prints the item."""
print(" " * indent + "Payload:")
indent += 2
if self.bytes:
payload = self.bytes.encode("utf-8", errors="replace")
if self.json:
payload = pprint.pformat(self.json)
try:
print(textwrap.indent(payload, " " * indent))
except UnicodeEncodeError:
# Windows CI appears fickle, and we put emojis in the json
payload = payload.encode("ascii", errors="replace").decode()
print(textwrap.indent(payload, " " * indent))

def __repr__(self):
# type: (...) -> str
return "<Payload bytes=%r json=%r>" % (self.bytes, self.json)
Expand All @@ -205,7 +235,11 @@ def __init__(

def get_event(self):
# type: (...) -> Optional[Event]
if self.headers.get("type") == "event" and self.payload.json is not None:
# Transactions are events with a few extra fields, so return them as well.
if (
self.headers.get("type") in ["event", "transaction"]
and self.payload.json is not None
):
return self.payload.json
return None

Expand All @@ -220,7 +254,7 @@ def deserialize_from(
headers = json.loads(line)
length = headers["length"]
payload = f.read(length)
if headers.get("type") == "event" or headers.get("type") == "session":
if headers.get("type") in ["event", "session", "transaction"]:
rv = cls(headers=headers, payload=PayloadRef(json=json.loads(payload)))
else:
rv = cls(headers=headers, payload=payload)
Expand All @@ -234,6 +268,18 @@ def deserialize(
# type: (...) -> Optional[Item]
return cls.deserialize_from(io.BytesIO(bytes))

def print_verbose(self, indent=0):
"""Pretty prints the item."""
item_type = self.headers.get("type", "?").capitalize()
print(" " * indent + f"{item_type}:")
indent += 2
print(" " * indent + "Headers:")
indent += 2
headers = pprint.pformat(self.headers)
print(textwrap.indent(headers, " " * indent))
indent -= 2
self.payload.print_verbose(indent)

def __repr__(self):
# type: (...) -> str
return "<Item headers=%r payload=%r>" % (
Expand Down
43 changes: 27 additions & 16 deletions tests/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def matches(actual, expected):
return {k: v for (k, v) in actual.items() if k in expected.keys()} == expected


def assert_matches(actual, expected):
"""Assert two objects for equality, ignoring extra keys in ``actual``."""
assert {k: v for (k, v) in actual.items() if k in expected.keys()} == expected


def assert_session(envelope, extra_assertion=None):
session = None
for item in envelope:
Expand All @@ -27,18 +32,23 @@ def assert_session(envelope, extra_assertion=None):
"environment": "development",
}
if extra_assertion:
assert matches(session, extra_assertion)
assert_matches(session, extra_assertion)


def assert_meta(envelope, release="test-example-release", integration=None):
def assert_meta(
envelope,
release="test-example-release",
integration=None,
transaction="test-transaction",
):
event = envelope.get_event()

expected = {
"platform": "native",
"environment": "development",
"release": release,
"user": {"id": 42, "username": "some_name"},
"transaction": "test-transaction",
"transaction": transaction,
"tags": {"expected-tag": "some value"},
"extra": {"extra stuff": "some value", "…unicode key…": "őá…–🤮🚀¿ 한글 테스트"},
}
Expand All @@ -51,7 +61,7 @@ def assert_meta(envelope, release="test-example-release", integration=None):
}
if not is_android:
if sys.platform == "win32":
assert matches(
assert_matches(
event["contexts"]["os"],
{"name": "Windows", "version": platform.version()},
)
Expand All @@ -62,7 +72,7 @@ def assert_meta(envelope, release="test-example-release", integration=None):
version = match.group(1)
build = match.group(2)

assert matches(
assert_matches(
event["contexts"]["os"],
{"name": "Linux", "version": version, "build": build},
)
Expand All @@ -72,7 +82,7 @@ def assert_meta(envelope, release="test-example-release", integration=None):
version.append("0")
version = ".".join(version)

assert matches(
assert_matches(
event["contexts"]["os"],
{
"name": "macOS",
Expand All @@ -82,20 +92,21 @@ def assert_meta(envelope, release="test-example-release", integration=None):
)
assert event["contexts"]["os"]["build"] is not None

assert matches(event, expected)
assert matches(event["sdk"], expected_sdk)
assert matches(
assert_matches(event, expected)
assert_matches(event["sdk"], expected_sdk)
assert_matches(
event["contexts"], {"runtime": {"type": "runtime", "name": "testing-runtime"}}
)

if integration is None:
assert event["sdk"].get("integrations") is None
else:
assert event["sdk"]["integrations"] == [integration]
assert any(
"sentry_example" in image["code_file"]
for image in event["debug_meta"]["images"]
)
if event.get("type") == "event":
assert any(
"sentry_example" in image["code_file"]
for image in event["debug_meta"]["images"]
)


def assert_stacktrace(envelope, inside_exception=False, check_size=True):
Expand Down Expand Up @@ -155,7 +166,7 @@ def assert_event(envelope):
"logger": "my-logger",
"message": {"formatted": "Hello World!"},
}
assert matches(event, expected)
assert_matches(event, expected)
assert_timestamp(event["timestamp"])


Expand All @@ -165,13 +176,13 @@ def assert_exception(envelope):
"type": "ParseIntError",
"value": "invalid digit found in string",
}
assert matches(event["exception"]["values"][0], exception)
assert_matches(event["exception"]["values"][0], exception)
assert_timestamp(event["timestamp"])


def assert_crash(envelope):
event = envelope.get_event()
assert matches(event, {"level": "fatal"})
assert_matches(event, {"level": "fatal"})
# depending on the unwinder, we currently don’t get any stack frames from
# a `ucontext`
assert_stacktrace(envelope, inside_exception=True, check_size=False)
Expand Down
59 changes: 59 additions & 0 deletions tests/test_integration_http.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import time
import pytest
import subprocess
import sys
import os
import time
import itertools
import uuid
import json
from . import make_dsn, check_output, run, Envelope
from .conditions import has_http, has_breakpad, has_files
Expand Down Expand Up @@ -413,3 +415,60 @@ def delayed(req):
run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env)

assert len(httpserver.log) == 10


def test_transaction_only(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})

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
output = httpserver.log[0][0].get_data()
envelope = Envelope.deserialize(output)

# 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"
json = event.payload.json

# See https://develop.sentry.dev/sdk/performance/trace-context/#trace-context
trace_context = json["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"

RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
start_timestamp = time.strptime(json["start_timestamp"], RFC3339_FORMAT)
assert start_timestamp
timestamp = time.strptime(json["timestamp"], RFC3339_FORMAT)
assert timestamp >= start_timestamp

0 comments on commit c191937

Please sign in to comment.