Skip to content

Commit

Permalink
feat: test against semaphore (#17)
Browse files Browse the repository at this point in the history
* feat: test against semaphore

* chore: refactor stripping module into event module

* fix: install rust non-interactively

* fix: add rust to path

* chore: travis cache

* fix: encoding bug under py3
  • Loading branch information
untitaker authored Aug 3, 2018
1 parent c6bfd12 commit 246f699
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 120 deletions.
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ignore = E203, E266, E501, W503, E402, E731
max-line-length = 80
max-complexity = 18
select = B,C,E,F,W,T4,B9
exclude=checkouts,lol*,.tox
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ venv
.vscode/tags
.pytest_cache
.hypothesis
checkouts
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ python:
- "3.6"
- "3.7-dev"

cache:
pip: true
directories:
- checkouts/semaphore/target/debug/
- ~/.cargo/registry/
- ~/.rustup/

branches:
only:
- master
Expand All @@ -26,7 +33,10 @@ matrix:
- zeus upload -t "application/zip+wheel" dist/*

install:
- curl https://sh.rustup.rs -sSf | sh -s -- -y
- . $HOME/.cargo/env
- pip install tox
- sh scripts/checkout-semaphore.sh

script:
- sh scripts/runtox.sh
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.black]
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| checkouts
)/
'''
23 changes: 23 additions & 0 deletions scripts/checkout-semaphore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh

# This script is able to restore partially checked out repositories,
# repositories that have all files but no git content, repositories checked out
# on the wrong branch, etc.
#
# This is mainly useful because Travis creates an empty folder in the attempt
# to restore the build cache.

set -xe

if [ ! -d checkouts/semaphore/.git ]; then
mkdir -p checkouts/semaphore/
fi

cd checkouts/semaphore/
git init
git remote remove origin || true
git remote add origin https://github.com/getsentry/semaphore
git fetch
git reset --hard origin/master
git clean -f
cargo build
4 changes: 2 additions & 2 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import random
import atexit

from .utils import Dsn, SkipEvent, ContextVar, Event
from .utils import Dsn, SkipEvent, ContextVar
from .transport import Transport
from .consts import DEFAULT_OPTIONS, SDK_INFO
from .stripping import strip_event, flatten_metadata
from .event import strip_event, flatten_metadata, Event


NO_DSN = object()
Expand Down
62 changes: 62 additions & 0 deletions sentry_sdk/stripping.py → sentry_sdk/event.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,70 @@
import uuid
from datetime import datetime

from collections import Mapping, Sequence

from .utils import exceptions_from_error_tuple
from ._compat import text_type


def datetime_to_json(dt):
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")


class Event(Mapping):
__slots__ = ("_data", "_exc_value")

def __init__(self, data={}):
self._data = {
"event_id": uuid.uuid4().hex,
"timestamp": datetime_to_json(datetime.utcnow()),
"level": "error",
}

self._data.update(data)

self._exc_value = None

def set_exception(self, exc_type, exc_value, tb, with_locals):
self["exception"] = {
"values": exceptions_from_error_tuple(exc_type, exc_value, tb, with_locals)
}
self._exc_value = exc_value

def __getitem__(self, key):
return self._data[key]

def __contains__(self, key):
return key in self._data

def get(self, *a, **kw):
return self._data.get(*a, **kw)

def setdefault(self, *a, **kw):
return self._data.setdefault(*a, **kw)

def __setitem__(self, key, value):
self._data[key] = value

def __iter__(self):
return iter(self._data)

def __len__(self):
return len(self._data)

def iter_frames(self):
stacktraces = []
if "stacktrace" in self:
stacktraces.append(self["stacktrace"])
if "exception" in self:
for exception in self["exception"].get("values") or ():
if "stacktrace" in exception:
stacktraces.append(exception["stacktrace"])
for stacktrace in stacktraces:
for frame in stacktrace.get("frames") or ():
yield frame


class AnnotatedValue(object):
def __init__(self, value, metadata):
self.value = value
Expand Down
3 changes: 2 additions & 1 deletion sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from ._compat import with_metaclass
from .scope import Scope
from .utils import Event, skip_internal_frames, ContextVar
from .utils import skip_internal_frames, ContextVar
from .event import Event


_local = ContextVar("sentry_current_hub")
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/_wsgi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json

from sentry_sdk.hub import _should_send_default_pii
from sentry_sdk.stripping import AnnotatedValue
from sentry_sdk.event import AnnotatedValue


def get_environ(environ):
Expand Down
7 changes: 6 additions & 1 deletion sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

import sys
import logging
import datetime

from sentry_sdk import get_current_hub, capture_event, add_breadcrumb
from sentry_sdk.utils import to_string, Event, skip_internal_frames
from sentry_sdk.utils import to_string, skip_internal_frames
from sentry_sdk.event import Event, datetime_to_json
from sentry_sdk.hub import _internal_exceptions

from . import Integration
Expand Down Expand Up @@ -51,6 +53,9 @@ def _breadcrumb_from_record(self, record):
"level": self._logging_to_event_level(record.levelname),
"category": record.name,
"message": record.message,
"timestamp": datetime_to_json(
datetime.datetime.fromtimestamp(record.created)
),
}

def _emit(self, record):
Expand Down
58 changes: 0 additions & 58 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import uuid
import linecache

from datetime import datetime
Expand Down Expand Up @@ -313,63 +312,6 @@ def exceptions_from_error_tuple(exc_type, exc_value, tb, with_locals=True):
return rv


class Event(Mapping):
__slots__ = ("_data", "_exc_value")

def __init__(self, data={}):
self._data = {
"event_id": uuid.uuid4().hex,
"timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
"level": "error",
}

self._data.update(data)

self._exc_value = None

def get_json(self):
return self._data

def set_exception(self, exc_type, exc_value, tb, with_locals):
self["exception"] = {
"values": exceptions_from_error_tuple(exc_type, exc_value, tb, with_locals)
}
self._exc_value = exc_value

def __getitem__(self, key):
return self._data[key]

def __contains__(self, key):
return key in self._data

def get(self, *a, **kw):
return self._data.get(*a, **kw)

def setdefault(self, *a, **kw):
return self._data.setdefault(*a, **kw)

def __setitem__(self, key, value):
self._data[key] = value

def __iter__(self):
return iter(self._data)

def __len__(self):
return len(self._data)

def iter_frames(self):
stacktraces = []
if "stacktrace" in self:
stacktraces.append(self["stacktrace"])
if "exception" in self:
for exception in self["exception"].get("values") or ():
if "stacktrace" in exception:
stacktraces.append(exception["stacktrace"])
for stacktrace in stacktraces:
for frame in stacktrace.get("frames") or ():
yield frame


class SkipEvent(Exception):
"""Risen from an event processor to indicate that the event should be
ignored and not be reported."""
Expand Down
100 changes: 100 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
import subprocess
import json

import pytest

import sentry_sdk
from sentry_sdk.client import Transport

SEMAPHORE = "./checkouts/semaphore/target/debug/semaphore"

if not os.path.isfile(SEMAPHORE):
SEMAPHORE = None


@pytest.fixture(autouse=True)
def reraise_internal_exceptions(monkeypatch):
def capture_internal_exception(error=None):
if not error:
raise
raise error

monkeypatch.setattr(
sentry_sdk.get_current_hub(),
"capture_internal_exception",
capture_internal_exception,
)


@pytest.fixture
def monkeypatch_test_transport(monkeypatch, assert_semaphore_acceptance):
def inner(client):
monkeypatch.setattr(
client, "_transport", TestTransport(assert_semaphore_acceptance)
)

return inner


def _no_errors_in_semaphore_response(obj):
"""Assert that semaphore didn't throw any errors when processing the
event."""

def inner(obj):
if not isinstance(obj, dict):
return

assert "err" not in obj

for value in obj.values():
inner(value)

try:
inner(obj.get("_meta"))
inner(obj.get(""))
except AssertionError:
raise AssertionError(obj)


@pytest.fixture
def assert_semaphore_acceptance(tmpdir):
if not SEMAPHORE:
return lambda event: None

def inner(event):
# not dealing with the subprocess API right now
file = tmpdir.join("event")
file.write(json.dumps(dict(event)))
output = json.loads(
subprocess.check_output(
[SEMAPHORE, "process-event"], stdin=file.open()
).decode("utf-8")
)
_no_errors_in_semaphore_response(output)

return inner


@pytest.fixture
def sentry_init(monkeypatch_test_transport, assert_semaphore_acceptance):
def inner(*a, **kw):
client = sentry_sdk.Client(*a, **kw)
monkeypatch_test_transport(client)
sentry_sdk.Hub.current.bind_client(client)

return inner


class TestTransport(Transport):
def __init__(self, capture_event_callback):
self.capture_event = capture_event_callback
self._queue = None

def start(self):
pass

def close(self):
pass

dsn = "LOL"
Loading

0 comments on commit 246f699

Please sign in to comment.