Blame SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch

01add4
From 28db4ce2840d74f06b55c1e2c7c48f228708fb1d Mon Sep 17 00:00:00 2001
01add4
From: Martin Milata <mmilata@redhat.com>
01add4
Date: Wed, 28 Jan 2015 16:11:15 +0100
01add4
Subject: [PATCH] Add support for Ruby report type
01add4
01add4
Related: #1334604
01add4
01add4
Signed-off-by: Martin Milata <mmilata@redhat.com>
01add4
---
01add4
 include/Makefile.am                          |   5 +
01add4
 include/report_type.h                        |   1 +
01add4
 include/ruby/frame.h                         |  99 ++++++
01add4
 include/ruby/stacktrace.h                    |  73 ++++
01add4
 lib/Makefile.am                              |   2 +
01add4
 lib/abrt.c                                   |  31 ++
01add4
 lib/generic_frame.c                          |   1 +
01add4
 lib/generic_frame.h                          |   3 +-
01add4
 lib/generic_stacktrace.c                     |   1 +
01add4
 lib/generic_stacktrace.h                     |   3 +-
01add4
 lib/generic_thread.c                         |   1 +
01add4
 lib/generic_thread.h                         |   3 +-
01add4
 lib/report.c                                 |   3 +
01add4
 lib/ruby_frame.c                             | 504 +++++++++++++++++++++++++++
01add4
 lib/ruby_stacktrace.c                        | 387 ++++++++++++++++++++
01add4
 tests/Makefile.am                            |   3 +
01add4
 tests/ruby_frame.at                          | 285 +++++++++++++++
01add4
 tests/ruby_stacktrace.at                     | 316 +++++++++++++++++
01add4
 tests/ruby_stacktraces/ruby-01               |  23 ++
01add4
 tests/ruby_stacktraces/ruby-01-expected-json |  97 ++++++
01add4
 tests/ruby_stacktraces/ruby-02               |  37 ++
01add4
 tests/ruby_stacktraces/ruby-02-expected-json | 158 +++++++++
01add4
 tests/ruby_stacktraces/ruby-03               |   4 +
01add4
 tests/ruby_stacktraces/ruby-03-expected-json |  19 +
01add4
 tests/ruby_stacktraces/ruby-04               |  39 +++
01add4
 tests/ruby_stacktraces/ruby-04-expected-json | 166 +++++++++
01add4
 tests/testsuite.at                           |   2 +
01add4
 27 files changed, 2263 insertions(+), 3 deletions(-)
01add4
 create mode 100644 include/ruby/frame.h
01add4
 create mode 100644 include/ruby/stacktrace.h
01add4
 create mode 100644 lib/ruby_frame.c
01add4
 create mode 100644 lib/ruby_stacktrace.c
01add4
 create mode 100644 tests/ruby_frame.at
01add4
 create mode 100644 tests/ruby_stacktrace.at
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-01
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-01-expected-json
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-02
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-02-expected-json
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-03
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-03-expected-json
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-04
01add4
 create mode 100644 tests/ruby_stacktraces/ruby-04-expected-json
01add4
01add4
diff --git a/include/Makefile.am b/include/Makefile.am
01add4
index f72ec66..bcffe31 100644
01add4
--- a/include/Makefile.am
01add4
+++ b/include/Makefile.am
01add4
@@ -49,3 +49,8 @@ pythonheadersdir = $(includedir)/satyr/python
01add4
 pythonheaders_HEADERS = \
01add4
 	python/frame.h \
01add4
 	python/stacktrace.h
01add4
+
01add4
+rubyheadersdir = $(includedir)/satyr/ruby
01add4
+rubyheaders_HEADERS = \
01add4
+	ruby/frame.h \
01add4
+	ruby/stacktrace.h
01add4
diff --git a/include/report_type.h b/include/report_type.h
01add4
index a51620d..0d411ee 100644
01add4
--- a/include/report_type.h
01add4
+++ b/include/report_type.h
01add4
@@ -32,6 +32,7 @@ enum sr_report_type
01add4
     SR_REPORT_KERNELOOPS,
01add4
     SR_REPORT_JAVA,
01add4
     SR_REPORT_GDB,
01add4
+    SR_REPORT_RUBY,
01add4
 
01add4
     /* Keep this the last entry. */
01add4
     SR_REPORT_NUM
01add4
diff --git a/include/ruby/frame.h b/include/ruby/frame.h
01add4
new file mode 100644
01add4
index 0000000..ffc3c67
01add4
--- /dev/null
01add4
+++ b/include/ruby/frame.h
01add4
@@ -0,0 +1,99 @@
01add4
+/*
01add4
+    ruby_frame.h
01add4
+
01add4
+    Copyright (C) 2015  ABRT Team
01add4
+    Copyright (C) 2015  Red Hat, Inc.
01add4
+
01add4
+    This program is free software; you can redistribute it and/or modify
01add4
+    it under the terms of the GNU General Public License as published by
01add4
+    the Free Software Foundation; either version 2 of the License, or
01add4
+    (at your option) any later version.
01add4
+
01add4
+    This program is distributed in the hope that it will be useful,
01add4
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
01add4
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
01add4
+    GNU General Public License for more details.
01add4
+
01add4
+    You should have received a copy of the GNU General Public License along
01add4
+    with this program; if not, write to the Free Software Foundation, Inc.,
01add4
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
01add4
+*/
01add4
+#ifndef SATYR_RUBY_FRAME_H
01add4
+#define SATYR_RUBY_FRAME_H
01add4
+
01add4
+/**
01add4
+ * @file
01add4
+ * @brief Ruby frame structure and related routines.
01add4
+ */
01add4
+
01add4
+#ifdef __cplusplus
01add4
+extern "C" {
01add4
+#endif
01add4
+
01add4
+#include "../report_type.h"
01add4
+#include <stdbool.h>
01add4
+#include <stdint.h>
01add4
+
01add4
+struct sr_location;
01add4
+struct sr_strbuf;
01add4
+struct sr_json_value;
01add4
+
01add4
+struct sr_ruby_frame
01add4
+{
01add4
+    enum sr_report_type type;
01add4
+
01add4
+    char *file_name;
01add4
+
01add4
+    uint32_t file_line;
01add4
+
01add4
+    bool special_function;
01add4
+
01add4
+    char *function_name;
01add4
+
01add4
+    uint32_t block_level;
01add4
+
01add4
+    uint32_t rescue_level;
01add4
+
01add4
+    struct sr_ruby_frame *next;
01add4
+};
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_new();
01add4
+
01add4
+void
01add4
+sr_ruby_frame_init(struct sr_ruby_frame *frame);
01add4
+
01add4
+void
01add4
+sr_ruby_frame_free(struct sr_ruby_frame *frame);
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_dup(struct sr_ruby_frame *frame, bool siblings);
01add4
+
01add4
+int
01add4
+sr_ruby_frame_cmp(struct sr_ruby_frame *frame1, struct sr_ruby_frame *frame2);
01add4
+
01add4
+int
01add4
+sr_ruby_frame_cmp_distance(struct sr_ruby_frame *frame1,
01add4
+                           struct sr_ruby_frame *frame2);
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_append(struct sr_ruby_frame *dest,
01add4
+                     struct sr_ruby_frame *item);
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_parse(const char **input, struct sr_location *location);
01add4
+
01add4
+char *
01add4
+sr_ruby_frame_to_json(struct sr_ruby_frame *frame);
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_from_json(struct sr_json_value *root, char **error_message);
01add4
+
01add4
+void
01add4
+sr_ruby_frame_append_to_str(struct sr_ruby_frame *frame, struct sr_strbuf *dest);
01add4
+
01add4
+#ifdef __cplusplus
01add4
+}
01add4
+#endif
01add4
+
01add4
+#endif
01add4
diff --git a/include/ruby/stacktrace.h b/include/ruby/stacktrace.h
01add4
new file mode 100644
01add4
index 0000000..caa3e0b
01add4
--- /dev/null
01add4
+++ b/include/ruby/stacktrace.h
01add4
@@ -0,0 +1,73 @@
01add4
+/*
01add4
+    ruby_stacktrace.h
01add4
+
01add4
+    Copyright (C) 2015  ABRT Team
01add4
+    Copyright (C) 2015  Red Hat, Inc.
01add4
+
01add4
+    This program is free software; you can redistribute it and/or modify
01add4
+    it under the terms of the GNU General Public License as published by
01add4
+    the Free Software Foundation; either version 2 of the License, or
01add4
+    (at your option) any later version.
01add4
+
01add4
+    This program is distributed in the hope that it will be useful,
01add4
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
01add4
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
01add4
+    GNU General Public License for more details.
01add4
+
01add4
+    You should have received a copy of the GNU General Public License along
01add4
+    with this program; if not, write to the Free Software Foundation, Inc.,
01add4
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
01add4
+*/
01add4
+#ifndef SATYR_RUBY_STACKTRACE_H
01add4
+#define SATYR_RUBY_STACKTRACE_H
01add4
+
01add4
+#ifdef __cplusplus
01add4
+extern "C" {
01add4
+#endif
01add4
+
01add4
+#include "../report_type.h"
01add4
+#include <stdint.h>
01add4
+
01add4
+struct sr_ruby_frame;
01add4
+struct sr_location;
01add4
+struct sr_json_value;
01add4
+
01add4
+struct sr_ruby_stacktrace
01add4
+{
01add4
+    enum sr_report_type type;
01add4
+
01add4
+    char *exception_name;
01add4
+
01add4
+    struct sr_ruby_frame *frames;
01add4
+};
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_new();
01add4
+
01add4
+void
01add4
+sr_ruby_stacktrace_init(struct sr_ruby_stacktrace *stacktrace);
01add4
+
01add4
+void
01add4
+sr_ruby_stacktrace_free(struct sr_ruby_stacktrace *stacktrace);
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_dup(struct sr_ruby_stacktrace *stacktrace);
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_parse(const char **input,
01add4
+                         struct sr_location *location);
01add4
+
01add4
+char *
01add4
+sr_ruby_stacktrace_get_reason(struct sr_ruby_stacktrace *stacktrace);
01add4
+
01add4
+char *
01add4
+sr_ruby_stacktrace_to_json(struct sr_ruby_stacktrace *stacktrace);
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_from_json(struct sr_json_value *root, char **error_message);
01add4
+
01add4
+#ifdef __cplusplus
01add4
+}
01add4
+#endif
01add4
+
01add4
+#endif
01add4
diff --git a/lib/Makefile.am b/lib/Makefile.am
01add4
index f798347..26016f2 100644
01add4
--- a/lib/Makefile.am
01add4
+++ b/lib/Makefile.am
01add4
@@ -57,6 +57,8 @@ libsatyr_conv_la_SOURCES = \
01add4
 	python_stacktrace.c \
01add4
 	report.c \
01add4
 	rpm.c \
01add4
+	ruby_frame.c \
01add4
+	ruby_stacktrace.c \
01add4
 	sha1.c \
01add4
 	strbuf.c \
01add4
 	unstrip.c \
01add4
diff --git a/lib/abrt.c b/lib/abrt.c
01add4
index 4599e2c..a07d44a 100644
01add4
--- a/lib/abrt.c
01add4
+++ b/lib/abrt.c
01add4
@@ -30,6 +30,7 @@
01add4
 #include "python/stacktrace.h"
01add4
 #include "koops/stacktrace.h"
01add4
 #include "java/stacktrace.h"
01add4
+#include "ruby/stacktrace.h"
01add4
 #include "json.h"
01add4
 #include "location.h"
01add4
 #include <stdio.h>
01add4
@@ -567,6 +568,34 @@ sr_abrt_report_from_dir(const char *directory,
01add4
         }
01add4
     }
01add4
 
01add4
+    /* Ruby stacktrace. */
01add4
+    if (report->report_type == SR_REPORT_RUBY)
01add4
+    {
01add4
+        char *backtrace_contents = file_contents(directory, "backtrace",
01add4
+                                                 error_message);
01add4
+        if (!backtrace_contents)
01add4
+        {
01add4
+            sr_report_free(report);
01add4
+            return NULL;
01add4
+        }
01add4
+
01add4
+        /* Parse the Ruby stacktrace. */
01add4
+        struct sr_location location;
01add4
+        sr_location_init(&location);
01add4
+        const char *contents_pointer = backtrace_contents;
01add4
+        report->stacktrace = (struct sr_stacktrace *)sr_ruby_stacktrace_parse(
01add4
+            &contents_pointer,
01add4
+            &location);
01add4
+
01add4
+        free(backtrace_contents);
01add4
+        if (!report->stacktrace)
01add4
+        {
01add4
+            *error_message = sr_location_to_string(&location);
01add4
+            sr_report_free(report);
01add4
+            return NULL;
01add4
+        }
01add4
+    }
01add4
+
01add4
     return report;
01add4
 }
01add4
 
01add4
@@ -581,6 +610,8 @@ sr_abrt_type_from_analyzer(const char *analyzer)
01add4
         return SR_REPORT_KERNELOOPS;
01add4
     else if (0 == strncmp(analyzer, "Java", 4))
01add4
         return SR_REPORT_JAVA;
01add4
+    else if (0 == strncmp(analyzer, "Ruby", 4))
01add4
+        return SR_REPORT_RUBY;
01add4
 
01add4
     return SR_REPORT_INVALID;
01add4
 }
01add4
diff --git a/lib/generic_frame.c b/lib/generic_frame.c
01add4
index eb644b5..d5512c9 100644
01add4
--- a/lib/generic_frame.c
01add4
+++ b/lib/generic_frame.c
01add4
@@ -34,6 +34,7 @@ static struct frame_methods* dtable[SR_REPORT_NUM] =
01add4
     [SR_REPORT_KERNELOOPS] = &koops_frame_methods,
01add4
     [SR_REPORT_JAVA] = &java_frame_methods,
01add4
     [SR_REPORT_GDB] = &gdb_frame_methods,
01add4
+    [SR_REPORT_RUBY] = &ruby_frame_methods,
01add4
 };
01add4
 
01add4
 void
01add4
diff --git a/lib/generic_frame.h b/lib/generic_frame.h
01add4
index a61626d..e9f392d 100644
01add4
--- a/lib/generic_frame.h
01add4
+++ b/lib/generic_frame.h
01add4
@@ -48,7 +48,8 @@ struct frame_methods
01add4
 };
01add4
 
01add4
 extern struct frame_methods core_frame_methods, python_frame_methods,
01add4
-       koops_frame_methods, gdb_frame_methods, java_frame_methods;
01add4
+       koops_frame_methods, gdb_frame_methods, java_frame_methods,
01add4
+       ruby_frame_methods;
01add4
 
01add4
 void
01add4
 frame_append_bthash_text(struct sr_frame *frame, enum sr_bthash_flags flags,
01add4
diff --git a/lib/generic_stacktrace.c b/lib/generic_stacktrace.c
01add4
index feb1dbc..f98a482 100644
01add4
--- a/lib/generic_stacktrace.c
01add4
+++ b/lib/generic_stacktrace.c
01add4
@@ -40,6 +40,7 @@ static struct stacktrace_methods* dtable[SR_REPORT_NUM] =
01add4
     [SR_REPORT_KERNELOOPS] = &koops_stacktrace_methods,
01add4
     [SR_REPORT_JAVA] = &java_stacktrace_methods,
01add4
     [SR_REPORT_GDB] = &gdb_stacktrace_methods,
01add4
+    [SR_REPORT_RUBY] = &ruby_stacktrace_methods,
01add4
 };
01add4
 
01add4
 /* In case stacktrace type supports only one thread, return the stacktrace itself. */
01add4
diff --git a/lib/generic_stacktrace.h b/lib/generic_stacktrace.h
01add4
index 8e3d629..6e1a444 100644
01add4
--- a/lib/generic_stacktrace.h
01add4
+++ b/lib/generic_stacktrace.h
01add4
@@ -55,7 +55,8 @@ struct stacktrace_methods
01add4
 };
01add4
 
01add4
 extern struct stacktrace_methods core_stacktrace_methods, python_stacktrace_methods,
01add4
-       koops_stacktrace_methods, gdb_stacktrace_methods, java_stacktrace_methods;
01add4
+       koops_stacktrace_methods, gdb_stacktrace_methods, java_stacktrace_methods,
01add4
+       ruby_stacktrace_methods;
01add4
 
01add4
 /* Macros to generate accessors for the "threads" member. */
01add4
 #define DEFINE_THREADS_FUNC(name, concrete_t) \
01add4
diff --git a/lib/generic_thread.c b/lib/generic_thread.c
01add4
index 59fff1b..16748b5 100644
01add4
--- a/lib/generic_thread.c
01add4
+++ b/lib/generic_thread.c
01add4
@@ -131,6 +131,7 @@ static struct thread_methods* dtable[SR_REPORT_NUM] =
01add4
     [SR_REPORT_KERNELOOPS] = &koops_thread_methods,
01add4
     [SR_REPORT_JAVA] = &java_thread_methods,
01add4
     [SR_REPORT_GDB] = &gdb_thread_methods,
01add4
+    [SR_REPORT_RUBY] = &ruby_thread_methods,
01add4
 };
01add4
 
01add4
 struct sr_frame *
01add4
diff --git a/lib/generic_thread.h b/lib/generic_thread.h
01add4
index 005e5ac..8635ad2 100644
01add4
--- a/lib/generic_thread.h
01add4
+++ b/lib/generic_thread.h
01add4
@@ -56,7 +56,8 @@ struct thread_methods
01add4
 };
01add4
 
01add4
 extern struct thread_methods core_thread_methods, python_thread_methods,
01add4
-       koops_thread_methods, gdb_thread_methods, java_thread_methods;
01add4
+       koops_thread_methods, gdb_thread_methods, java_thread_methods,
01add4
+       ruby_thread_methods;
01add4
 
01add4
 /* Macros to generate accessors for the "frames" member. */
01add4
 #define DEFINE_FRAMES_FUNC(name, concrete_t) \
01add4
diff --git a/lib/report.c b/lib/report.c
01add4
index 411df64..614b213 100644
01add4
--- a/lib/report.c
01add4
+++ b/lib/report.c
01add4
@@ -42,6 +42,7 @@ static char *report_types[] =
01add4
     [SR_REPORT_KERNELOOPS] = "kerneloops",
01add4
     [SR_REPORT_JAVA] = "java",
01add4
     [SR_REPORT_GDB] = "gdb",
01add4
+    [SR_REPORT_RUBY] = "ruby",
01add4
     NULL
01add4
 };
01add4
 
01add4
@@ -189,6 +190,7 @@ sr_report_to_json(struct sr_report *report)
01add4
     case SR_REPORT_PYTHON:
01add4
     case SR_REPORT_KERNELOOPS:
01add4
     case SR_REPORT_JAVA:
01add4
+    case SR_REPORT_RUBY:
01add4
         report_type = sr_report_type_to_string(report->report_type);
01add4
         reason = sr_stacktrace_get_reason(report->stacktrace);
01add4
         break;
01add4
@@ -383,6 +385,7 @@ sr_report_from_json(struct sr_json_value *root, char **error_message)
01add4
         case SR_REPORT_PYTHON:
01add4
         case SR_REPORT_KERNELOOPS:
01add4
         case SR_REPORT_JAVA:
01add4
+        case SR_REPORT_RUBY:
01add4
             report->stacktrace = sr_stacktrace_from_json(report->report_type, problem, error_message);
01add4
             break;
01add4
         default:
01add4
diff --git a/lib/ruby_frame.c b/lib/ruby_frame.c
01add4
new file mode 100644
01add4
index 0000000..9a9a317
01add4
--- /dev/null
01add4
+++ b/lib/ruby_frame.c
01add4
@@ -0,0 +1,504 @@
01add4
+/*
01add4
+    ruby_frame.c
01add4
+
01add4
+    Copyright (C) 2015  Red Hat, Inc.
01add4
+
01add4
+    This program is free software; you can redistribute it and/or modify
01add4
+    it under the terms of the GNU General Public License as published by
01add4
+    the Free Software Foundation; either version 2 of the License, or
01add4
+    (at your option) any later version.
01add4
+
01add4
+    This program is distributed in the hope that it will be useful,
01add4
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
01add4
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
01add4
+    GNU General Public License for more details.
01add4
+
01add4
+    You should have received a copy of the GNU General Public License along
01add4
+    with this program; if not, write to the Free Software Foundation, Inc.,
01add4
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
01add4
+*/
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include "strbuf.h"
01add4
+#include "json.h"
01add4
+#include "generic_frame.h"
01add4
+#include "thread.h"
01add4
+#include "stacktrace.h"
01add4
+#include "internal_utils.h"
01add4
+#include <string.h>
01add4
+#include <inttypes.h>
01add4
+#include <ctype.h>
01add4
+
01add4
+/* Method table */
01add4
+
01add4
+static void
01add4
+ruby_append_bthash_text(struct sr_ruby_frame *frame, enum sr_bthash_flags flags,
01add4
+                          struct sr_strbuf *strbuf);
01add4
+static void
01add4
+ruby_append_duphash_text(struct sr_ruby_frame *frame, enum sr_duphash_flags flags,
01add4
+                           struct sr_strbuf *strbuf);
01add4
+
01add4
+DEFINE_NEXT_FUNC(ruby_next, struct sr_frame, struct sr_ruby_frame)
01add4
+DEFINE_SET_NEXT_FUNC(ruby_set_next, struct sr_frame, struct sr_ruby_frame)
01add4
+
01add4
+struct frame_methods ruby_frame_methods =
01add4
+{
01add4
+    .append_to_str = (append_to_str_fn_t) sr_ruby_frame_append_to_str,
01add4
+    .next = (next_frame_fn_t) ruby_next,
01add4
+    .set_next = (set_next_frame_fn_t) ruby_set_next,
01add4
+    .cmp = (frame_cmp_fn_t) sr_ruby_frame_cmp,
01add4
+    .cmp_distance = (frame_cmp_fn_t) sr_ruby_frame_cmp_distance,
01add4
+    .frame_append_bthash_text =
01add4
+        (frame_append_bthash_text_fn_t) ruby_append_bthash_text,
01add4
+    .frame_append_duphash_text =
01add4
+        (frame_append_duphash_text_fn_t) ruby_append_duphash_text,
01add4
+    .frame_free = (frame_free_fn_t) sr_ruby_frame_free,
01add4
+};
01add4
+
01add4
+/* Public functions */
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_new()
01add4
+{
01add4
+    struct sr_ruby_frame *frame =
01add4
+        sr_malloc(sizeof(struct sr_ruby_frame));
01add4
+
01add4
+    sr_ruby_frame_init(frame);
01add4
+    return frame;
01add4
+}
01add4
+
01add4
+void
01add4
+sr_ruby_frame_init(struct sr_ruby_frame *frame)
01add4
+{
01add4
+    memset(frame, 0, sizeof(struct sr_ruby_frame));
01add4
+    frame->type = SR_REPORT_RUBY;
01add4
+}
01add4
+
01add4
+void
01add4
+sr_ruby_frame_free(struct sr_ruby_frame *frame)
01add4
+{
01add4
+    if (!frame)
01add4
+        return;
01add4
+
01add4
+    free(frame->file_name);
01add4
+    free(frame->function_name);
01add4
+    free(frame);
01add4
+}
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_dup(struct sr_ruby_frame *frame, bool siblings)
01add4
+{
01add4
+    struct sr_ruby_frame *result = sr_ruby_frame_new();
01add4
+    memcpy(result, frame, sizeof(struct sr_ruby_frame));
01add4
+
01add4
+    /* Handle siblings. */
01add4
+    if (siblings)
01add4
+    {
01add4
+        if (result->next)
01add4
+            result->next = sr_ruby_frame_dup(result->next, true);
01add4
+    }
01add4
+    else
01add4
+        result->next = NULL; /* Do not copy that. */
01add4
+
01add4
+    /* Duplicate all strings. */
01add4
+    if (result->file_name)
01add4
+        result->file_name = sr_strdup(result->file_name);
01add4
+
01add4
+    if (result->function_name)
01add4
+        result->function_name = sr_strdup(result->function_name);
01add4
+
01add4
+    return result;
01add4
+}
01add4
+
01add4
+int
01add4
+sr_ruby_frame_cmp(struct sr_ruby_frame *frame1,
01add4
+                  struct sr_ruby_frame *frame2)
01add4
+{
01add4
+    /* function_name */
01add4
+    int function_name = sr_strcmp0(frame1->function_name,
01add4
+                                   frame2->function_name);
01add4
+    if (function_name != 0)
01add4
+        return function_name;
01add4
+
01add4
+    /* file_name */
01add4
+    int file_name = sr_strcmp0(frame1->file_name,
01add4
+                               frame2->file_name);
01add4
+    if (file_name != 0)
01add4
+        return file_name;
01add4
+
01add4
+    /* file_line */
01add4
+    int file_line = frame1->file_line - frame2->file_line;
01add4
+    if (file_line != 0)
01add4
+        return file_line;
01add4
+
01add4
+    /* special_function */
01add4
+    int special_function = frame1->special_function - frame2->special_function;
01add4
+    if (special_function != 0)
01add4
+        return special_function;
01add4
+
01add4
+    /* block_level */
01add4
+    int block_level = frame1->block_level - frame2->block_level;
01add4
+    if (block_level != 0)
01add4
+            return block_level;
01add4
+
01add4
+    /* rescue_level */
01add4
+    int rescue_level = frame1->rescue_level - frame2->rescue_level;
01add4
+    if (rescue_level != 0)
01add4
+            return rescue_level;
01add4
+
01add4
+    return 0;
01add4
+}
01add4
+
01add4
+int
01add4
+sr_ruby_frame_cmp_distance(struct sr_ruby_frame *frame1,
01add4
+                           struct sr_ruby_frame *frame2)
01add4
+{
01add4
+    /* function_name */
01add4
+    int function_name = sr_strcmp0(frame1->function_name,
01add4
+                                   frame2->function_name);
01add4
+    if (function_name != 0)
01add4
+        return function_name;
01add4
+
01add4
+    /* file_name */
01add4
+    int file_name = sr_strcmp0(frame1->file_name,
01add4
+                               frame2->file_name);
01add4
+    if (file_name != 0)
01add4
+        return file_name;
01add4
+
01add4
+    /* special_function */
01add4
+    int special_function = frame1->special_function - frame2->special_function;
01add4
+    if (special_function != 0)
01add4
+        return special_function;
01add4
+
01add4
+    return 0;
01add4
+}
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_append(struct sr_ruby_frame *dest,
01add4
+                     struct sr_ruby_frame *item)
01add4
+{
01add4
+    if (!dest)
01add4
+        return item;
01add4
+
01add4
+    struct sr_ruby_frame *dest_loop = dest;
01add4
+    while (dest_loop->next)
01add4
+        dest_loop = dest_loop->next;
01add4
+
01add4
+    dest_loop->next = item;
01add4
+    return dest;
01add4
+}
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_parse(const char **input,
01add4
+                    struct sr_location *location)
01add4
+{
01add4
+    const char *local_input = *input;
01add4
+    struct sr_ruby_frame *frame = sr_ruby_frame_new();
01add4
+
01add4
+    /* take everything before the backtick
01add4
+     * /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
01add4
+     */
01add4
+    char *filename_lineno_in = NULL;
01add4
+    if (!sr_parse_char_cspan(&local_input, "`", &filename_lineno_in))
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the '`' character "
01add4
+                                      "identifying the beginning of function name.");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    size_t l = strlen(filename_lineno_in);
01add4
+    location->column += l;
01add4
+
01add4
+    char *p = filename_lineno_in + l;
01add4
+
01add4
+    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+     *                                                           ^^^^
01add4
+     */
01add4
+    p -= strlen(":in ");
01add4
+    if (p < filename_lineno_in || 0 != strcmp(":in ", p))
01add4
+    {
01add4
+        location->column -= strlen(":in ");
01add4
+        location->message = sr_strdup("Unable to find ':in ' preceding the "
01add4
+                                      "backtick character.");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* Find the beginning of the line number. */
01add4
+    *p = '\0';
01add4
+    do {
01add4
+        p--;
01add4
+    } while (p >= filename_lineno_in && isdigit(*p));
01add4
+    p++;
01add4
+
01add4
+    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+     *                                                         ^^
01add4
+     */
01add4
+    char *p_copy = p;
01add4
+    int lineno_len = sr_parse_uint32((const char **)&p_copy, &frame->file_line);
01add4
+    if (lineno_len <= 0)
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find line number before ':in '");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+     *                                                        ^
01add4
+     */
01add4
+    p--;
01add4
+    if (p < filename_lineno_in || *p != ':')
01add4
+    {
01add4
+        location->column -= lineno_len;
01add4
+        location->message = sr_strdup("Unable to fin the ':' character "
01add4
+                                      "preceding the line number");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* Everything before the colon is the file name. */
01add4
+    *p = '\0';
01add4
+    frame->file_name = filename_lineno_in;
01add4
+    filename_lineno_in = NULL;
01add4
+
01add4
+    if(!sr_skip_char(&local_input, '`'))
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the '`' character "
01add4
+                                      "identifying the beginning of function name.");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    location->column++;
01add4
+
01add4
+    /* The part in quotes can look like:
01add4
+     * `rescue in rescue in block (3 levels) in func'
01add4
+     * parse the number of rescues and blocks
01add4
+     */
01add4
+    while (sr_skip_string(&local_input, "rescue in "))
01add4
+    {
01add4
+        frame->rescue_level++;
01add4
+        location->column += strlen("rescue in ");
01add4
+    }
01add4
+
01add4
+    if (sr_skip_string(&local_input, "block in "))
01add4
+    {
01add4
+        frame->block_level = 1;
01add4
+        location->column += strlen("block in");
01add4
+    }
01add4
+    else if(sr_skip_string(&local_input, "block ("))
01add4
+    {
01add4
+        location->column += strlen("block (");
01add4
+
01add4
+        int len = sr_parse_uint32(&local_input, &frame->block_level);
01add4
+        if (len == 0 || !sr_skip_string(&local_input, " levels) in "))
01add4
+        {
01add4
+            location->message = sr_strdup("Unable to parse block depth.");
01add4
+            goto fail;
01add4
+        }
01add4
+        location->column += len + strlen(" levels) in ");
01add4
+    }
01add4
+
01add4
+    if (sr_skip_char(&local_input, '<'))
01add4
+    {
01add4
+        location->column++;
01add4
+        frame->special_function = true;
01add4
+    }
01add4
+
01add4
+    if (!sr_parse_char_cspan(&local_input, "'>", &frame->function_name))
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the \"'\" character "
01add4
+                                      "delimiting the function name.");
01add4
+        goto fail;
01add4
+    }
01add4
+    location->column += strlen(frame->function_name);
01add4
+
01add4
+    if (frame->special_function)
01add4
+    {
01add4
+        if (!sr_skip_char(&local_input, '>'))
01add4
+        {
01add4
+            location->message = sr_strdup("Unable to find the \">\" character "
01add4
+                                          "delimiting the function name.");
01add4
+            goto fail;
01add4
+        }
01add4
+        location->column++;
01add4
+    }
01add4
+
01add4
+    if (!sr_skip_char(&local_input, '\''))
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the \"'\" character "
01add4
+                                      "delimiting the function name.");
01add4
+        goto fail;
01add4
+    }
01add4
+    location->column++;
01add4
+
01add4
+    *input = local_input;
01add4
+    return frame;
01add4
+
01add4
+fail:
01add4
+    sr_ruby_frame_free(frame);
01add4
+    free(filename_lineno_in);
01add4
+    return NULL;
01add4
+}
01add4
+
01add4
+char *
01add4
+sr_ruby_frame_to_json(struct sr_ruby_frame *frame)
01add4
+{
01add4
+    struct sr_strbuf *strbuf = sr_strbuf_new();
01add4
+
01add4
+    /* Source file name. */
01add4
+    if (frame->file_name)
01add4
+    {
01add4
+        sr_strbuf_append_str(strbuf, ",   \"file_name\": ");
01add4
+        sr_json_append_escaped(strbuf, frame->file_name);
01add4
+        sr_strbuf_append_str(strbuf, "\n");
01add4
+    }
01add4
+
01add4
+    /* Source file line. */
01add4
+    if (frame->file_line)
01add4
+    {
01add4
+        sr_strbuf_append_strf(strbuf,
01add4
+                              ",   \"file_line\": %"PRIu32"\n",
01add4
+                              frame->file_line);
01add4
+    }
01add4
+
01add4
+    /* Function name / special function. */
01add4
+    if (frame->function_name)
01add4
+    {
01add4
+        if (frame->special_function)
01add4
+            sr_strbuf_append_str(strbuf, ",   \"special_function\": ");
01add4
+        else
01add4
+            sr_strbuf_append_str(strbuf, ",   \"function_name\": ");
01add4
+
01add4
+        sr_json_append_escaped(strbuf, frame->function_name);
01add4
+        sr_strbuf_append_str(strbuf, "\n");
01add4
+    }
01add4
+
01add4
+    /* Block level. */
01add4
+    if (frame->block_level > 0)
01add4
+    {
01add4
+        sr_strbuf_append_strf(strbuf,
01add4
+                              ",   \"block_level\": %"PRIu32"\n",
01add4
+                              frame->block_level);
01add4
+    }
01add4
+
01add4
+    /* Rescue level. */
01add4
+    if (frame->rescue_level > 0)
01add4
+    {
01add4
+        sr_strbuf_append_strf(strbuf,
01add4
+                              ",   \"rescue_level\": %"PRIu32"\n",
01add4
+                              frame->rescue_level);
01add4
+    }
01add4
+
01add4
+
01add4
+    strbuf->buf[0] = '{';
01add4
+    sr_strbuf_append_char(strbuf, '}');
01add4
+    return sr_strbuf_free_nobuf(strbuf);
01add4
+}
01add4
+
01add4
+struct sr_ruby_frame *
01add4
+sr_ruby_frame_from_json(struct sr_json_value *root, char **error_message)
01add4
+{
01add4
+    if (!JSON_CHECK_TYPE(root, SR_JSON_OBJECT, "frame"))
01add4
+        return NULL;
01add4
+
01add4
+    struct sr_ruby_frame *result = sr_ruby_frame_new();
01add4
+    struct sr_json_value *val;
01add4
+
01add4
+    /* Source file name */
01add4
+    if ((val = json_element(root, "file_name")))
01add4
+    {
01add4
+        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "file_name"))
01add4
+            goto fail;
01add4
+
01add4
+        result->file_name = sr_strdup(val->u.string.ptr);
01add4
+    }
01add4
+
01add4
+    /* Function name / special function. */
01add4
+    if ((val = json_element(root, "function_name")))
01add4
+    {
01add4
+        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "function_name"))
01add4
+            goto fail;
01add4
+
01add4
+        result->special_function = false;
01add4
+        result->function_name = sr_strdup(val->u.string.ptr);
01add4
+    }
01add4
+    else if ((val = json_element(root, "special_function")))
01add4
+    {
01add4
+        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "special_function"))
01add4
+            goto fail;
01add4
+
01add4
+        result->special_function = true;
01add4
+        result->function_name = sr_strdup(val->u.string.ptr);
01add4
+    }
01add4
+
01add4
+    bool success =
01add4
+        JSON_READ_UINT32(root, "file_line", &result->file_line) &&
01add4
+        JSON_READ_UINT32(root, "block_level", &result->block_level) &&
01add4
+        JSON_READ_UINT32(root, "rescue_level", &result->rescue_level);
01add4
+
01add4
+    if (!success)
01add4
+        goto fail;
01add4
+
01add4
+    return result;
01add4
+
01add4
+fail:
01add4
+    sr_ruby_frame_free(result);
01add4
+    return NULL;
01add4
+}
01add4
+
01add4
+void
01add4
+sr_ruby_frame_append_to_str(struct sr_ruby_frame *frame,
01add4
+                            struct sr_strbuf *dest)
01add4
+{
01add4
+    for (int i = 0; i < frame->rescue_level; i++)
01add4
+    {
01add4
+        sr_strbuf_append_str(dest, "rescue in ");
01add4
+    }
01add4
+
01add4
+    if (frame->block_level == 1)
01add4
+    {
01add4
+        sr_strbuf_append_str(dest, "block in ");
01add4
+    }
01add4
+    else if (frame->block_level > 1)
01add4
+    {
01add4
+        sr_strbuf_append_strf(dest, "block (%u levels) in ", (unsigned)frame->block_level);
01add4
+    }
01add4
+
01add4
+    sr_strbuf_append_strf(dest, "%s%s%s",
01add4
+                          (frame->special_function ? "<" : ""),
01add4
+                          (frame->function_name ? frame->function_name : "??"),
01add4
+                          (frame->special_function ? ">" : ""));
01add4
+
01add4
+    if (frame->file_name)
01add4
+    {
01add4
+        sr_strbuf_append_strf(dest, " in %s", frame->file_name);
01add4
+
01add4
+        if (frame->file_line)
01add4
+        {
01add4
+            sr_strbuf_append_strf(dest, ":%d", frame->file_line);
01add4
+        }
01add4
+    }
01add4
+}
01add4
+
01add4
+static void
01add4
+ruby_append_bthash_text(struct sr_ruby_frame *frame, enum sr_bthash_flags flags,
01add4
+                        struct sr_strbuf *strbuf)
01add4
+{
01add4
+    sr_strbuf_append_strf(strbuf,
01add4
+                          "%s, %"PRIu32", %s, %d, %"PRIu32", %"PRIu32"\n",
01add4
+                          OR_UNKNOWN(frame->file_name),
01add4
+                          frame->file_line,
01add4
+                          OR_UNKNOWN(frame->function_name),
01add4
+                          frame->special_function,
01add4
+                          frame->block_level,
01add4
+                          frame->rescue_level);
01add4
+}
01add4
+
01add4
+static void
01add4
+ruby_append_duphash_text(struct sr_ruby_frame *frame, enum sr_duphash_flags flags,
01add4
+                         struct sr_strbuf *strbuf)
01add4
+{
01add4
+    /* filename:line */
01add4
+    sr_strbuf_append_strf(strbuf, "%s:%"PRIu32"\n",
01add4
+                          OR_UNKNOWN(frame->file_name),
01add4
+                          frame->file_line);
01add4
+}
01add4
diff --git a/lib/ruby_stacktrace.c b/lib/ruby_stacktrace.c
01add4
new file mode 100644
01add4
index 0000000..4adde08
01add4
--- /dev/null
01add4
+++ b/lib/ruby_stacktrace.c
01add4
@@ -0,0 +1,387 @@
01add4
+/*
01add4
+    ruby_stacktrace.c
01add4
+
01add4
+    Copyright (C) 2015  Red Hat, Inc.
01add4
+
01add4
+    This program is free software; you can redistribute it and/or modify
01add4
+    it under the terms of the GNU General Public License as published by
01add4
+    the Free Software Foundation; either version 2 of the License, or
01add4
+    (at your option) any later version.
01add4
+
01add4
+    This program is distributed in the hope that it will be useful,
01add4
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
01add4
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
01add4
+    GNU General Public License for more details.
01add4
+
01add4
+    You should have received a copy of the GNU General Public License along
01add4
+    with this program; if not, write to the Free Software Foundation, Inc.,
01add4
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
01add4
+*/
01add4
+#include "ruby/stacktrace.h"
01add4
+#include "ruby/frame.h"
01add4
+#include "location.h"
01add4
+#include "utils.h"
01add4
+#include "json.h"
01add4
+#include "sha1.h"
01add4
+#include "report_type.h"
01add4
+#include "strbuf.h"
01add4
+#include "generic_stacktrace.h"
01add4
+#include "generic_thread.h"
01add4
+#include "internal_utils.h"
01add4
+#include <stdio.h>
01add4
+#include <stdlib.h>
01add4
+#include <string.h>
01add4
+#include <inttypes.h>
01add4
+
01add4
+/* Method tables */
01add4
+
01add4
+static void
01add4
+ruby_append_bthash_text(struct sr_ruby_stacktrace *stacktrace, enum sr_bthash_flags flags,
01add4
+                          struct sr_strbuf *strbuf);
01add4
+
01add4
+DEFINE_FRAMES_FUNC(ruby_frames, struct sr_ruby_stacktrace)
01add4
+DEFINE_SET_FRAMES_FUNC(ruby_set_frames, struct sr_ruby_stacktrace)
01add4
+DEFINE_PARSE_WRAPPER_FUNC(ruby_parse, SR_REPORT_RUBY)
01add4
+
01add4
+struct thread_methods ruby_thread_methods =
01add4
+{
01add4
+    .frames = (frames_fn_t) ruby_frames,
01add4
+    .set_frames = (set_frames_fn_t) ruby_set_frames,
01add4
+    .cmp = (thread_cmp_fn_t) NULL,
01add4
+    .frame_count = (frame_count_fn_t) thread_frame_count,
01add4
+    .next = (next_thread_fn_t) thread_no_next_thread,
01add4
+    .set_next = (set_next_thread_fn_t) NULL,
01add4
+    .thread_append_bthash_text =
01add4
+        (thread_append_bthash_text_fn_t) thread_no_bthash_text,
01add4
+    .thread_free = (thread_free_fn_t) sr_ruby_stacktrace_free,
01add4
+    .remove_frame = (remove_frame_fn_t) thread_remove_frame,
01add4
+    .remove_frames_above =
01add4
+        (remove_frames_above_fn_t) thread_remove_frames_above,
01add4
+    .thread_dup = (thread_dup_fn_t) sr_ruby_stacktrace_dup,
01add4
+    .normalize = (normalize_fn_t) thread_no_normalization,
01add4
+};
01add4
+
01add4
+struct stacktrace_methods ruby_stacktrace_methods =
01add4
+{
01add4
+    .parse = (parse_fn_t) ruby_parse,
01add4
+    .parse_location = (parse_location_fn_t) sr_ruby_stacktrace_parse,
01add4
+    .to_short_text = (to_short_text_fn_t) stacktrace_to_short_text,
01add4
+    .to_json = (to_json_fn_t) sr_ruby_stacktrace_to_json,
01add4
+    .from_json = (from_json_fn_t) sr_ruby_stacktrace_from_json,
01add4
+    .get_reason = (get_reason_fn_t) sr_ruby_stacktrace_get_reason,
01add4
+    .find_crash_thread = (find_crash_thread_fn_t) stacktrace_one_thread_only,
01add4
+    .threads = (threads_fn_t) stacktrace_one_thread_only,
01add4
+    .set_threads = (set_threads_fn_t) NULL,
01add4
+    .stacktrace_free = (stacktrace_free_fn_t) sr_ruby_stacktrace_free,
01add4
+    .stacktrace_append_bthash_text =
01add4
+        (stacktrace_append_bthash_text_fn_t) ruby_append_bthash_text,
01add4
+};
01add4
+
01add4
+/* Public functions */
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_new()
01add4
+{
01add4
+    struct sr_ruby_stacktrace *stacktrace =
01add4
+        sr_malloc(sizeof(struct sr_ruby_stacktrace));
01add4
+
01add4
+    sr_ruby_stacktrace_init(stacktrace);
01add4
+    return stacktrace;
01add4
+}
01add4
+
01add4
+void
01add4
+sr_ruby_stacktrace_init(struct sr_ruby_stacktrace *stacktrace)
01add4
+{
01add4
+    memset(stacktrace, 0, sizeof(struct sr_ruby_stacktrace));
01add4
+    stacktrace->type = SR_REPORT_RUBY;
01add4
+}
01add4
+
01add4
+void
01add4
+sr_ruby_stacktrace_free(struct sr_ruby_stacktrace *stacktrace)
01add4
+{
01add4
+    if (!stacktrace)
01add4
+        return;
01add4
+
01add4
+    while (stacktrace->frames)
01add4
+    {
01add4
+        struct sr_ruby_frame *frame = stacktrace->frames;
01add4
+        stacktrace->frames = frame->next;
01add4
+        sr_ruby_frame_free(frame);
01add4
+    }
01add4
+
01add4
+    free(stacktrace->exception_name);
01add4
+    free(stacktrace);
01add4
+}
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_dup(struct sr_ruby_stacktrace *stacktrace)
01add4
+{
01add4
+    struct sr_ruby_stacktrace *result = sr_ruby_stacktrace_new();
01add4
+    memcpy(result, stacktrace, sizeof(struct sr_ruby_stacktrace));
01add4
+
01add4
+    if (result->exception_name)
01add4
+        result->exception_name = sr_strdup(result->exception_name);
01add4
+
01add4
+    if (result->frames)
01add4
+        result->frames = sr_ruby_frame_dup(result->frames, true);
01add4
+
01add4
+    return result;
01add4
+}
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_parse(const char **input,
01add4
+                         struct sr_location *location)
01add4
+{
01add4
+    const char *local_input = *input;
01add4
+    struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_new();
01add4
+    char *message_and_class = NULL;
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
01add4
+     */
01add4
+    stacktrace->frames = sr_ruby_frame_parse(&local_input, location);
01add4
+    if (!stacktrace->frames)
01add4
+    {
01add4
+        location->message = sr_asprintf("Topmost stacktrace frame not found: %s",
01add4
+                            location->message ? location->message : "(unknown reason)");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                              ^^
01add4
+     */
01add4
+    if (!sr_skip_string(&local_input, ": "))
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the colon after first function name.");
01add4
+        goto fail;
01add4
+    }
01add4
+    location->column += strlen(": ");
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
01add4
+     */
01add4
+    if (!sr_parse_char_cspan(&local_input, "\t", &message_and_class))
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the exception type and message.");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                                                                    ^^
01add4
+     */
01add4
+    size_t l = strlen(message_and_class);
01add4
+    location->column += l;
01add4
+
01add4
+    char *p = message_and_class + l - 1;
01add4
+    if (p < message_and_class || *p != '\n')
01add4
+    {
01add4
+        location->column--;
01add4
+        location->message = sr_strdup("Unable to find the new line character after "
01add4
+                                      "the end of exception class");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                                                                   ^
01add4
+     */
01add4
+    p--;
01add4
+    if (p < message_and_class || *p != ')')
01add4
+    {
01add4
+        location->column -= 2;
01add4
+        location->message = sr_strdup("Unable to find the ')' character identifying "
01add4
+                                      "the end of exception class");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                                                   ^^^^^^^^^^^^^^^^
01add4
+     */
01add4
+    *p = '\0';
01add4
+    do {
01add4
+        p--;
01add4
+    } while (p >= message_and_class && *p != '(');
01add4
+    p++;
01add4
+
01add4
+    if (strlen(p) <= 0)
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the '(' character identifying "
01add4
+                                      "the beginning of the exception class");
01add4
+        goto fail;
01add4
+    }
01add4
+    stacktrace->exception_name = sr_strdup(p);
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                                                  ^
01add4
+     */
01add4
+    p--;
01add4
+    if (p < message_and_class || *p != '(')
01add4
+    {
01add4
+        location->message = sr_strdup("Unable to find the '(' character identifying "
01add4
+                                      "the beginning of the exception class");
01add4
+        goto fail;
01add4
+    }
01add4
+
01add4
+    /* Throw away the message, it may contain sensitive data. */
01add4
+    free(message_and_class);
01add4
+    message_and_class = p = NULL;
01add4
+
01add4
+    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
01add4
+     *                                   we're here now, increment the line ^^
01add4
+     */
01add4
+    location->column = 0;
01add4
+    location->line++;
01add4
+
01add4
+    struct sr_ruby_frame *last_frame = stacktrace->frames;
01add4
+    while (*local_input)
01add4
+    {
01add4
+        /* The exception message can continue on the lines after the topmost frame
01add4
+         * - skip those.
01add4
+         */
01add4
+        int skipped = sr_skip_string(&local_input, "\tfrom ");
01add4
+        if (!skipped)
01add4
+        {
01add4
+            location->message = sr_strdup("Frame header not found.");
01add4
+            goto fail;
01add4
+        }
01add4
+        location->column += skipped;
01add4
+
01add4
+        last_frame->next = sr_ruby_frame_parse(&local_input, location);
01add4
+        if (!last_frame->next)
01add4
+        {
01add4
+            /* location->message is already set */
01add4
+            goto fail;
01add4
+        }
01add4
+
01add4
+        /* Eat newline (except at the end of file). */
01add4
+        if (!sr_skip_char(&local_input, '\n') && *local_input != '\0')
01add4
+        {
01add4
+            location->message = sr_strdup("Expected newline after stacktrace frame.");
01add4
+            goto fail;
01add4
+        }
01add4
+        location->column = 0;
01add4
+        location->line++;
01add4
+        last_frame = last_frame->next;
01add4
+    }
01add4
+
01add4
+    *input = local_input;
01add4
+    return stacktrace;
01add4
+
01add4
+fail:
01add4
+    sr_ruby_stacktrace_free(stacktrace);
01add4
+    free(message_and_class);
01add4
+    return NULL;
01add4
+}
01add4
+
01add4
+char *
01add4
+sr_ruby_stacktrace_to_json(struct sr_ruby_stacktrace *stacktrace)
01add4
+{
01add4
+    struct sr_strbuf *strbuf = sr_strbuf_new();
01add4
+
01add4
+    /* Exception class name. */
01add4
+    if (stacktrace->exception_name)
01add4
+    {
01add4
+        sr_strbuf_append_str(strbuf, ",   \"exception_name\": ");
01add4
+        sr_json_append_escaped(strbuf, stacktrace->exception_name);
01add4
+        sr_strbuf_append_str(strbuf, "\n");
01add4
+    }
01add4
+
01add4
+    /* Frames. */
01add4
+    if (stacktrace->frames)
01add4
+    {
01add4
+        struct sr_ruby_frame *frame = stacktrace->frames;
01add4
+        sr_strbuf_append_str(strbuf, ",   \"stacktrace\":\n");
01add4
+        while (frame)
01add4
+        {
01add4
+            if (frame == stacktrace->frames)
01add4
+                sr_strbuf_append_str(strbuf, "      [ ");
01add4
+            else
01add4
+                sr_strbuf_append_str(strbuf, "      , ");
01add4
+
01add4
+            char *frame_json = sr_ruby_frame_to_json(frame);
01add4
+            char *indented_frame_json = sr_indent_except_first_line(frame_json, 8);
01add4
+            sr_strbuf_append_str(strbuf, indented_frame_json);
01add4
+            free(indented_frame_json);
01add4
+            free(frame_json);
01add4
+            frame = frame->next;
01add4
+            if (frame)
01add4
+                sr_strbuf_append_str(strbuf, "\n");
01add4
+        }
01add4
+
01add4
+        sr_strbuf_append_str(strbuf, " ]\n");
01add4
+    }
01add4
+
01add4
+    if (strbuf->len > 0)
01add4
+        strbuf->buf[0] = '{';
01add4
+    else
01add4
+        sr_strbuf_append_char(strbuf, '{');
01add4
+
01add4
+    sr_strbuf_append_char(strbuf, '}');
01add4
+    return sr_strbuf_free_nobuf(strbuf);
01add4
+}
01add4
+
01add4
+struct sr_ruby_stacktrace *
01add4
+sr_ruby_stacktrace_from_json(struct sr_json_value *root, char **error_message)
01add4
+{
01add4
+    if (!JSON_CHECK_TYPE(root, SR_JSON_OBJECT, "stacktrace"))
01add4
+        return NULL;
01add4
+
01add4
+    struct sr_ruby_stacktrace *result = sr_ruby_stacktrace_new();
01add4
+
01add4
+    /* Exception name. */
01add4
+    if (!JSON_READ_STRING(root, "exception_name", &result->exception_name))
01add4
+        goto fail;
01add4
+
01add4
+    /* Frames. */
01add4
+    struct sr_json_value *stacktrace = json_element(root, "stacktrace");
01add4
+    if (stacktrace)
01add4
+    {
01add4
+        if (!JSON_CHECK_TYPE(stacktrace, SR_JSON_ARRAY, "stacktrace"))
01add4
+            goto fail;
01add4
+
01add4
+        struct sr_json_value *frame_json;
01add4
+        FOR_JSON_ARRAY(stacktrace, frame_json)
01add4
+        {
01add4
+            struct sr_ruby_frame *frame = sr_ruby_frame_from_json(frame_json,
01add4
+                error_message);
01add4
+
01add4
+            if (!frame)
01add4
+                goto fail;
01add4
+
01add4
+            result->frames = sr_ruby_frame_append(result->frames, frame);
01add4
+        }
01add4
+    }
01add4
+
01add4
+    return result;
01add4
+
01add4
+fail:
01add4
+    sr_ruby_stacktrace_free(result);
01add4
+    return NULL;
01add4
+}
01add4
+
01add4
+char *
01add4
+sr_ruby_stacktrace_get_reason(struct sr_ruby_stacktrace *stacktrace)
01add4
+{
01add4
+    char *exc = "Unknown error";
01add4
+    char *file = "<unknown>";
01add4
+    uint32_t line = 0;
01add4
+
01add4
+    struct sr_ruby_frame *frame = stacktrace->frames;
01add4
+    if (frame)
01add4
+    {
01add4
+        file = frame->file_name;
01add4
+        line = frame->file_line;
01add4
+    }
01add4
+
01add4
+    if (stacktrace->exception_name)
01add4
+        exc = stacktrace->exception_name;
01add4
+
01add4
+    return sr_asprintf("%s in %s:%"PRIu32, exc, file, line);
01add4
+}
01add4
+
01add4
+static void
01add4
+ruby_append_bthash_text(struct sr_ruby_stacktrace *stacktrace, enum sr_bthash_flags flags,
01add4
+                        struct sr_strbuf *strbuf)
01add4
+{
01add4
+    sr_strbuf_append_strf(strbuf, "Exception: %s\n", OR_UNKNOWN(stacktrace->exception_name));
01add4
+    sr_strbuf_append_char(strbuf, '\n');
01add4
+}
01add4
diff --git a/tests/Makefile.am b/tests/Makefile.am
01add4
index d69f493..0eb33a5 100644
01add4
--- a/tests/Makefile.am
01add4
+++ b/tests/Makefile.am
01add4
@@ -1,5 +1,6 @@
01add4
 EXTRA_DIST = gdb_stacktraces \
01add4
              java_stacktraces \
01add4
+             ruby_stacktraces \
01add4
              json_files \
01add4
              kerneloopses \
01add4
              programs \
01add4
@@ -50,6 +51,8 @@ TESTSUITE_AT =		\
01add4
   core_frame.at         \
01add4
   core_thread.at        \
01add4
   core_stacktrace.at    \
01add4
+  ruby_frame.at		\
01add4
+  ruby_stacktrace.at	\
01add4
   normalize.at  	\
01add4
   metrics.at 		\
01add4
   cluster.at  		\
01add4
diff --git a/tests/ruby_frame.at b/tests/ruby_frame.at
01add4
new file mode 100644
01add4
index 0000000..1b1b9d0
01add4
--- /dev/null
01add4
+++ b/tests/ruby_frame.at
01add4
@@ -0,0 +1,285 @@
01add4
+# Checking the satyr. -*- Autotest -*-
01add4
+
01add4
+AT_BANNER([Ruby frames])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_parse],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+
01add4
+void
01add4
+check(char *input, char *file, unsigned line,
01add4
+      char *function, bool special, unsigned block_level, unsigned rescue_level)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+
01add4
+  struct sr_ruby_frame *frame;
01add4
+  frame = sr_ruby_frame_parse(&input, &location);
01add4
+
01add4
+  assert(frame);
01add4
+  assert(frame->file_name && 0 == strcmp(frame->file_name, file));
01add4
+  assert(frame->file_line == line);
01add4
+  assert(frame->function_name && 0 == strcmp(frame->function_name, function));
01add4
+  assert(!!frame->special_function == !!special);
01add4
+  assert(frame->block_level == block_level);
01add4
+  assert(frame->rescue_level == rescue_level);
01add4
+  assert(*input == '\0');
01add4
+
01add4
+  sr_ruby_frame_free(frame);
01add4
+}
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  check("/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'",
01add4
+        "/usr/share/ruby/vendor_ruby/will_crash.rb", 13, "func", false, 2, 1);
01add4
+
01add4
+  check("/home/u/work/will:crash/will_crash.rb:30:in `block in <class:WillClass>'",
01add4
+        "/home/u/work/will:crash/will_crash.rb", 30, "class:WillClass", true, 1, 0);
01add4
+
01add4
+  check("./will_ruby_raise:8:in `<main>'",
01add4
+        "./will_ruby_raise", 8, "main", true, 0, 0);
01add4
+
01add4
+  /* try failing */
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+  char *input = "i;dikasdfxc";
01add4
+
01add4
+  struct sr_ruby_frame *frame  = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(!frame);
01add4
+  assert(location.message);
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_cmp],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
01add4
+  char *input;
01add4
+
01add4
+  sr_location_init(&location);
01add4
+  input = line;
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  sr_location_init(&location);
01add4
+  input = line;
01add4
+  struct sr_ruby_frame *frame2 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
01add4
+  assert(frame1 != frame2);
01add4
+
01add4
+  frame2->file_line = 9000;
01add4
+  assert(0 != sr_ruby_frame_cmp(frame1, frame2));
01add4
+
01add4
+  sr_ruby_frame_free(frame1);
01add4
+  sr_ruby_frame_free(frame2);
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_dup],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
01add4
+  char *input;
01add4
+
01add4
+  sr_location_init(&location);
01add4
+  input = line;
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  struct sr_ruby_frame *frame2 = sr_ruby_frame_dup(frame1, false);
01add4
+
01add4
+  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
01add4
+  assert(frame1 != frame2);
01add4
+  assert(frame1->function_name != frame2->function_name);
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_append],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_new();
01add4
+  struct sr_ruby_frame *frame2 = sr_ruby_frame_new();
01add4
+
01add4
+  assert(frame1->next == frame2->next);
01add4
+  assert(frame1->next == NULL);
01add4
+
01add4
+  sr_ruby_frame_append(frame1, frame2);
01add4
+  assert(frame1->next == frame2);
01add4
+
01add4
+  sr_ruby_frame_free(frame1);
01add4
+  sr_ruby_frame_free(frame2);
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_to_json],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
01add4
+  char *input;
01add4
+
01add4
+  sr_location_init(&location);
01add4
+  input = line;
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  char *expected = "{   \"file_name\": \"/usr/share/ruby/vendor_ruby/will_crash.rb\"\n"
01add4
+                   ",   \"file_line\": 13\n"
01add4
+                   ",   \"function_name\": \"func\"\n"
01add4
+                   ",   \"block_level\": 2\n"
01add4
+                   ",   \"rescue_level\": 1\n"
01add4
+                   "}";
01add4
+
01add4
+  char *json = sr_ruby_frame_to_json(frame1);
01add4
+  assert(0 == strcmp(json, expected));
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_from_json],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "json.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+
01add4
+void
01add4
+check(char *input)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  char *json = sr_ruby_frame_to_json(frame1);
01add4
+  char *error = NULL;
01add4
+
01add4
+  struct sr_json_value *root = sr_json_parse(json, &error);
01add4
+  assert(root);
01add4
+  struct sr_ruby_frame *frame2 = sr_ruby_frame_from_json(root, &error);
01add4
+  assert(frame2);
01add4
+  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  check("/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'");
01add4
+  check("/usr/share/ruby/vendor:ruby/will_crash.rb:13:in `block (22 levels) in <func>'");
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_append_to_str],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include "strbuf.h"
01add4
+#include <assert.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
01add4
+  char *input;
01add4
+
01add4
+  sr_location_init(&location);
01add4
+  input = line;
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  struct sr_strbuf *strbuf = sr_strbuf_new();
01add4
+  sr_ruby_frame_append_to_str(frame1, strbuf);
01add4
+  char *result = sr_strbuf_free_nobuf(strbuf);
01add4
+
01add4
+  assert(0 == strcmp(result, "rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13"));
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_frame_generic_functions],
01add4
+[[
01add4
+#include "ruby/frame.h"
01add4
+#include "frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include "strbuf.h"
01add4
+#include <assert.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_location location;
01add4
+  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
01add4
+  char *input;
01add4
+
01add4
+  sr_location_init(&location);
01add4
+  input = line;
01add4
+  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
01add4
+  assert(frame1);
01add4
+
01add4
+  assert(sr_frame_next(frame1) == NULL);
01add4
+
01add4
+  struct sr_strbuf *strbuf = sr_strbuf_new();
01add4
+  sr_frame_append_to_str((struct sr_frame*)frame1, strbuf);
01add4
+  char *result = sr_strbuf_free_nobuf(strbuf);
01add4
+
01add4
+  assert(0 == strcmp(result, "rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13"));
01add4
+
01add4
+  sr_frame_free((struct sr_frame*)frame1);
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
diff --git a/tests/ruby_stacktrace.at b/tests/ruby_stacktrace.at
01add4
new file mode 100644
01add4
index 0000000..5a2d901
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktrace.at
01add4
@@ -0,0 +1,316 @@
01add4
+# Checking the satyr. -*- Autotest -*-
01add4
+
01add4
+AT_BANNER([Ruby stacktrace])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_stacktrace_parse],
01add4
+[[
01add4
+#include "ruby/stacktrace.h"
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+#include <stdlib.h>
01add4
+#include <stdio.h>
01add4
+
01add4
+void
01add4
+check(char *filename, char *exc_name, unsigned frame_count, struct sr_ruby_frame *top_frame, struct sr_ruby_frame *second_frame, struct sr_ruby_frame *bottom_frame)
01add4
+{
01add4
+  char *error_message = NULL;
01add4
+  const char *file_contents = sr_file_to_string(filename, &error_message);
01add4
+  const char *input = file_contents;
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+
01add4
+  struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_parse(&input, &location);
01add4
+  assert(stacktrace);
01add4
+  assert(*input == '\0');
01add4
+  assert(0 == strcmp(stacktrace->exception_name, exc_name));
01add4
+
01add4
+  struct sr_ruby_frame *frame = stacktrace->frames;
01add4
+  int i = 0;
01add4
+  while (frame)
01add4
+  {
01add4
+    if (i==0 && top_frame)
01add4
+    {
01add4
+      assert(sr_ruby_frame_cmp(frame, top_frame) == 0);
01add4
+    }
01add4
+    else if (i == 1 && second_frame)
01add4
+    {
01add4
+      assert(sr_ruby_frame_cmp(frame, second_frame) == 0);
01add4
+    }
01add4
+
01add4
+    frame = frame->next;
01add4
+    i++;
01add4
+  }
01add4
+
01add4
+  assert(i == frame_count);
01add4
+  if (frame && bottom_frame)
01add4
+  {
01add4
+    assert(sr_ruby_frame_cmp(frame, bottom_frame) == 0);
01add4
+  }
01add4
+
01add4
+  sr_ruby_stacktrace_free(stacktrace);
01add4
+  free(file_contents);
01add4
+}
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  struct sr_ruby_frame top = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/share/ruby/vendor_ruby/will_crash.rb",
01add4
+    .file_line = 13,
01add4
+    .special_function = false,
01add4
+    .function_name = "func",
01add4
+    .block_level = 2,
01add4
+    .rescue_level = 1
01add4
+  };
01add4
+  struct sr_ruby_frame second = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/share/ruby/vendor_ruby/will_crash.rb",
01add4
+    .file_line = 10,
01add4
+    .special_function = false,
01add4
+    .function_name = "func",
01add4
+    .block_level = 2,
01add4
+    .rescue_level = 0
01add4
+  };
01add4
+  struct sr_ruby_frame bottom = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/bin/will_ruby_raise",
01add4
+    .file_line = 8,
01add4
+    .special_function = true,
01add4
+    .function_name = "main",
01add4
+    .block_level = 0,
01add4
+    .rescue_level = 0
01add4
+  };
01add4
+  check("../../ruby_stacktraces/ruby-01", "Wrap::MyException", 21, &top, &second, &bottom);
01add4
+
01add4
+  struct sr_ruby_frame top2 = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb",
01add4
+    .file_line = 173,
01add4
+    .special_function = false,
01add4
+    .function_name = "aref_prefix",
01add4
+    .block_level = 0,
01add4
+    .rescue_level = 0
01add4
+  };
01add4
+  struct sr_ruby_frame second2 = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb",
01add4
+    .file_line = 181,
01add4
+    .special_function = false,
01add4
+    .function_name = "aref",
01add4
+    .block_level = 0,
01add4
+    .rescue_level = 0
01add4
+  };
01add4
+  check("../../ruby_stacktraces/ruby-02", "NotImplementedError", 37, &top2, &second2, NULL);
01add4
+
01add4
+  check("../../ruby_stacktraces/ruby-03", "RuntimeError", 4, NULL, NULL, NULL);
01add4
+
01add4
+  struct sr_ruby_frame top4 = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/share/rubygems/rubygems/basic_specification.rb",
01add4
+    .file_line = 50,
01add4
+    .special_function = false,
01add4
+    .function_name = "contains_requirable_file?",
01add4
+    .block_level = 3,
01add4
+    .rescue_level = 0
01add4
+  };
01add4
+  struct sr_ruby_frame second4 = {
01add4
+    .type = SR_REPORT_RUBY,
01add4
+    .file_name = "/usr/share/rubygems/rubygems/basic_specification.rb",
01add4
+    .file_line = 50,
01add4
+    .special_function = false,
01add4
+    .function_name = "each",
01add4
+    .block_level = 0,
01add4
+    .rescue_level = 0
01add4
+  };
01add4
+  check("../../ruby_stacktraces/ruby-04", "SignalException", 39, &top4, &second4, NULL);
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_stacktrace_dup],
01add4
+[[
01add4
+#include "ruby/stacktrace.h"
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+#include <stdlib.h>
01add4
+#include <stdio.h>
01add4
+
01add4
+void
01add4
+check(char *filename)
01add4
+{
01add4
+  char *error_message = NULL;
01add4
+  const char *file_contents = sr_file_to_string(filename, &error_message);
01add4
+  const char *input = file_contents;
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+
01add4
+  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
01add4
+  struct sr_ruby_stacktrace *stacktrace2 = sr_ruby_stacktrace_dup(stacktrace1);
01add4
+
01add4
+  assert(stacktrace1 != stacktrace2);
01add4
+  assert(0 == strcmp(stacktrace1->exception_name, stacktrace2->exception_name));
01add4
+
01add4
+  struct sr_ruby_frame *f1 = stacktrace1->frames;
01add4
+  struct sr_ruby_frame *f2 = stacktrace2->frames;
01add4
+
01add4
+  while (f1 && f2)
01add4
+  {
01add4
+    assert(0 == sr_ruby_frame_cmp(f1, f2));
01add4
+    f1 = f1->next;
01add4
+    f2 = f2->next;
01add4
+  }
01add4
+  assert(f1 == NULL);
01add4
+  assert(f2 == NULL);
01add4
+
01add4
+  sr_ruby_stacktrace_free(stacktrace1);
01add4
+  sr_ruby_stacktrace_free(stacktrace2);
01add4
+  free(file_contents);
01add4
+}
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  check("../../ruby_stacktraces/ruby-01");
01add4
+  check("../../ruby_stacktraces/ruby-02");
01add4
+  check("../../ruby_stacktraces/ruby-03");
01add4
+  check("../../ruby_stacktraces/ruby-04");
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_stacktrace_get_reason],
01add4
+[[
01add4
+#include "ruby/stacktrace.h"
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+#include <stdlib.h>
01add4
+#include <stdio.h>
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  char *error_message = NULL;
01add4
+  const char *file_contents = sr_file_to_string("../../ruby_stacktraces/ruby-03", &error_message);
01add4
+  const char *input = file_contents;
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
01add4
+
01add4
+  char *reason = sr_ruby_stacktrace_get_reason(stacktrace1);
01add4
+  char *expected = "RuntimeError in /usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:103";
01add4
+  assert(0 == strcmp(reason, expected));
01add4
+
01add4
+  sr_ruby_stacktrace_free(stacktrace1);
01add4
+  free(file_contents);
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_stacktrace_to_json],
01add4
+[[
01add4
+#include "ruby/stacktrace.h"
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+#include <stdlib.h>
01add4
+#include <stdio.h>
01add4
+
01add4
+void
01add4
+check(char *filename, char *json_filename)
01add4
+{
01add4
+  char *error_message = NULL;
01add4
+  const char *file_contents = sr_file_to_string(filename, &error_message);
01add4
+  const char *input = file_contents;
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+
01add4
+  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
01add4
+
01add4
+  char *expected = sr_file_to_string(json_filename, &error_message);
01add4
+  char *json = sr_ruby_stacktrace_to_json(stacktrace1);
01add4
+
01add4
+  assert(0 == strcmp(json, expected));
01add4
+
01add4
+  sr_ruby_stacktrace_free(stacktrace1);
01add4
+  free(json);
01add4
+  free(file_contents);
01add4
+}
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  check("../../ruby_stacktraces/ruby-01", "../../ruby_stacktraces/ruby-01-expected-json");
01add4
+  check("../../ruby_stacktraces/ruby-02", "../../ruby_stacktraces/ruby-02-expected-json");
01add4
+  check("../../ruby_stacktraces/ruby-03", "../../ruby_stacktraces/ruby-03-expected-json");
01add4
+  check("../../ruby_stacktraces/ruby-04", "../../ruby_stacktraces/ruby-04-expected-json");
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
+
01add4
+AT_TESTFUN([sr_ruby_stacktrace_from_json],
01add4
+[[
01add4
+#include "ruby/stacktrace.h"
01add4
+#include "ruby/frame.h"
01add4
+#include "utils.h"
01add4
+#include "location.h"
01add4
+#include <assert.h>
01add4
+#include <stdlib.h>
01add4
+#include <stdio.h>
01add4
+
01add4
+void
01add4
+check(char *filename)
01add4
+{
01add4
+  char *error_message = NULL;
01add4
+  const char *file_contents = sr_file_to_string(filename, &error_message);
01add4
+  const char *input = file_contents;
01add4
+  struct sr_location location;
01add4
+  sr_location_init(&location);
01add4
+
01add4
+  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
01add4
+
01add4
+  char *json = sr_ruby_stacktrace_to_json(stacktrace1);
01add4
+  struct sr_ruby_stacktrace *stacktrace2 = sr_stacktrace_from_json_text(SR_REPORT_RUBY, json, &error_message);
01add4
+
01add4
+  assert(0 == strcmp(stacktrace1->exception_name, stacktrace2->exception_name));
01add4
+
01add4
+  struct sr_ruby_frame *f1 = stacktrace1->frames;
01add4
+  struct sr_ruby_frame *f2 = stacktrace2->frames;
01add4
+
01add4
+  while (f1 && f2)
01add4
+  {
01add4
+    assert(0 == sr_ruby_frame_cmp(f1, f2));
01add4
+    f1 = f1->next;
01add4
+    f2 = f2->next;
01add4
+  }
01add4
+  assert(f1 == NULL);
01add4
+  assert(f2 == NULL);
01add4
+
01add4
+  sr_ruby_stacktrace_free(stacktrace1);
01add4
+  sr_ruby_stacktrace_free(stacktrace2);
01add4
+  free(json);
01add4
+  free(file_contents);
01add4
+}
01add4
+
01add4
+int
01add4
+main(void)
01add4
+{
01add4
+  check("../../ruby_stacktraces/ruby-01");
01add4
+  check("../../ruby_stacktraces/ruby-02");
01add4
+  check("../../ruby_stacktraces/ruby-03");
01add4
+  check("../../ruby_stacktraces/ruby-04");
01add4
+
01add4
+  return 0;
01add4
+}
01add4
+]])
01add4
diff --git a/tests/ruby_stacktraces/ruby-01 b/tests/ruby_stacktraces/ruby-01
01add4
new file mode 100644
01add4
index 0000000..a635dd9
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-01
01add4
@@ -0,0 +1,23 @@
01add4
+/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func': Exception
01add4
+successfully
01add4
+raised. (Wrap::MyException)
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:10:in `block (2 levels) in func'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:9:in `times'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:9:in `block in func'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:8:in `times'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:8:in `func'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:30:in `block in <class:WillClass>'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:28:in `times'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:28:in `rescue in rescue in rescue in <class:WillClass>'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:25:in `rescue in rescue in <class:WillClass>'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:22:in `rescue in <class:WillClass>'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:19:in `<class:WillClass>'
01add4
+	from /usr/share/ruby/vendor_ruby/will_crash.rb:6:in `<top (required)>'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/bin/will_ruby_raise:11:in `block (2 levels) in <module:WillModule>'
01add4
+	from /usr/bin/will_ruby_raise:10:in `times'
01add4
+	from /usr/bin/will_ruby_raise:10:in `block in <module:WillModule>'
01add4
+	from /usr/bin/will_ruby_raise:9:in `times'
01add4
+	from /usr/bin/will_ruby_raise:9:in `<module:WillModule>'
01add4
+	from /usr/bin/will_ruby_raise:8:in `<main>'
01add4
diff --git a/tests/ruby_stacktraces/ruby-01-expected-json b/tests/ruby_stacktraces/ruby-01-expected-json
01add4
new file mode 100644
01add4
index 0000000..493fb18
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-01-expected-json
01add4
@@ -0,0 +1,97 @@
01add4
+{   "exception_name": "Wrap::MyException"
01add4
+,   "stacktrace":
01add4
+      [ {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 13
01add4
+        ,   "function_name": "func"
01add4
+        ,   "block_level": 2
01add4
+        ,   "rescue_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 10
01add4
+        ,   "function_name": "func"
01add4
+        ,   "block_level": 2
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 9
01add4
+        ,   "function_name": "times"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 9
01add4
+        ,   "function_name": "func"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 8
01add4
+        ,   "function_name": "times"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 8
01add4
+        ,   "function_name": "func"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 30
01add4
+        ,   "special_function": "class:WillClass"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 28
01add4
+        ,   "function_name": "times"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 28
01add4
+        ,   "special_function": "class:WillClass"
01add4
+        ,   "rescue_level": 3
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 25
01add4
+        ,   "special_function": "class:WillClass"
01add4
+        ,   "rescue_level": 2
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 22
01add4
+        ,   "special_function": "class:WillClass"
01add4
+        ,   "rescue_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 19
01add4
+        ,   "special_function": "class:WillClass"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
01add4
+        ,   "file_line": 6
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/will_ruby_raise"
01add4
+        ,   "file_line": 11
01add4
+        ,   "special_function": "module:WillModule"
01add4
+        ,   "block_level": 2
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/will_ruby_raise"
01add4
+        ,   "file_line": 10
01add4
+        ,   "function_name": "times"
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/will_ruby_raise"
01add4
+        ,   "file_line": 10
01add4
+        ,   "special_function": "module:WillModule"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/will_ruby_raise"
01add4
+        ,   "file_line": 9
01add4
+        ,   "function_name": "times"
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/will_ruby_raise"
01add4
+        ,   "file_line": 9
01add4
+        ,   "special_function": "module:WillModule"
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/will_ruby_raise"
01add4
+        ,   "file_line": 8
01add4
+        ,   "special_function": "main"
01add4
+        } ]
01add4
+}
01add4
\ No newline at end of file
01add4
diff --git a/tests/ruby_stacktraces/ruby-02 b/tests/ruby_stacktraces/ruby-02
01add4
new file mode 100644
01add4
index 0000000..dd24a02
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-02
01add4
@@ -0,0 +1,37 @@
01add4
+/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb:173:in `aref_prefix': missing aref_prefix for RDoc::SingleClass (NotImplementedError)
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb:181:in `aref'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/template/darkfish/class.rhtml:47:in `block in generate_class'
01add4
+	from /usr/share/ruby/erb.rb:850:in `eval'
01add4
+	from /usr/share/ruby/erb.rb:850:in `result'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:724:in `template_result'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:703:in `block in render_template'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `open'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `open'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `render_template'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:351:in `generate_class'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:371:in `block in generate_class_files'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:368:in `each'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:368:in `generate_class_files'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:245:in `generate'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:137:in `block in document'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:134:in `chdir'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:134:in `document'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:202:in `generate'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:56:in `block in generation_hook'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:55:in `each'
01add4
+	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:55:in `generation_hook'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:188:in `call'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:188:in `block in install'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:187:in `each'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:187:in `install'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/dependency_installer.rb:394:in `install'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:211:in `update_gem'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:223:in `block in update_gems'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:222:in `each'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:222:in `update_gems'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:98:in `execute'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/command.rb:307:in `invoke_with_build_args'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/command_manager.rb:168:in `process_args'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/command_manager.rb:138:in `run'
01add4
+	from /usr/local/share/ruby/site_ruby/rubygems/gem_runner.rb:54:in `run'
01add4
+	from /bin/gem:21:in `<main>'
01add4
\ No newline at end of file
01add4
diff --git a/tests/ruby_stacktraces/ruby-02-expected-json b/tests/ruby_stacktraces/ruby-02-expected-json
01add4
new file mode 100644
01add4
index 0000000..1bef95c
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-02-expected-json
01add4
@@ -0,0 +1,158 @@
01add4
+{   "exception_name": "NotImplementedError"
01add4
+,   "stacktrace":
01add4
+      [ {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb"
01add4
+        ,   "file_line": 173
01add4
+        ,   "function_name": "aref_prefix"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb"
01add4
+        ,   "file_line": 181
01add4
+        ,   "function_name": "aref"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/template/darkfish/class.rhtml"
01add4
+        ,   "file_line": 47
01add4
+        ,   "function_name": "generate_class"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/erb.rb"
01add4
+        ,   "file_line": 850
01add4
+        ,   "function_name": "eval"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/ruby/erb.rb"
01add4
+        ,   "file_line": 850
01add4
+        ,   "function_name": "result"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 724
01add4
+        ,   "function_name": "template_result"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 703
01add4
+        ,   "function_name": "render_template"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 698
01add4
+        ,   "function_name": "open"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 698
01add4
+        ,   "function_name": "open"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 698
01add4
+        ,   "function_name": "render_template"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 351
01add4
+        ,   "function_name": "generate_class"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 371
01add4
+        ,   "function_name": "generate_class_files"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 368
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 368
01add4
+        ,   "function_name": "generate_class_files"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
01add4
+        ,   "file_line": 245
01add4
+        ,   "function_name": "generate"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 137
01add4
+        ,   "function_name": "document"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 134
01add4
+        ,   "function_name": "chdir"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 134
01add4
+        ,   "function_name": "document"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 202
01add4
+        ,   "function_name": "generate"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 56
01add4
+        ,   "function_name": "generation_hook"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "generation_hook"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
01add4
+        ,   "file_line": 188
01add4
+        ,   "function_name": "call"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
01add4
+        ,   "file_line": 188
01add4
+        ,   "function_name": "install"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
01add4
+        ,   "file_line": 187
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
01add4
+        ,   "file_line": 187
01add4
+        ,   "function_name": "install"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/dependency_installer.rb"
01add4
+        ,   "file_line": 394
01add4
+        ,   "function_name": "install"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
01add4
+        ,   "file_line": 211
01add4
+        ,   "function_name": "update_gem"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
01add4
+        ,   "file_line": 223
01add4
+        ,   "function_name": "update_gems"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
01add4
+        ,   "file_line": 222
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
01add4
+        ,   "file_line": 222
01add4
+        ,   "function_name": "update_gems"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
01add4
+        ,   "file_line": 98
01add4
+        ,   "function_name": "execute"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command.rb"
01add4
+        ,   "file_line": 307
01add4
+        ,   "function_name": "invoke_with_build_args"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command_manager.rb"
01add4
+        ,   "file_line": 168
01add4
+        ,   "function_name": "process_args"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command_manager.rb"
01add4
+        ,   "file_line": 138
01add4
+        ,   "function_name": "run"
01add4
+        }
01add4
+      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/gem_runner.rb"
01add4
+        ,   "file_line": 54
01add4
+        ,   "function_name": "run"
01add4
+        }
01add4
+      , {   "file_name": "/bin/gem"
01add4
+        ,   "file_line": 21
01add4
+        ,   "special_function": "main"
01add4
+        } ]
01add4
+}
01add4
\ No newline at end of file
01add4
diff --git a/tests/ruby_stacktraces/ruby-03 b/tests/ruby_stacktraces/ruby-03
01add4
new file mode 100644
01add4
index 0000000..d931254
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-03
01add4
@@ -0,0 +1,4 @@
01add4
+/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:103:in `get_interface_mtu': Unable to determine external network interface IP address. (RuntimeError)
01add4
+	from /usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:77:in `initialize'
01add4
+	from /sbin/oo-admin-ctl-tc:24:in `new'
01add4
+	from /sbin/oo-admin-ctl-tc:24:in `<main>'
01add4
\ No newline at end of file
01add4
diff --git a/tests/ruby_stacktraces/ruby-03-expected-json b/tests/ruby_stacktraces/ruby-03-expected-json
01add4
new file mode 100644
01add4
index 0000000..a1fa1f8
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-03-expected-json
01add4
@@ -0,0 +1,19 @@
01add4
+{   "exception_name": "RuntimeError"
01add4
+,   "stacktrace":
01add4
+      [ {   "file_name": "/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb"
01add4
+        ,   "file_line": 103
01add4
+        ,   "function_name": "get_interface_mtu"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb"
01add4
+        ,   "file_line": 77
01add4
+        ,   "function_name": "initialize"
01add4
+        }
01add4
+      , {   "file_name": "/sbin/oo-admin-ctl-tc"
01add4
+        ,   "file_line": 24
01add4
+        ,   "function_name": "new"
01add4
+        }
01add4
+      , {   "file_name": "/sbin/oo-admin-ctl-tc"
01add4
+        ,   "file_line": 24
01add4
+        ,   "special_function": "main"
01add4
+        } ]
01add4
+}
01add4
\ No newline at end of file
01add4
diff --git a/tests/ruby_stacktraces/ruby-04 b/tests/ruby_stacktraces/ruby-04
01add4
new file mode 100644
01add4
index 0000000..12388a7
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-04
01add4
@@ -0,0 +1,39 @@
01add4
+/usr/share/rubygems/rubygems/basic_specification.rb:50:in `block (3 levels) in contains_requirable_file?': SIGTERM (SignalException)
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `each'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `any?'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `block (2 levels) in contains_requirable_file?'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `each'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `any?'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `block in contains_requirable_file?'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `each'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `any?'
01add4
+	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `contains_requirable_file?'
01add4
+	from /usr/share/rubygems/rubygems/specification.rb:898:in `block in find_inactive_by_path'
01add4
+	from /usr/share/rubygems/rubygems/specification.rb:897:in `each'
01add4
+	from /usr/share/rubygems/rubygems/specification.rb:897:in `find'
01add4
+	from /usr/share/rubygems/rubygems/specification.rb:897:in `find_inactive_by_path'
01add4
+	from /usr/share/rubygems/rubygems.rb:183:in `try_activate'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:144:in `require'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb:2:in `<top (required)>'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines.rb:25:in `<top (required)>'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites.rb:18:in `<top (required)>'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions.rb:10:in `<top (required)>'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:5:in `block in <top (required)>'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:4:in `each'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:4:in `<top (required)>'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/bin/compass:20:in `block in <top (required)>'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/bin/compass:8:in `fallback_load_path'
01add4
+	from /usr/share/gems/gems/compass-0.12.2/bin/compass:19:in `<top (required)>'
01add4
+	from /usr/bin/compass:23:in `load'
01add4
+	from /usr/bin/compass:23:in `<main>'
01add4
\ No newline at end of file
01add4
diff --git a/tests/ruby_stacktraces/ruby-04-expected-json b/tests/ruby_stacktraces/ruby-04-expected-json
01add4
new file mode 100644
01add4
index 0000000..22bca55
01add4
--- /dev/null
01add4
+++ b/tests/ruby_stacktraces/ruby-04-expected-json
01add4
@@ -0,0 +1,166 @@
01add4
+{   "exception_name": "SignalException"
01add4
+,   "stacktrace":
01add4
+      [ {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 50
01add4
+        ,   "function_name": "contains_requirable_file?"
01add4
+        ,   "block_level": 3
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 50
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 50
01add4
+        ,   "function_name": "any?"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 50
01add4
+        ,   "function_name": "contains_requirable_file?"
01add4
+        ,   "block_level": 2
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 49
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 49
01add4
+        ,   "function_name": "any?"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 49
01add4
+        ,   "function_name": "contains_requirable_file?"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 45
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 45
01add4
+        ,   "function_name": "any?"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
01add4
+        ,   "file_line": 45
01add4
+        ,   "function_name": "contains_requirable_file?"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
01add4
+        ,   "file_line": 898
01add4
+        ,   "function_name": "find_inactive_by_path"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
01add4
+        ,   "file_line": 897
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
01add4
+        ,   "file_line": 897
01add4
+        ,   "function_name": "find"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
01add4
+        ,   "file_line": 897
01add4
+        ,   "function_name": "find_inactive_by_path"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems.rb"
01add4
+        ,   "file_line": 183
01add4
+        ,   "function_name": "try_activate"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 132
01add4
+        ,   "function_name": "require"
01add4
+        ,   "rescue_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 144
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb"
01add4
+        ,   "file_line": 2
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines.rb"
01add4
+        ,   "file_line": 25
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites.rb"
01add4
+        ,   "file_line": 18
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions.rb"
01add4
+        ,   "file_line": 10
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
01add4
+        ,   "file_line": 5
01add4
+        ,   "special_function": "top (required)"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
01add4
+        ,   "file_line": 4
01add4
+        ,   "function_name": "each"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
01add4
+        ,   "file_line": 4
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
01add4
+        ,   "file_line": 55
01add4
+        ,   "function_name": "require"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
01add4
+        ,   "file_line": 20
01add4
+        ,   "special_function": "top (required)"
01add4
+        ,   "block_level": 1
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
01add4
+        ,   "file_line": 8
01add4
+        ,   "function_name": "fallback_load_path"
01add4
+        }
01add4
+      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
01add4
+        ,   "file_line": 19
01add4
+        ,   "special_function": "top (required)"
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/compass"
01add4
+        ,   "file_line": 23
01add4
+        ,   "function_name": "load"
01add4
+        }
01add4
+      , {   "file_name": "/usr/bin/compass"
01add4
+        ,   "file_line": 23
01add4
+        ,   "special_function": "main"
01add4
+        } ]
01add4
+}
01add4
\ No newline at end of file
01add4
diff --git a/tests/testsuite.at b/tests/testsuite.at
01add4
index 904fe64..f27ed13 100644
01add4
--- a/tests/testsuite.at
01add4
+++ b/tests/testsuite.at
01add4
@@ -15,6 +15,8 @@ m4_include([koops_stacktrace.at])
01add4
 m4_include([core_frame.at])
01add4
 m4_include([core_thread.at])
01add4
 m4_include([core_stacktrace.at])
01add4
+m4_include([ruby_frame.at])
01add4
+m4_include([ruby_stacktrace.at])
01add4
 m4_include([operating_system.at])
01add4
 m4_include([normalize.at])
01add4
 m4_include([metrics.at])
01add4
-- 
01add4
1.8.3.1
01add4