From b0c2427cdc59be0bb1fa76e928b17d9aa1a0d4eb Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 28 Jan 2015 17:31:41 +0100 Subject: [PATCH] python: add Ruby support Related: #1334604 Signed-off-by: Martin Milata Conflicts: tests/Makefile.am --- python/Makefile.am | 4 + python/py_module.c | 22 +++++ python/py_report.c | 9 ++ python/py_ruby_frame.c | 227 ++++++++++++++++++++++++++++++++++++++++++++ python/py_ruby_frame.h | 69 ++++++++++++++ python/py_ruby_stacktrace.c | 182 +++++++++++++++++++++++++++++++++++ python/py_ruby_stacktrace.h | 71 ++++++++++++++ tests/Makefile.am | 11 ++- tests/python/ruby.py | 174 +++++++++++++++++++++++++++++++++ tests/python_bindings.at | 1 + 10 files changed, 769 insertions(+), 1 deletion(-) create mode 100644 python/py_ruby_frame.c create mode 100644 python/py_ruby_frame.h create mode 100644 python/py_ruby_stacktrace.c create mode 100644 python/py_ruby_stacktrace.h create mode 100755 tests/python/ruby.py diff --git a/python/Makefile.am b/python/Makefile.am index 83273a2..9380c12 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -44,6 +44,10 @@ _satyr_la_SOURCES = \ py_java_thread.c \ py_java_stacktrace.h \ py_java_stacktrace.c \ + py_ruby_frame.h \ + py_ruby_frame.c \ + py_ruby_stacktrace.h \ + py_ruby_stacktrace.c \ py_rpm_package.h \ py_rpm_package.c \ py_metrics.h \ diff --git a/python/py_module.c b/python/py_module.c index 83697ef..144465b 100644 --- a/python/py_module.c +++ b/python/py_module.c @@ -17,6 +17,8 @@ #include "py_core_frame.h" #include "py_core_thread.h" #include "py_core_stacktrace.h" +#include "py_ruby_frame.h" +#include "py_ruby_stacktrace.h" #include "py_rpm_package.h" #include "py_metrics.h" #include "py_operating_system.h" @@ -187,6 +189,18 @@ init_satyr() return; } + if (PyType_Ready(&sr_py_ruby_frame_type) < 0) + { + puts("PyType_Ready(&sr_py_ruby_frame_type) < 0"); + return; + } + + if (PyType_Ready(&sr_py_ruby_stacktrace_type) < 0) + { + puts("PyType_Ready(&sr_py_ruby_stacktrace_type) < 0"); + return; + } + if (PyType_Ready(&sr_py_operating_system_type) < 0) { puts("PyType_Ready(&sr_py_operating_system_type) < 0"); @@ -301,6 +315,14 @@ init_satyr() PyModule_AddObject(module, "JavaStacktrace", (PyObject *)&sr_py_java_stacktrace_type); + Py_INCREF(&sr_py_ruby_frame_type); + PyModule_AddObject(module, "RubyFrame", + (PyObject *)&sr_py_ruby_frame_type); + + Py_INCREF(&sr_py_ruby_stacktrace_type); + PyModule_AddObject(module, "RubyStacktrace", + (PyObject *)&sr_py_ruby_stacktrace_type); + Py_INCREF(&sr_py_core_frame_type); PyModule_AddObject(module, "CoreFrame", (PyObject *)&sr_py_core_frame_type); diff --git a/python/py_report.c b/python/py_report.c index 8aa35dd..74cd9dd 100644 --- a/python/py_report.c +++ b/python/py_report.c @@ -28,6 +28,7 @@ #include "py_python_stacktrace.h" #include "py_koops_stacktrace.h" #include "py_java_stacktrace.h" +#include "py_ruby_stacktrace.h" #include "report.h" #include "operating_system.h" @@ -284,6 +285,10 @@ report_prepare_subobjects(struct sr_py_report *report) if (stacktrace_prepare(report, &sr_py_koops_stacktrace_type, false) < 0) return -1; break; + case SR_REPORT_RUBY: + if (stacktrace_prepare(report, &sr_py_ruby_stacktrace_type, false) < 0) + return -1; + break; default: report->report->stacktrace = NULL; break; @@ -347,6 +352,10 @@ report_to_python_obj(struct sr_report *report) ro->stacktrace = koops_stacktrace_to_python_obj( (struct sr_koops_stacktrace *)report->stacktrace); break; + case SR_REPORT_RUBY: + ro->stacktrace = ruby_stacktrace_to_python_obj( + (struct sr_ruby_stacktrace *)report->stacktrace); + break; default: Py_INCREF(Py_None); ro->stacktrace = Py_None; diff --git a/python/py_ruby_frame.c b/python/py_ruby_frame.c new file mode 100644 index 0000000..9ed45ad --- /dev/null +++ b/python/py_ruby_frame.c @@ -0,0 +1,227 @@ +/* + py_ruby_frame.c + + Copyright (C) 2015 Red Hat, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "py_common.h" +#include "py_base_frame.h" +#include "py_ruby_frame.h" +#include "location.h" +#include "strbuf.h" +#include "utils.h" +#include "ruby/frame.h" + +#define frame_doc "satyr.RubyFrame - class representing a ruby frame\n\n" \ + "Usage:\n\n" \ + "satyr.RubyFrame() - creates an empty frame\n\n" \ + "satyr.RubyFrame(str) - parses str and fills the frame object" + +#define f_dup_doc "Usage: frame.dup()\n\n" \ + "Returns: satyr.RubyFrame - a new clone of frame\n\n" \ + "Clones the frame object. All new structures are independent " \ + "of the original object." + + +static PyMethodDef +frame_methods[] = +{ + /* methods */ + { "dup", sr_py_ruby_frame_dup, METH_NOARGS, f_dup_doc }, + { NULL }, +}; + +/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */ +#define GSOFF_PY_STRUCT sr_py_ruby_frame +#define GSOFF_PY_MEMBER frame +#define GSOFF_C_STRUCT sr_ruby_frame +GSOFF_START +GSOFF_MEMBER(file_name), +GSOFF_MEMBER(file_line), +GSOFF_MEMBER(special_function), +GSOFF_MEMBER(function_name), +GSOFF_MEMBER(block_level), +GSOFF_MEMBER(rescue_level) +GSOFF_END + +static PyGetSetDef +frame_getset[] = +{ + SR_ATTRIBUTE_STRING(file_name, "Source file name (string)" ), + SR_ATTRIBUTE_UINT32(file_line, "Source line number (positive integer)" ), + SR_ATTRIBUTE_BOOL (special_function, "True if the frame doesn't belong to an actual function" ), + SR_ATTRIBUTE_STRING(function_name, "Function name (string)" ), + SR_ATTRIBUTE_UINT32(block_level, "Block nesting level (integer)" ), + SR_ATTRIBUTE_UINT32(rescue_level, "Rescue block nesting level (integer)" ), + { NULL }, +}; + +PyTypeObject +sr_py_ruby_frame_type = +{ + PyObject_HEAD_INIT(NULL) + 0, + "satyr.RubyFrame", /* tp_name */ + sizeof(struct sr_py_ruby_frame), /* tp_basicsize */ + 0, /* tp_itemsize */ + sr_py_ruby_frame_free, /* tp_dealloc */ + NULL, /* tp_print */ + NULL, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_compare */ + NULL, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL, /* tp_hash */ + NULL, /* tp_call */ + sr_py_ruby_frame_str, /* tp_str */ + NULL, /* tp_getattro */ + NULL, /* tp_setattro */ + NULL, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + frame_doc, /* tp_doc */ + NULL, /* tp_traverse */ + NULL, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + frame_methods, /* tp_methods */ + NULL, /* tp_members */ + frame_getset, /* tp_getset */ + &sr_py_base_frame_type, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + sr_py_ruby_frame_new, /* tp_new */ + NULL, /* tp_free */ + NULL, /* tp_is_gc */ + NULL, /* tp_bases */ + NULL, /* tp_mro */ + NULL, /* tp_cache */ + NULL, /* tp_subclasses */ + NULL, /* tp_weaklist */ +}; + +/* constructor */ +PyObject * +sr_py_ruby_frame_new(PyTypeObject *object, PyObject *args, PyObject *kwds) +{ + struct sr_py_ruby_frame *fo = (struct sr_py_ruby_frame*) + PyObject_New(struct sr_py_ruby_frame, &sr_py_ruby_frame_type); + + if (!fo) + return PyErr_NoMemory(); + + const char *str = NULL; + if (!PyArg_ParseTuple(args, "|s", &str)) + return NULL; + + if (str) + { + struct sr_location location; + sr_location_init(&location); + fo->frame = sr_ruby_frame_parse(&str, &location); + + if (!fo->frame) + { + PyErr_SetString(PyExc_ValueError, location.message); + return NULL; + } + } + else + fo->frame = sr_ruby_frame_new(); + + return (PyObject*)fo; +} + +/* destructor */ +void +sr_py_ruby_frame_free(PyObject *object) +{ + struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)object; + sr_ruby_frame_free(this->frame); + PyObject_Del(object); +} + +/* str */ +PyObject * +sr_py_ruby_frame_str(PyObject *self) +{ + struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)self; + struct sr_strbuf *buf = sr_strbuf_new(); + + + if (this->frame->file_name) + { + sr_strbuf_append_str(buf, this->frame->file_name); + } + + if (this->frame->file_line) + { + sr_strbuf_append_strf(buf, ":%d", this->frame->file_line); + } + + if (this->frame->function_name) + { + sr_strbuf_append_str(buf, ":in `"); + + int i; + for (i = 0; i < this->frame->rescue_level; i++) + { + sr_strbuf_append_str(buf, "rescue in "); + } + + if (this->frame->block_level == 1) + { + sr_strbuf_append_str(buf, "block in "); + } + else if (this->frame->block_level > 1) + { + sr_strbuf_append_strf(buf, "block (%d levels) in ", this->frame->block_level); + } + + sr_strbuf_append_strf(buf, "%s%s%s'", + (this->frame->special_function ? "<" : ""), + this->frame->function_name, + (this->frame->special_function ? ">" : "")); + } + + char *str = sr_strbuf_free_nobuf(buf); + PyObject *result = Py_BuildValue("s", str); + free(str); + return result; +} + +/* methods */ +PyObject * +sr_py_ruby_frame_dup(PyObject *self, PyObject *args) +{ + struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)self; + struct sr_py_ruby_frame *fo = (struct sr_py_ruby_frame*) + PyObject_New(struct sr_py_ruby_frame, &sr_py_ruby_frame_type); + + if (!fo) + return PyErr_NoMemory(); + + fo->frame = sr_ruby_frame_dup(this->frame, false); + + return (PyObject*)fo; +} diff --git a/python/py_ruby_frame.h b/python/py_ruby_frame.h new file mode 100644 index 0000000..7f460ff --- /dev/null +++ b/python/py_ruby_frame.h @@ -0,0 +1,69 @@ +/* + py_ruby_frame.h + + Copyright (C) 2015 Red Hat, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef SATYR_PY_RUBY_FRAME_H +#define SATYR_PY_RUBY_FRAME_H + +/** + * @file + * @brief Python bindings for RUBY frame. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +PyTypeObject sr_py_ruby_frame_type; + +/* The beginning of this structure has to have the same layout as + * sr_py_base_frame. + */ +struct sr_py_ruby_frame +{ + PyObject_HEAD + struct sr_ruby_frame *frame; +}; + +/** + * Constructor. + */ +PyObject *sr_py_ruby_frame_new(PyTypeObject *object, + PyObject *args, PyObject *kwds); + +/** + * Destructor. + */ +void sr_py_ruby_frame_free(PyObject *object); + +/** + * str + */ +PyObject *sr_py_ruby_frame_str(PyObject *self); + +/* methods */ +PyObject *sr_py_ruby_frame_dup(PyObject *self, PyObject *args); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/python/py_ruby_stacktrace.c b/python/py_ruby_stacktrace.c new file mode 100644 index 0000000..97fbb44 --- /dev/null +++ b/python/py_ruby_stacktrace.c @@ -0,0 +1,182 @@ +#include "py_common.h" +#include "py_ruby_frame.h" +#include "py_ruby_stacktrace.h" +#include "py_base_stacktrace.h" +#include "utils.h" +#include "strbuf.h" +#include "ruby/frame.h" +#include "ruby/stacktrace.h" +#include "location.h" +#include "stacktrace.h" + +#define stacktrace_doc "satyr.RubyStacktrace - class representing a ruby stacktrace\n\n" \ + "Usage:\n\n" \ + "satyr.RubyStacktrace() - creates an empty ruby stacktrace\n\n" \ + "satyr.RubyStacktrace(str) - parses str and fills the ruby stacktrace object" + +#define f_dup_doc "Usage: stacktrace.dup()\n\n" \ + "Returns: satyr.RubyStacktrace - a new clone of ruby stacktrace\n\n" \ + "Clones the RubyStacktrace object. All new structures are independent " \ + "of the original object." + + +static PyMethodDef +ruby_stacktrace_methods[] = +{ + /* methods */ + { "dup", sr_py_ruby_stacktrace_dup, METH_NOARGS, f_dup_doc }, + { NULL }, +}; + +/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */ +#define GSOFF_PY_STRUCT sr_py_ruby_stacktrace +#define GSOFF_PY_MEMBER stacktrace +#define GSOFF_C_STRUCT sr_ruby_stacktrace +GSOFF_START +GSOFF_MEMBER(exception_name) +GSOFF_END + +static PyGetSetDef +ruby_stacktrace_getset[] = +{ + SR_ATTRIBUTE_STRING(exception_name, "Exception type (string)" ), + { NULL }, +}; + +PyTypeObject sr_py_ruby_stacktrace_type = { + PyObject_HEAD_INIT(NULL) + 0, + "satyr.RubyStacktrace", /* tp_name */ + sizeof(struct sr_py_ruby_stacktrace), /* tp_basicsize */ + 0, /* tp_itemsize */ + sr_py_ruby_stacktrace_free, /* tp_dealloc */ + NULL, /* tp_print */ + NULL, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_compare */ + NULL, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL, /* tp_hash */ + NULL, /* tp_call */ + sr_py_ruby_stacktrace_str, /* tp_str */ + NULL, /* tp_getattro */ + NULL, /* tp_setattro */ + NULL, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + stacktrace_doc, /* tp_doc */ + NULL, /* tp_traverse */ + NULL, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + ruby_stacktrace_methods, /* tp_methods */ + NULL, /* tp_members */ + ruby_stacktrace_getset, /* tp_getset */ + &sr_py_single_stacktrace_type, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + sr_py_ruby_stacktrace_new, /* tp_new */ + NULL, /* tp_free */ + NULL, /* tp_is_gc */ + NULL, /* tp_bases */ + NULL, /* tp_mro */ + NULL, /* tp_cache */ + NULL, /* tp_subclasses */ + NULL, /* tp_weaklist */ +}; + +PyObject * +ruby_stacktrace_to_python_obj(struct sr_ruby_stacktrace *stacktrace) +{ + struct sr_py_ruby_stacktrace *bo = PyObject_New(struct sr_py_ruby_stacktrace, + &sr_py_ruby_stacktrace_type); + if (!bo) + return PyErr_NoMemory(); + + bo->frame_type = &sr_py_ruby_frame_type; + + bo->stacktrace = stacktrace; + bo->frames = frames_to_python_list((struct sr_thread *)bo->stacktrace, + bo->frame_type); + if (!bo->frames) + return NULL; + + return (PyObject *)bo; +} + +/* constructor */ +PyObject * +sr_py_ruby_stacktrace_new(PyTypeObject *object, + PyObject *args, + PyObject *kwds) +{ + const char *str = NULL; + if (!PyArg_ParseTuple(args, "|s", &str)) + return NULL; + + struct sr_ruby_stacktrace *stacktrace; + + if (str) + { + struct sr_location location; + sr_location_init(&location); + stacktrace = sr_ruby_stacktrace_parse(&str, &location); + if (!stacktrace) + { + PyErr_SetString(PyExc_ValueError, location.message); + return NULL; + } + } + else + stacktrace = sr_ruby_stacktrace_new(); + + return ruby_stacktrace_to_python_obj(stacktrace); +} + +/* destructor */ +void +sr_py_ruby_stacktrace_free(PyObject *object) +{ + struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace*)object; + /* the list will decref all of its elements */ + Py_DECREF(this->frames); + this->stacktrace->frames = NULL; + sr_ruby_stacktrace_free(this->stacktrace); + PyObject_Del(object); +} + +/* str */ +PyObject * +sr_py_ruby_stacktrace_str(PyObject *self) +{ + struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace *)self; + struct sr_strbuf *buf = sr_strbuf_new(); + sr_strbuf_append_strf(buf, "Ruby stacktrace with %zd frames", + (ssize_t)(PyList_Size(this->frames))); + char *str = sr_strbuf_free_nobuf(buf); + PyObject *result = Py_BuildValue("s", str); + free(str); + return result; +} + +/* methods */ +PyObject * +sr_py_ruby_stacktrace_dup(PyObject *self, PyObject *args) +{ + struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace*)self; + if (frames_prepare_linked_list((struct sr_py_base_thread *)this) < 0) + return NULL; + + struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_dup(this->stacktrace); + if (!stacktrace) + return NULL; + + return ruby_stacktrace_to_python_obj(stacktrace); +} diff --git a/python/py_ruby_stacktrace.h b/python/py_ruby_stacktrace.h new file mode 100644 index 0000000..f4eebce --- /dev/null +++ b/python/py_ruby_stacktrace.h @@ -0,0 +1,71 @@ +/* + py_ruby_stacktrace.h + + Copyright (C) 2015 Red Hat, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef SATYR_PY_RUBY_STACKTRACE_H +#define SATYR_PY_RUBY_STACKTRACE_H + +/** + * @file + * @brief Python bindings for Ruby stack trace. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +PyTypeObject sr_py_ruby_stacktrace_type; + +/* The beginning of this structure has to have the same layout as + * sr_py_base_thread. + */ +struct sr_py_ruby_stacktrace +{ + PyObject_HEAD + struct sr_ruby_stacktrace *stacktrace; + PyObject *frames; + PyTypeObject *frame_type; +}; + +/* helpers */ +PyObject *ruby_stacktrace_to_python_obj(struct sr_ruby_stacktrace *stacktrace); + +/* constructor */ +PyObject *sr_py_ruby_stacktrace_new(PyTypeObject *object, + PyObject *args, + PyObject *kwds); + +/* destructor */ +void sr_py_ruby_stacktrace_free(PyObject *object); + +/* str */ +PyObject *sr_py_ruby_stacktrace_str(PyObject *self); + +/* methods */ +PyObject *sr_py_ruby_stacktrace_dup(PyObject *self, PyObject *args); +PyObject *sr_py_ruby_stacktrace_normalize(PyObject *self, PyObject *args); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 0eb33a5..b261880 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,7 +4,16 @@ EXTRA_DIST = gdb_stacktraces \ json_files \ kerneloopses \ programs \ - python \ + python/core.py \ + python/gdb.py \ + python/java.py \ + python/koops.py \ + python/metrics.py \ + python/misc.py \ + python/python.py \ + python/report.py \ + python/ruby.py \ + python/test_helpers.py \ python_stacktraces ## ------------ ## diff --git a/tests/python/ruby.py b/tests/python/ruby.py new file mode 100755 index 0000000..8a20593 --- /dev/null +++ b/tests/python/ruby.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python + +import unittest +from test_helpers import * + +path = '../ruby_stacktraces/ruby-01' +contents = load_input_contents(path) +frames_expected = 21 +expected_short_text = '''#1 rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13 +#2 block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:10 +#3 times in /usr/share/ruby/vendor_ruby/will_crash.rb:9 +#4 block in func in /usr/share/ruby/vendor_ruby/will_crash.rb:9 +#5 times in /usr/share/ruby/vendor_ruby/will_crash.rb:8 +#6 func in /usr/share/ruby/vendor_ruby/will_crash.rb:8 +''' + +class TestRubyStacktrace(BindingsTestCase): + def setUp(self): + self.trace = satyr.RubyStacktrace(contents) + + def test_correct_frame_count(self): + self.assertEqual(frame_count(self.trace), frames_expected) + + def test_dup(self): + dup = self.trace.dup() + self.assertNotEqual(id(dup.frames), id(self.trace.frames)) + self.assertTrue(all(map(lambda t1, t2: t1.equals(t2), dup.frames, self.trace.frames))) + + dup.frames = dup.frames[:5] + dup2 = dup.dup() + self.assertEqual(len(dup.frames), len(dup2.frames)) + self.assertNotEqual(id(dup.frames), id(dup2.frames)) + + def test_prepare_linked_list(self): + dup = self.trace.dup() + dup.frames = dup.frames[:5] + dup2 = dup.dup() + self.assertTrue(len(dup2.frames) <= 5) + + def test_str(self): + out = str(self.trace) + self.assertTrue(('Ruby stacktrace with %d frames' % frames_expected) in out) + + def test_getset(self): + self.assertGetSetCorrect(self.trace, 'exception_name', 'Wrap::MyException', 'WhateverException') + + def test_special_functions(self): + trace = load_input_contents('../ruby_stacktraces/ruby-01') + trace = satyr.RubyStacktrace(trace) + + f = trace.frames[0] + self.assertEqual(f.file_name, '/usr/share/ruby/vendor_ruby/will_crash.rb') + self.assertEqual(f.function_name, 'func') + self.assertFalse(f.special_function) + self.assertEqual(f.block_level, 2) + self.assertEqual(f.rescue_level, 1) + + f = trace.frames[-1] + self.assertEqual(f.file_name, '/usr/bin/will_ruby_raise') + self.assertEqual(f.function_name, 'main') + self.assertTrue(f.special_function) + self.assertEqual(f.block_level, 0) + self.assertEqual(f.rescue_level, 0) + + def test_to_short_text(self): + self.assertEqual(self.trace.to_short_text(6), expected_short_text) + + def test_bthash(self): + self.assertEqual(self.trace.get_bthash(), '6124e03542a0381b14ebaf2c5b5e9f467ebba33b') + + def test_duphash(self): + expected_plain = '''Thread +/usr/share/ruby/vendor_ruby/will_crash.rb:13 +/usr/share/ruby/vendor_ruby/will_crash.rb:10 +/usr/share/ruby/vendor_ruby/will_crash.rb:9 +''' + self.assertEqual(self.trace.get_duphash(flags=satyr.DUPHASH_NOHASH, frames=3), expected_plain) + self.assertEqual(self.trace.get_duphash(), 'c877cda04fbce8d51c4d9b1d628b0f618677607e') + + def test_crash_thread(self): + self.assertTrue(self.trace.crash_thread is self.trace) + + def test_from_json(self): + trace = satyr.RubyStacktrace.from_json('{}') + self.assertEqual(trace.frames, []) + + json_text = ''' + { + "exception_name": "NotImplementedError", + "stacktrace": [ + { + "file_name": "/usr/share/foobar/mod/file.rb", + "function_name": "do_nothing", + "file_line": 42, + }, + { + "special_function": "top (required)", + "file_line": 451 + }, + { + "unknown_key": 19876543, + "block_level": 42, + "rescue_level": 6 + } + ] + } +''' + trace = satyr.RubyStacktrace.from_json(json_text) + self.assertEqual(trace.exception_name, 'NotImplementedError') + self.assertEqual(len(trace.frames), 3) + + self.assertEqual(trace.frames[0].file_name, '/usr/share/foobar/mod/file.rb') + self.assertEqual(trace.frames[0].function_name, 'do_nothing') + self.assertEqual(trace.frames[0].file_line, 42) + self.assertFalse(trace.frames[0].special_function) + self.assertEqual(trace.frames[0].block_level, 0) + self.assertEqual(trace.frames[0].rescue_level, 0) + + self.assertEqual(trace.frames[1].file_name, None) + self.assertEqual(trace.frames[1].function_name, 'top (required)') + self.assertEqual(trace.frames[1].file_line, 451) + self.assertTrue(trace.frames[1].special_function) + self.assertEqual(trace.frames[1].block_level, 0) + self.assertEqual(trace.frames[1].rescue_level, 0) + + self.assertEqual(trace.frames[2].file_name, None) + self.assertEqual(trace.frames[2].function_name, None) + self.assertEqual(trace.frames[2].file_line, 0) + self.assertFalse(trace.frames[2].special_function) + self.assertEqual(trace.frames[2].block_level, 42) + self.assertEqual(trace.frames[2].rescue_level, 6) + + def test_hash(self): + self.assertHashable(self.trace) + + +class TestRubyFrame(BindingsTestCase): + def setUp(self): + self.frame = satyr.RubyStacktrace(contents).frames[1] + + def test_str(self): + out = str(self.frame) + self.assertEqual(out, "/usr/share/ruby/vendor_ruby/will_crash.rb:10:in `block (2 levels) in func'") + + def test_dup(self): + dup = self.frame.dup() + self.assertEqual(dup.function_name, + self.frame.function_name) + + dup.function_name = 'other' + self.assertNotEqual(dup.function_name, + self.frame.function_name) + + def test_cmp(self): + dup = self.frame.dup() + self.assertTrue(dup.equals(dup)) + self.assertTrue(dup.equals(self.frame)) + self.assertNotEqual(id(dup), id(self.frame)) + dup.function_name = 'another' + self.assertFalse(dup.equals(self.frame)) + + def test_getset(self): + self.assertGetSetCorrect(self.frame, 'file_name', '/usr/share/ruby/vendor_ruby/will_crash.rb', 'java.rs') + self.assertGetSetCorrect(self.frame, 'file_line', 10, 6667) + self.assertGetSetCorrect(self.frame, 'special_function', False, True) + self.assertGetSetCorrect(self.frame, 'function_name', 'func', 'iiiiii') + self.assertGetSetCorrect(self.frame, 'block_level', 2, 666) + self.assertGetSetCorrect(self.frame, 'rescue_level', 0, 9000) + + def test_hash(self): + self.assertHashable(self.frame) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python_bindings.at b/tests/python_bindings.at index a5e19c3..906f561 100644 --- a/tests/python_bindings.at +++ b/tests/python_bindings.at @@ -12,5 +12,6 @@ AT_TEST_PYTHON([koops]) AT_TEST_PYTHON([python]) AT_TEST_PYTHON([java]) AT_TEST_PYTHON([core]) +AT_TEST_PYTHON([ruby]) AT_TEST_PYTHON([metrics]) AT_TEST_PYTHON([report]) -- 1.8.3.1