markdown_stacktrace util: display an event's stacktrace in markdown

This commit is contained in:
Klaas van Schelven
2025-09-12 15:23:19 +02:00
parent 3156f05756
commit 0fb81b29ae
3 changed files with 267 additions and 1 deletions

View File

@@ -27,6 +27,7 @@ from ingest.views import BaseIngestAPIView
from issues.factories import get_or_create_issue
from tags.models import store_tags
from tags.tasks import vacuum_tagvalues
from events.markdown_stacktrace import render_stacktrace_md
from .models import Issue, IssueStateManager, TurningPoint, TurningPointKind
from .regressions import is_regression, is_regression_2, issue_is_regression
@@ -446,6 +447,7 @@ class IntegrationTest(TransactionTestCase):
def setUp(self):
super().setUp()
self.verbosity = self.get_verbosity()
self.maxDiff = None # show full diff on assertEqual failures
def get_verbosity(self):
# https://stackoverflow.com/a/27457315/339144
@@ -527,6 +529,8 @@ class IntegrationTest(TransactionTestCase):
filename, response.content if response.status_code != 302 else response.url))
for event in Event.objects.all():
render_stacktrace_md(event) # just make sure this doesn't crash
urls = [
f'/issues/issue/{ event.issue.id }/event/{ event.id }/',
f'/issues/issue/{ event.issue.id }/event/{ event.id }/details/',
@@ -552,6 +556,90 @@ class IntegrationTest(TransactionTestCase):
# we want to know _which_ event failed, hence the raise-from-e here
raise AssertionError("Error rendering event %s" % event.debug_info) from e
def test_render_stacktrace_md(self):
user = User.objects.create_user(username='test', password='test')
project = Project.objects.create(name="test")
ProjectMembership.objects.create(project=project, user=user)
self.client.force_login(user)
sentry_auth_header = get_header_value(f"http://{ project.sentry_key }@hostisignored/{ project.id }")
# event through the ingestion pipeline
command = SendJsonCommand()
command.stdout = StringIO()
command.stderr = StringIO()
SAMPLES_DIR = os.getenv("SAMPLES_DIR", "../event-samples")
# a nice example because it has 4 kinds of frames (some missing source context, some missing local vars)
filename = SAMPLES_DIR + "/bugsink/frames-with-missing-info.json"
with open(filename) as f:
data = json.loads(f.read())
# leave as-is for reproducibility of the test
# data["event_id"] =
if not command.is_valid(data, filename):
raise Exception("validatity check in %s: %s" % (filename, command.stderr.getvalue()))
response = self.client.post(
f"/api/{ project.id }/store/",
json.dumps(data),
content_type="application/json",
headers={
"X-Sentry-Auth": sentry_auth_header,
"X-BugSink-DebugInfo": filename,
},
)
self.assertEqual(
200, response.status_code, "Error in %s: %s" % (
filename, response.content if response.status_code != 302 else response.url))
event = Event.objects.get(issue__project=project, event_id=data["event_id"])
md = render_stacktrace_md(event, frames="all", exceptions="all", include_locals=True)
self.assertEqual('''# CapturedStacktraceFo
4 kinds of frames
### manage.py:22 in `complete_with_both` [in-app]
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
▶ 22 | main()
#### Locals
* `__name__` = `'__main__'`
* `__doc__` = `"Django's command-line utility for administrative tasks."`
* `__package__` = `None`
* `__loader__` = `<_frozen_importlib_external.SourceFileLoader object at 0x7fe00fb21810>`
* `__spec__` = `None`
* `__annotations__` = `{}`
* `__builtins__` = `<module 'builtins' (built-in)>`
* `__file__` = `'/mnt/datacrypt/dev/bugsink/manage.py'`
* `__cached__` = `None`
* `os` = `<module 'os' from '/usr/lib/python3.10/os.py'>`
### manage.py in `missing_code` [in-app]
_no source context available_
#### Locals
* `execute_from_command_line` = `<function execute_from_command_line at 0x7fe00ec72f80>`
### django/core/management/__init__.py:442 in `missing_vars` [in-app]
437 |
438 |
439 | def execute_from_command_line(argv=None):
440 | """Run a ManagementUtility."""
441 | utility = ManagementUtility(argv)
▶ 442 | utility.execute()
### django/core/management/__init__.py in `missing_everything` [in-app]
_no source context available_''', md)
class GroupingUtilsTestCase(DjangoTestCase):