diff --git a/SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch b/SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch
new file mode 100644
index 0000000..bd1c13e
--- /dev/null
+++ b/SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch
@@ -0,0 +1,2586 @@
+From 28db4ce2840d74f06b55c1e2c7c48f228708fb1d Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 28 Jan 2015 16:11:15 +0100
+Subject: [PATCH] Add support for Ruby report type
+
+Related: #1334604
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ include/Makefile.am                          |   5 +
+ include/report_type.h                        |   1 +
+ include/ruby/frame.h                         |  99 ++++++
+ include/ruby/stacktrace.h                    |  73 ++++
+ lib/Makefile.am                              |   2 +
+ lib/abrt.c                                   |  31 ++
+ lib/generic_frame.c                          |   1 +
+ lib/generic_frame.h                          |   3 +-
+ lib/generic_stacktrace.c                     |   1 +
+ lib/generic_stacktrace.h                     |   3 +-
+ lib/generic_thread.c                         |   1 +
+ lib/generic_thread.h                         |   3 +-
+ lib/report.c                                 |   3 +
+ lib/ruby_frame.c                             | 504 +++++++++++++++++++++++++++
+ lib/ruby_stacktrace.c                        | 387 ++++++++++++++++++++
+ tests/Makefile.am                            |   3 +
+ tests/ruby_frame.at                          | 285 +++++++++++++++
+ tests/ruby_stacktrace.at                     | 316 +++++++++++++++++
+ tests/ruby_stacktraces/ruby-01               |  23 ++
+ tests/ruby_stacktraces/ruby-01-expected-json |  97 ++++++
+ tests/ruby_stacktraces/ruby-02               |  37 ++
+ tests/ruby_stacktraces/ruby-02-expected-json | 158 +++++++++
+ tests/ruby_stacktraces/ruby-03               |   4 +
+ tests/ruby_stacktraces/ruby-03-expected-json |  19 +
+ tests/ruby_stacktraces/ruby-04               |  39 +++
+ tests/ruby_stacktraces/ruby-04-expected-json | 166 +++++++++
+ tests/testsuite.at                           |   2 +
+ 27 files changed, 2263 insertions(+), 3 deletions(-)
+ create mode 100644 include/ruby/frame.h
+ create mode 100644 include/ruby/stacktrace.h
+ create mode 100644 lib/ruby_frame.c
+ create mode 100644 lib/ruby_stacktrace.c
+ create mode 100644 tests/ruby_frame.at
+ create mode 100644 tests/ruby_stacktrace.at
+ create mode 100644 tests/ruby_stacktraces/ruby-01
+ create mode 100644 tests/ruby_stacktraces/ruby-01-expected-json
+ create mode 100644 tests/ruby_stacktraces/ruby-02
+ create mode 100644 tests/ruby_stacktraces/ruby-02-expected-json
+ create mode 100644 tests/ruby_stacktraces/ruby-03
+ create mode 100644 tests/ruby_stacktraces/ruby-03-expected-json
+ create mode 100644 tests/ruby_stacktraces/ruby-04
+ create mode 100644 tests/ruby_stacktraces/ruby-04-expected-json
+
+diff --git a/include/Makefile.am b/include/Makefile.am
+index f72ec66..bcffe31 100644
+--- a/include/Makefile.am
++++ b/include/Makefile.am
+@@ -49,3 +49,8 @@ pythonheadersdir = $(includedir)/satyr/python
+ pythonheaders_HEADERS = \
+ 	python/frame.h \
+ 	python/stacktrace.h
++
++rubyheadersdir = $(includedir)/satyr/ruby
++rubyheaders_HEADERS = \
++	ruby/frame.h \
++	ruby/stacktrace.h
+diff --git a/include/report_type.h b/include/report_type.h
+index a51620d..0d411ee 100644
+--- a/include/report_type.h
++++ b/include/report_type.h
+@@ -32,6 +32,7 @@ enum sr_report_type
+     SR_REPORT_KERNELOOPS,
+     SR_REPORT_JAVA,
+     SR_REPORT_GDB,
++    SR_REPORT_RUBY,
+ 
+     /* Keep this the last entry. */
+     SR_REPORT_NUM
+diff --git a/include/ruby/frame.h b/include/ruby/frame.h
+new file mode 100644
+index 0000000..ffc3c67
+--- /dev/null
++++ b/include/ruby/frame.h
+@@ -0,0 +1,99 @@
++/*
++    ruby_frame.h
++
++    Copyright (C) 2015  ABRT Team
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_RUBY_FRAME_H
++#define SATYR_RUBY_FRAME_H
++
++/**
++ * @file
++ * @brief Ruby frame structure and related routines.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include "../report_type.h"
++#include <stdbool.h>
++#include <stdint.h>
++
++struct sr_location;
++struct sr_strbuf;
++struct sr_json_value;
++
++struct sr_ruby_frame
++{
++    enum sr_report_type type;
++
++    char *file_name;
++
++    uint32_t file_line;
++
++    bool special_function;
++
++    char *function_name;
++
++    uint32_t block_level;
++
++    uint32_t rescue_level;
++
++    struct sr_ruby_frame *next;
++};
++
++struct sr_ruby_frame *
++sr_ruby_frame_new();
++
++void
++sr_ruby_frame_init(struct sr_ruby_frame *frame);
++
++void
++sr_ruby_frame_free(struct sr_ruby_frame *frame);
++
++struct sr_ruby_frame *
++sr_ruby_frame_dup(struct sr_ruby_frame *frame, bool siblings);
++
++int
++sr_ruby_frame_cmp(struct sr_ruby_frame *frame1, struct sr_ruby_frame *frame2);
++
++int
++sr_ruby_frame_cmp_distance(struct sr_ruby_frame *frame1,
++                           struct sr_ruby_frame *frame2);
++
++struct sr_ruby_frame *
++sr_ruby_frame_append(struct sr_ruby_frame *dest,
++                     struct sr_ruby_frame *item);
++
++struct sr_ruby_frame *
++sr_ruby_frame_parse(const char **input, struct sr_location *location);
++
++char *
++sr_ruby_frame_to_json(struct sr_ruby_frame *frame);
++
++struct sr_ruby_frame *
++sr_ruby_frame_from_json(struct sr_json_value *root, char **error_message);
++
++void
++sr_ruby_frame_append_to_str(struct sr_ruby_frame *frame, struct sr_strbuf *dest);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/include/ruby/stacktrace.h b/include/ruby/stacktrace.h
+new file mode 100644
+index 0000000..caa3e0b
+--- /dev/null
++++ b/include/ruby/stacktrace.h
+@@ -0,0 +1,73 @@
++/*
++    ruby_stacktrace.h
++
++    Copyright (C) 2015  ABRT Team
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_RUBY_STACKTRACE_H
++#define SATYR_RUBY_STACKTRACE_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include "../report_type.h"
++#include <stdint.h>
++
++struct sr_ruby_frame;
++struct sr_location;
++struct sr_json_value;
++
++struct sr_ruby_stacktrace
++{
++    enum sr_report_type type;
++
++    char *exception_name;
++
++    struct sr_ruby_frame *frames;
++};
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_new();
++
++void
++sr_ruby_stacktrace_init(struct sr_ruby_stacktrace *stacktrace);
++
++void
++sr_ruby_stacktrace_free(struct sr_ruby_stacktrace *stacktrace);
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_dup(struct sr_ruby_stacktrace *stacktrace);
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_parse(const char **input,
++                         struct sr_location *location);
++
++char *
++sr_ruby_stacktrace_get_reason(struct sr_ruby_stacktrace *stacktrace);
++
++char *
++sr_ruby_stacktrace_to_json(struct sr_ruby_stacktrace *stacktrace);
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_from_json(struct sr_json_value *root, char **error_message);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index f798347..26016f2 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -57,6 +57,8 @@ libsatyr_conv_la_SOURCES = \
+ 	python_stacktrace.c \
+ 	report.c \
+ 	rpm.c \
++	ruby_frame.c \
++	ruby_stacktrace.c \
+ 	sha1.c \
+ 	strbuf.c \
+ 	unstrip.c \
+diff --git a/lib/abrt.c b/lib/abrt.c
+index 4599e2c..a07d44a 100644
+--- a/lib/abrt.c
++++ b/lib/abrt.c
+@@ -30,6 +30,7 @@
+ #include "python/stacktrace.h"
+ #include "koops/stacktrace.h"
+ #include "java/stacktrace.h"
++#include "ruby/stacktrace.h"
+ #include "json.h"
+ #include "location.h"
+ #include <stdio.h>
+@@ -567,6 +568,34 @@ sr_abrt_report_from_dir(const char *directory,
+         }
+     }
+ 
++    /* Ruby stacktrace. */
++    if (report->report_type == SR_REPORT_RUBY)
++    {
++        char *backtrace_contents = file_contents(directory, "backtrace",
++                                                 error_message);
++        if (!backtrace_contents)
++        {
++            sr_report_free(report);
++            return NULL;
++        }
++
++        /* Parse the Ruby stacktrace. */
++        struct sr_location location;
++        sr_location_init(&location);
++        const char *contents_pointer = backtrace_contents;
++        report->stacktrace = (struct sr_stacktrace *)sr_ruby_stacktrace_parse(
++            &contents_pointer,
++            &location);
++
++        free(backtrace_contents);
++        if (!report->stacktrace)
++        {
++            *error_message = sr_location_to_string(&location);
++            sr_report_free(report);
++            return NULL;
++        }
++    }
++
+     return report;
+ }
+ 
+@@ -581,6 +610,8 @@ sr_abrt_type_from_analyzer(const char *analyzer)
+         return SR_REPORT_KERNELOOPS;
+     else if (0 == strncmp(analyzer, "Java", 4))
+         return SR_REPORT_JAVA;
++    else if (0 == strncmp(analyzer, "Ruby", 4))
++        return SR_REPORT_RUBY;
+ 
+     return SR_REPORT_INVALID;
+ }
+diff --git a/lib/generic_frame.c b/lib/generic_frame.c
+index eb644b5..d5512c9 100644
+--- a/lib/generic_frame.c
++++ b/lib/generic_frame.c
+@@ -34,6 +34,7 @@ static struct frame_methods* dtable[SR_REPORT_NUM] =
+     [SR_REPORT_KERNELOOPS] = &koops_frame_methods,
+     [SR_REPORT_JAVA] = &java_frame_methods,
+     [SR_REPORT_GDB] = &gdb_frame_methods,
++    [SR_REPORT_RUBY] = &ruby_frame_methods,
+ };
+ 
+ void
+diff --git a/lib/generic_frame.h b/lib/generic_frame.h
+index a61626d..e9f392d 100644
+--- a/lib/generic_frame.h
++++ b/lib/generic_frame.h
+@@ -48,7 +48,8 @@ struct frame_methods
+ };
+ 
+ extern struct frame_methods core_frame_methods, python_frame_methods,
+-       koops_frame_methods, gdb_frame_methods, java_frame_methods;
++       koops_frame_methods, gdb_frame_methods, java_frame_methods,
++       ruby_frame_methods;
+ 
+ void
+ frame_append_bthash_text(struct sr_frame *frame, enum sr_bthash_flags flags,
+diff --git a/lib/generic_stacktrace.c b/lib/generic_stacktrace.c
+index feb1dbc..f98a482 100644
+--- a/lib/generic_stacktrace.c
++++ b/lib/generic_stacktrace.c
+@@ -40,6 +40,7 @@ static struct stacktrace_methods* dtable[SR_REPORT_NUM] =
+     [SR_REPORT_KERNELOOPS] = &koops_stacktrace_methods,
+     [SR_REPORT_JAVA] = &java_stacktrace_methods,
+     [SR_REPORT_GDB] = &gdb_stacktrace_methods,
++    [SR_REPORT_RUBY] = &ruby_stacktrace_methods,
+ };
+ 
+ /* In case stacktrace type supports only one thread, return the stacktrace itself. */
+diff --git a/lib/generic_stacktrace.h b/lib/generic_stacktrace.h
+index 8e3d629..6e1a444 100644
+--- a/lib/generic_stacktrace.h
++++ b/lib/generic_stacktrace.h
+@@ -55,7 +55,8 @@ struct stacktrace_methods
+ };
+ 
+ extern struct stacktrace_methods core_stacktrace_methods, python_stacktrace_methods,
+-       koops_stacktrace_methods, gdb_stacktrace_methods, java_stacktrace_methods;
++       koops_stacktrace_methods, gdb_stacktrace_methods, java_stacktrace_methods,
++       ruby_stacktrace_methods;
+ 
+ /* Macros to generate accessors for the "threads" member. */
+ #define DEFINE_THREADS_FUNC(name, concrete_t) \
+diff --git a/lib/generic_thread.c b/lib/generic_thread.c
+index 59fff1b..16748b5 100644
+--- a/lib/generic_thread.c
++++ b/lib/generic_thread.c
+@@ -131,6 +131,7 @@ static struct thread_methods* dtable[SR_REPORT_NUM] =
+     [SR_REPORT_KERNELOOPS] = &koops_thread_methods,
+     [SR_REPORT_JAVA] = &java_thread_methods,
+     [SR_REPORT_GDB] = &gdb_thread_methods,
++    [SR_REPORT_RUBY] = &ruby_thread_methods,
+ };
+ 
+ struct sr_frame *
+diff --git a/lib/generic_thread.h b/lib/generic_thread.h
+index 005e5ac..8635ad2 100644
+--- a/lib/generic_thread.h
++++ b/lib/generic_thread.h
+@@ -56,7 +56,8 @@ struct thread_methods
+ };
+ 
+ extern struct thread_methods core_thread_methods, python_thread_methods,
+-       koops_thread_methods, gdb_thread_methods, java_thread_methods;
++       koops_thread_methods, gdb_thread_methods, java_thread_methods,
++       ruby_thread_methods;
+ 
+ /* Macros to generate accessors for the "frames" member. */
+ #define DEFINE_FRAMES_FUNC(name, concrete_t) \
+diff --git a/lib/report.c b/lib/report.c
+index 411df64..614b213 100644
+--- a/lib/report.c
++++ b/lib/report.c
+@@ -42,6 +42,7 @@ static char *report_types[] =
+     [SR_REPORT_KERNELOOPS] = "kerneloops",
+     [SR_REPORT_JAVA] = "java",
+     [SR_REPORT_GDB] = "gdb",
++    [SR_REPORT_RUBY] = "ruby",
+     NULL
+ };
+ 
+@@ -189,6 +190,7 @@ sr_report_to_json(struct sr_report *report)
+     case SR_REPORT_PYTHON:
+     case SR_REPORT_KERNELOOPS:
+     case SR_REPORT_JAVA:
++    case SR_REPORT_RUBY:
+         report_type = sr_report_type_to_string(report->report_type);
+         reason = sr_stacktrace_get_reason(report->stacktrace);
+         break;
+@@ -383,6 +385,7 @@ sr_report_from_json(struct sr_json_value *root, char **error_message)
+         case SR_REPORT_PYTHON:
+         case SR_REPORT_KERNELOOPS:
+         case SR_REPORT_JAVA:
++        case SR_REPORT_RUBY:
+             report->stacktrace = sr_stacktrace_from_json(report->report_type, problem, error_message);
+             break;
+         default:
+diff --git a/lib/ruby_frame.c b/lib/ruby_frame.c
+new file mode 100644
+index 0000000..9a9a317
+--- /dev/null
++++ b/lib/ruby_frame.c
+@@ -0,0 +1,504 @@
++/*
++    ruby_frame.c
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include "strbuf.h"
++#include "json.h"
++#include "generic_frame.h"
++#include "thread.h"
++#include "stacktrace.h"
++#include "internal_utils.h"
++#include <string.h>
++#include <inttypes.h>
++#include <ctype.h>
++
++/* Method table */
++
++static void
++ruby_append_bthash_text(struct sr_ruby_frame *frame, enum sr_bthash_flags flags,
++                          struct sr_strbuf *strbuf);
++static void
++ruby_append_duphash_text(struct sr_ruby_frame *frame, enum sr_duphash_flags flags,
++                           struct sr_strbuf *strbuf);
++
++DEFINE_NEXT_FUNC(ruby_next, struct sr_frame, struct sr_ruby_frame)
++DEFINE_SET_NEXT_FUNC(ruby_set_next, struct sr_frame, struct sr_ruby_frame)
++
++struct frame_methods ruby_frame_methods =
++{
++    .append_to_str = (append_to_str_fn_t) sr_ruby_frame_append_to_str,
++    .next = (next_frame_fn_t) ruby_next,
++    .set_next = (set_next_frame_fn_t) ruby_set_next,
++    .cmp = (frame_cmp_fn_t) sr_ruby_frame_cmp,
++    .cmp_distance = (frame_cmp_fn_t) sr_ruby_frame_cmp_distance,
++    .frame_append_bthash_text =
++        (frame_append_bthash_text_fn_t) ruby_append_bthash_text,
++    .frame_append_duphash_text =
++        (frame_append_duphash_text_fn_t) ruby_append_duphash_text,
++    .frame_free = (frame_free_fn_t) sr_ruby_frame_free,
++};
++
++/* Public functions */
++
++struct sr_ruby_frame *
++sr_ruby_frame_new()
++{
++    struct sr_ruby_frame *frame =
++        sr_malloc(sizeof(struct sr_ruby_frame));
++
++    sr_ruby_frame_init(frame);
++    return frame;
++}
++
++void
++sr_ruby_frame_init(struct sr_ruby_frame *frame)
++{
++    memset(frame, 0, sizeof(struct sr_ruby_frame));
++    frame->type = SR_REPORT_RUBY;
++}
++
++void
++sr_ruby_frame_free(struct sr_ruby_frame *frame)
++{
++    if (!frame)
++        return;
++
++    free(frame->file_name);
++    free(frame->function_name);
++    free(frame);
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_dup(struct sr_ruby_frame *frame, bool siblings)
++{
++    struct sr_ruby_frame *result = sr_ruby_frame_new();
++    memcpy(result, frame, sizeof(struct sr_ruby_frame));
++
++    /* Handle siblings. */
++    if (siblings)
++    {
++        if (result->next)
++            result->next = sr_ruby_frame_dup(result->next, true);
++    }
++    else
++        result->next = NULL; /* Do not copy that. */
++
++    /* Duplicate all strings. */
++    if (result->file_name)
++        result->file_name = sr_strdup(result->file_name);
++
++    if (result->function_name)
++        result->function_name = sr_strdup(result->function_name);
++
++    return result;
++}
++
++int
++sr_ruby_frame_cmp(struct sr_ruby_frame *frame1,
++                  struct sr_ruby_frame *frame2)
++{
++    /* function_name */
++    int function_name = sr_strcmp0(frame1->function_name,
++                                   frame2->function_name);
++    if (function_name != 0)
++        return function_name;
++
++    /* file_name */
++    int file_name = sr_strcmp0(frame1->file_name,
++                               frame2->file_name);
++    if (file_name != 0)
++        return file_name;
++
++    /* file_line */
++    int file_line = frame1->file_line - frame2->file_line;
++    if (file_line != 0)
++        return file_line;
++
++    /* special_function */
++    int special_function = frame1->special_function - frame2->special_function;
++    if (special_function != 0)
++        return special_function;
++
++    /* block_level */
++    int block_level = frame1->block_level - frame2->block_level;
++    if (block_level != 0)
++            return block_level;
++
++    /* rescue_level */
++    int rescue_level = frame1->rescue_level - frame2->rescue_level;
++    if (rescue_level != 0)
++            return rescue_level;
++
++    return 0;
++}
++
++int
++sr_ruby_frame_cmp_distance(struct sr_ruby_frame *frame1,
++                           struct sr_ruby_frame *frame2)
++{
++    /* function_name */
++    int function_name = sr_strcmp0(frame1->function_name,
++                                   frame2->function_name);
++    if (function_name != 0)
++        return function_name;
++
++    /* file_name */
++    int file_name = sr_strcmp0(frame1->file_name,
++                               frame2->file_name);
++    if (file_name != 0)
++        return file_name;
++
++    /* special_function */
++    int special_function = frame1->special_function - frame2->special_function;
++    if (special_function != 0)
++        return special_function;
++
++    return 0;
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_append(struct sr_ruby_frame *dest,
++                     struct sr_ruby_frame *item)
++{
++    if (!dest)
++        return item;
++
++    struct sr_ruby_frame *dest_loop = dest;
++    while (dest_loop->next)
++        dest_loop = dest_loop->next;
++
++    dest_loop->next = item;
++    return dest;
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_parse(const char **input,
++                    struct sr_location *location)
++{
++    const char *local_input = *input;
++    struct sr_ruby_frame *frame = sr_ruby_frame_new();
++
++    /* take everything before the backtick
++     * /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++     */
++    char *filename_lineno_in = NULL;
++    if (!sr_parse_char_cspan(&local_input, "`", &filename_lineno_in))
++    {
++        location->message = sr_strdup("Unable to find the '`' character "
++                                      "identifying the beginning of function name.");
++        goto fail;
++    }
++
++    size_t l = strlen(filename_lineno_in);
++    location->column += l;
++
++    char *p = filename_lineno_in + l;
++
++    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     *                                                           ^^^^
++     */
++    p -= strlen(":in ");
++    if (p < filename_lineno_in || 0 != strcmp(":in ", p))
++    {
++        location->column -= strlen(":in ");
++        location->message = sr_strdup("Unable to find ':in ' preceding the "
++                                      "backtick character.");
++        goto fail;
++    }
++
++    /* Find the beginning of the line number. */
++    *p = '\0';
++    do {
++        p--;
++    } while (p >= filename_lineno_in && isdigit(*p));
++    p++;
++
++    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     *                                                         ^^
++     */
++    char *p_copy = p;
++    int lineno_len = sr_parse_uint32((const char **)&p_copy, &frame->file_line);
++    if (lineno_len <= 0)
++    {
++        location->message = sr_strdup("Unable to find line number before ':in '");
++        goto fail;
++    }
++
++    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     *                                                        ^
++     */
++    p--;
++    if (p < filename_lineno_in || *p != ':')
++    {
++        location->column -= lineno_len;
++        location->message = sr_strdup("Unable to fin the ':' character "
++                                      "preceding the line number");
++        goto fail;
++    }
++
++    /* Everything before the colon is the file name. */
++    *p = '\0';
++    frame->file_name = filename_lineno_in;
++    filename_lineno_in = NULL;
++
++    if(!sr_skip_char(&local_input, '`'))
++    {
++        location->message = sr_strdup("Unable to find the '`' character "
++                                      "identifying the beginning of function name.");
++        goto fail;
++    }
++
++    location->column++;
++
++    /* The part in quotes can look like:
++     * `rescue in rescue in block (3 levels) in func'
++     * parse the number of rescues and blocks
++     */
++    while (sr_skip_string(&local_input, "rescue in "))
++    {
++        frame->rescue_level++;
++        location->column += strlen("rescue in ");
++    }
++
++    if (sr_skip_string(&local_input, "block in "))
++    {
++        frame->block_level = 1;
++        location->column += strlen("block in");
++    }
++    else if(sr_skip_string(&local_input, "block ("))
++    {
++        location->column += strlen("block (");
++
++        int len = sr_parse_uint32(&local_input, &frame->block_level);
++        if (len == 0 || !sr_skip_string(&local_input, " levels) in "))
++        {
++            location->message = sr_strdup("Unable to parse block depth.");
++            goto fail;
++        }
++        location->column += len + strlen(" levels) in ");
++    }
++
++    if (sr_skip_char(&local_input, '<'))
++    {
++        location->column++;
++        frame->special_function = true;
++    }
++
++    if (!sr_parse_char_cspan(&local_input, "'>", &frame->function_name))
++    {
++        location->message = sr_strdup("Unable to find the \"'\" character "
++                                      "delimiting the function name.");
++        goto fail;
++    }
++    location->column += strlen(frame->function_name);
++
++    if (frame->special_function)
++    {
++        if (!sr_skip_char(&local_input, '>'))
++        {
++            location->message = sr_strdup("Unable to find the \">\" character "
++                                          "delimiting the function name.");
++            goto fail;
++        }
++        location->column++;
++    }
++
++    if (!sr_skip_char(&local_input, '\''))
++    {
++        location->message = sr_strdup("Unable to find the \"'\" character "
++                                      "delimiting the function name.");
++        goto fail;
++    }
++    location->column++;
++
++    *input = local_input;
++    return frame;
++
++fail:
++    sr_ruby_frame_free(frame);
++    free(filename_lineno_in);
++    return NULL;
++}
++
++char *
++sr_ruby_frame_to_json(struct sr_ruby_frame *frame)
++{
++    struct sr_strbuf *strbuf = sr_strbuf_new();
++
++    /* Source file name. */
++    if (frame->file_name)
++    {
++        sr_strbuf_append_str(strbuf, ",   \"file_name\": ");
++        sr_json_append_escaped(strbuf, frame->file_name);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
++    /* Source file line. */
++    if (frame->file_line)
++    {
++        sr_strbuf_append_strf(strbuf,
++                              ",   \"file_line\": %"PRIu32"\n",
++                              frame->file_line);
++    }
++
++    /* Function name / special function. */
++    if (frame->function_name)
++    {
++        if (frame->special_function)
++            sr_strbuf_append_str(strbuf, ",   \"special_function\": ");
++        else
++            sr_strbuf_append_str(strbuf, ",   \"function_name\": ");
++
++        sr_json_append_escaped(strbuf, frame->function_name);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
++    /* Block level. */
++    if (frame->block_level > 0)
++    {
++        sr_strbuf_append_strf(strbuf,
++                              ",   \"block_level\": %"PRIu32"\n",
++                              frame->block_level);
++    }
++
++    /* Rescue level. */
++    if (frame->rescue_level > 0)
++    {
++        sr_strbuf_append_strf(strbuf,
++                              ",   \"rescue_level\": %"PRIu32"\n",
++                              frame->rescue_level);
++    }
++
++
++    strbuf->buf[0] = '{';
++    sr_strbuf_append_char(strbuf, '}');
++    return sr_strbuf_free_nobuf(strbuf);
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_from_json(struct sr_json_value *root, char **error_message)
++{
++    if (!JSON_CHECK_TYPE(root, SR_JSON_OBJECT, "frame"))
++        return NULL;
++
++    struct sr_ruby_frame *result = sr_ruby_frame_new();
++    struct sr_json_value *val;
++
++    /* Source file name */
++    if ((val = json_element(root, "file_name")))
++    {
++        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "file_name"))
++            goto fail;
++
++        result->file_name = sr_strdup(val->u.string.ptr);
++    }
++
++    /* Function name / special function. */
++    if ((val = json_element(root, "function_name")))
++    {
++        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "function_name"))
++            goto fail;
++
++        result->special_function = false;
++        result->function_name = sr_strdup(val->u.string.ptr);
++    }
++    else if ((val = json_element(root, "special_function")))
++    {
++        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "special_function"))
++            goto fail;
++
++        result->special_function = true;
++        result->function_name = sr_strdup(val->u.string.ptr);
++    }
++
++    bool success =
++        JSON_READ_UINT32(root, "file_line", &result->file_line) &&
++        JSON_READ_UINT32(root, "block_level", &result->block_level) &&
++        JSON_READ_UINT32(root, "rescue_level", &result->rescue_level);
++
++    if (!success)
++        goto fail;
++
++    return result;
++
++fail:
++    sr_ruby_frame_free(result);
++    return NULL;
++}
++
++void
++sr_ruby_frame_append_to_str(struct sr_ruby_frame *frame,
++                            struct sr_strbuf *dest)
++{
++    for (int i = 0; i < frame->rescue_level; i++)
++    {
++        sr_strbuf_append_str(dest, "rescue in ");
++    }
++
++    if (frame->block_level == 1)
++    {
++        sr_strbuf_append_str(dest, "block in ");
++    }
++    else if (frame->block_level > 1)
++    {
++        sr_strbuf_append_strf(dest, "block (%u levels) in ", (unsigned)frame->block_level);
++    }
++
++    sr_strbuf_append_strf(dest, "%s%s%s",
++                          (frame->special_function ? "<" : ""),
++                          (frame->function_name ? frame->function_name : "??"),
++                          (frame->special_function ? ">" : ""));
++
++    if (frame->file_name)
++    {
++        sr_strbuf_append_strf(dest, " in %s", frame->file_name);
++
++        if (frame->file_line)
++        {
++            sr_strbuf_append_strf(dest, ":%d", frame->file_line);
++        }
++    }
++}
++
++static void
++ruby_append_bthash_text(struct sr_ruby_frame *frame, enum sr_bthash_flags flags,
++                        struct sr_strbuf *strbuf)
++{
++    sr_strbuf_append_strf(strbuf,
++                          "%s, %"PRIu32", %s, %d, %"PRIu32", %"PRIu32"\n",
++                          OR_UNKNOWN(frame->file_name),
++                          frame->file_line,
++                          OR_UNKNOWN(frame->function_name),
++                          frame->special_function,
++                          frame->block_level,
++                          frame->rescue_level);
++}
++
++static void
++ruby_append_duphash_text(struct sr_ruby_frame *frame, enum sr_duphash_flags flags,
++                         struct sr_strbuf *strbuf)
++{
++    /* filename:line */
++    sr_strbuf_append_strf(strbuf, "%s:%"PRIu32"\n",
++                          OR_UNKNOWN(frame->file_name),
++                          frame->file_line);
++}
+diff --git a/lib/ruby_stacktrace.c b/lib/ruby_stacktrace.c
+new file mode 100644
+index 0000000..4adde08
+--- /dev/null
++++ b/lib/ruby_stacktrace.c
+@@ -0,0 +1,387 @@
++/*
++    ruby_stacktrace.c
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "location.h"
++#include "utils.h"
++#include "json.h"
++#include "sha1.h"
++#include "report_type.h"
++#include "strbuf.h"
++#include "generic_stacktrace.h"
++#include "generic_thread.h"
++#include "internal_utils.h"
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <inttypes.h>
++
++/* Method tables */
++
++static void
++ruby_append_bthash_text(struct sr_ruby_stacktrace *stacktrace, enum sr_bthash_flags flags,
++                          struct sr_strbuf *strbuf);
++
++DEFINE_FRAMES_FUNC(ruby_frames, struct sr_ruby_stacktrace)
++DEFINE_SET_FRAMES_FUNC(ruby_set_frames, struct sr_ruby_stacktrace)
++DEFINE_PARSE_WRAPPER_FUNC(ruby_parse, SR_REPORT_RUBY)
++
++struct thread_methods ruby_thread_methods =
++{
++    .frames = (frames_fn_t) ruby_frames,
++    .set_frames = (set_frames_fn_t) ruby_set_frames,
++    .cmp = (thread_cmp_fn_t) NULL,
++    .frame_count = (frame_count_fn_t) thread_frame_count,
++    .next = (next_thread_fn_t) thread_no_next_thread,
++    .set_next = (set_next_thread_fn_t) NULL,
++    .thread_append_bthash_text =
++        (thread_append_bthash_text_fn_t) thread_no_bthash_text,
++    .thread_free = (thread_free_fn_t) sr_ruby_stacktrace_free,
++    .remove_frame = (remove_frame_fn_t) thread_remove_frame,
++    .remove_frames_above =
++        (remove_frames_above_fn_t) thread_remove_frames_above,
++    .thread_dup = (thread_dup_fn_t) sr_ruby_stacktrace_dup,
++    .normalize = (normalize_fn_t) thread_no_normalization,
++};
++
++struct stacktrace_methods ruby_stacktrace_methods =
++{
++    .parse = (parse_fn_t) ruby_parse,
++    .parse_location = (parse_location_fn_t) sr_ruby_stacktrace_parse,
++    .to_short_text = (to_short_text_fn_t) stacktrace_to_short_text,
++    .to_json = (to_json_fn_t) sr_ruby_stacktrace_to_json,
++    .from_json = (from_json_fn_t) sr_ruby_stacktrace_from_json,
++    .get_reason = (get_reason_fn_t) sr_ruby_stacktrace_get_reason,
++    .find_crash_thread = (find_crash_thread_fn_t) stacktrace_one_thread_only,
++    .threads = (threads_fn_t) stacktrace_one_thread_only,
++    .set_threads = (set_threads_fn_t) NULL,
++    .stacktrace_free = (stacktrace_free_fn_t) sr_ruby_stacktrace_free,
++    .stacktrace_append_bthash_text =
++        (stacktrace_append_bthash_text_fn_t) ruby_append_bthash_text,
++};
++
++/* Public functions */
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_new()
++{
++    struct sr_ruby_stacktrace *stacktrace =
++        sr_malloc(sizeof(struct sr_ruby_stacktrace));
++
++    sr_ruby_stacktrace_init(stacktrace);
++    return stacktrace;
++}
++
++void
++sr_ruby_stacktrace_init(struct sr_ruby_stacktrace *stacktrace)
++{
++    memset(stacktrace, 0, sizeof(struct sr_ruby_stacktrace));
++    stacktrace->type = SR_REPORT_RUBY;
++}
++
++void
++sr_ruby_stacktrace_free(struct sr_ruby_stacktrace *stacktrace)
++{
++    if (!stacktrace)
++        return;
++
++    while (stacktrace->frames)
++    {
++        struct sr_ruby_frame *frame = stacktrace->frames;
++        stacktrace->frames = frame->next;
++        sr_ruby_frame_free(frame);
++    }
++
++    free(stacktrace->exception_name);
++    free(stacktrace);
++}
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_dup(struct sr_ruby_stacktrace *stacktrace)
++{
++    struct sr_ruby_stacktrace *result = sr_ruby_stacktrace_new();
++    memcpy(result, stacktrace, sizeof(struct sr_ruby_stacktrace));
++
++    if (result->exception_name)
++        result->exception_name = sr_strdup(result->exception_name);
++
++    if (result->frames)
++        result->frames = sr_ruby_frame_dup(result->frames, true);
++
++    return result;
++}
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_parse(const char **input,
++                         struct sr_location *location)
++{
++    const char *local_input = *input;
++    struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_new();
++    char *message_and_class = NULL;
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++     */
++    stacktrace->frames = sr_ruby_frame_parse(&local_input, location);
++    if (!stacktrace->frames)
++    {
++        location->message = sr_asprintf("Topmost stacktrace frame not found: %s",
++                            location->message ? location->message : "(unknown reason)");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                              ^^
++     */
++    if (!sr_skip_string(&local_input, ": "))
++    {
++        location->message = sr_strdup("Unable to find the colon after first function name.");
++        goto fail;
++    }
++    location->column += strlen(": ");
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++     */
++    if (!sr_parse_char_cspan(&local_input, "\t", &message_and_class))
++    {
++        location->message = sr_strdup("Unable to find the exception type and message.");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                                    ^^
++     */
++    size_t l = strlen(message_and_class);
++    location->column += l;
++
++    char *p = message_and_class + l - 1;
++    if (p < message_and_class || *p != '\n')
++    {
++        location->column--;
++        location->message = sr_strdup("Unable to find the new line character after "
++                                      "the end of exception class");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                                   ^
++     */
++    p--;
++    if (p < message_and_class || *p != ')')
++    {
++        location->column -= 2;
++        location->message = sr_strdup("Unable to find the ')' character identifying "
++                                      "the end of exception class");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                   ^^^^^^^^^^^^^^^^
++     */
++    *p = '\0';
++    do {
++        p--;
++    } while (p >= message_and_class && *p != '(');
++    p++;
++
++    if (strlen(p) <= 0)
++    {
++        location->message = sr_strdup("Unable to find the '(' character identifying "
++                                      "the beginning of the exception class");
++        goto fail;
++    }
++    stacktrace->exception_name = sr_strdup(p);
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                  ^
++     */
++    p--;
++    if (p < message_and_class || *p != '(')
++    {
++        location->message = sr_strdup("Unable to find the '(' character identifying "
++                                      "the beginning of the exception class");
++        goto fail;
++    }
++
++    /* Throw away the message, it may contain sensitive data. */
++    free(message_and_class);
++    message_and_class = p = NULL;
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                   we're here now, increment the line ^^
++     */
++    location->column = 0;
++    location->line++;
++
++    struct sr_ruby_frame *last_frame = stacktrace->frames;
++    while (*local_input)
++    {
++        /* The exception message can continue on the lines after the topmost frame
++         * - skip those.
++         */
++        int skipped = sr_skip_string(&local_input, "\tfrom ");
++        if (!skipped)
++        {
++            location->message = sr_strdup("Frame header not found.");
++            goto fail;
++        }
++        location->column += skipped;
++
++        last_frame->next = sr_ruby_frame_parse(&local_input, location);
++        if (!last_frame->next)
++        {
++            /* location->message is already set */
++            goto fail;
++        }
++
++        /* Eat newline (except at the end of file). */
++        if (!sr_skip_char(&local_input, '\n') && *local_input != '\0')
++        {
++            location->message = sr_strdup("Expected newline after stacktrace frame.");
++            goto fail;
++        }
++        location->column = 0;
++        location->line++;
++        last_frame = last_frame->next;
++    }
++
++    *input = local_input;
++    return stacktrace;
++
++fail:
++    sr_ruby_stacktrace_free(stacktrace);
++    free(message_and_class);
++    return NULL;
++}
++
++char *
++sr_ruby_stacktrace_to_json(struct sr_ruby_stacktrace *stacktrace)
++{
++    struct sr_strbuf *strbuf = sr_strbuf_new();
++
++    /* Exception class name. */
++    if (stacktrace->exception_name)
++    {
++        sr_strbuf_append_str(strbuf, ",   \"exception_name\": ");
++        sr_json_append_escaped(strbuf, stacktrace->exception_name);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
++    /* Frames. */
++    if (stacktrace->frames)
++    {
++        struct sr_ruby_frame *frame = stacktrace->frames;
++        sr_strbuf_append_str(strbuf, ",   \"stacktrace\":\n");
++        while (frame)
++        {
++            if (frame == stacktrace->frames)
++                sr_strbuf_append_str(strbuf, "      [ ");
++            else
++                sr_strbuf_append_str(strbuf, "      , ");
++
++            char *frame_json = sr_ruby_frame_to_json(frame);
++            char *indented_frame_json = sr_indent_except_first_line(frame_json, 8);
++            sr_strbuf_append_str(strbuf, indented_frame_json);
++            free(indented_frame_json);
++            free(frame_json);
++            frame = frame->next;
++            if (frame)
++                sr_strbuf_append_str(strbuf, "\n");
++        }
++
++        sr_strbuf_append_str(strbuf, " ]\n");
++    }
++
++    if (strbuf->len > 0)
++        strbuf->buf[0] = '{';
++    else
++        sr_strbuf_append_char(strbuf, '{');
++
++    sr_strbuf_append_char(strbuf, '}');
++    return sr_strbuf_free_nobuf(strbuf);
++}
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_from_json(struct sr_json_value *root, char **error_message)
++{
++    if (!JSON_CHECK_TYPE(root, SR_JSON_OBJECT, "stacktrace"))
++        return NULL;
++
++    struct sr_ruby_stacktrace *result = sr_ruby_stacktrace_new();
++
++    /* Exception name. */
++    if (!JSON_READ_STRING(root, "exception_name", &result->exception_name))
++        goto fail;
++
++    /* Frames. */
++    struct sr_json_value *stacktrace = json_element(root, "stacktrace");
++    if (stacktrace)
++    {
++        if (!JSON_CHECK_TYPE(stacktrace, SR_JSON_ARRAY, "stacktrace"))
++            goto fail;
++
++        struct sr_json_value *frame_json;
++        FOR_JSON_ARRAY(stacktrace, frame_json)
++        {
++            struct sr_ruby_frame *frame = sr_ruby_frame_from_json(frame_json,
++                error_message);
++
++            if (!frame)
++                goto fail;
++
++            result->frames = sr_ruby_frame_append(result->frames, frame);
++        }
++    }
++
++    return result;
++
++fail:
++    sr_ruby_stacktrace_free(result);
++    return NULL;
++}
++
++char *
++sr_ruby_stacktrace_get_reason(struct sr_ruby_stacktrace *stacktrace)
++{
++    char *exc = "Unknown error";
++    char *file = "<unknown>";
++    uint32_t line = 0;
++
++    struct sr_ruby_frame *frame = stacktrace->frames;
++    if (frame)
++    {
++        file = frame->file_name;
++        line = frame->file_line;
++    }
++
++    if (stacktrace->exception_name)
++        exc = stacktrace->exception_name;
++
++    return sr_asprintf("%s in %s:%"PRIu32, exc, file, line);
++}
++
++static void
++ruby_append_bthash_text(struct sr_ruby_stacktrace *stacktrace, enum sr_bthash_flags flags,
++                        struct sr_strbuf *strbuf)
++{
++    sr_strbuf_append_strf(strbuf, "Exception: %s\n", OR_UNKNOWN(stacktrace->exception_name));
++    sr_strbuf_append_char(strbuf, '\n');
++}
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index d69f493..0eb33a5 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -1,5 +1,6 @@
+ EXTRA_DIST = gdb_stacktraces \
+              java_stacktraces \
++             ruby_stacktraces \
+              json_files \
+              kerneloopses \
+              programs \
+@@ -50,6 +51,8 @@ TESTSUITE_AT =		\
+   core_frame.at         \
+   core_thread.at        \
+   core_stacktrace.at    \
++  ruby_frame.at		\
++  ruby_stacktrace.at	\
+   normalize.at  	\
+   metrics.at 		\
+   cluster.at  		\
+diff --git a/tests/ruby_frame.at b/tests/ruby_frame.at
+new file mode 100644
+index 0000000..1b1b9d0
+--- /dev/null
++++ b/tests/ruby_frame.at
+@@ -0,0 +1,285 @@
++# Checking the satyr. -*- Autotest -*-
++
++AT_BANNER([Ruby frames])
++
++AT_TESTFUN([sr_ruby_frame_parse],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++void
++check(char *input, char *file, unsigned line,
++      char *function, bool special, unsigned block_level, unsigned rescue_level)
++{
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_frame *frame;
++  frame = sr_ruby_frame_parse(&input, &location);
++
++  assert(frame);
++  assert(frame->file_name && 0 == strcmp(frame->file_name, file));
++  assert(frame->file_line == line);
++  assert(frame->function_name && 0 == strcmp(frame->function_name, function));
++  assert(!!frame->special_function == !!special);
++  assert(frame->block_level == block_level);
++  assert(frame->rescue_level == rescue_level);
++  assert(*input == '\0');
++
++  sr_ruby_frame_free(frame);
++}
++
++int
++main(void)
++{
++  check("/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'",
++        "/usr/share/ruby/vendor_ruby/will_crash.rb", 13, "func", false, 2, 1);
++
++  check("/home/u/work/will:crash/will_crash.rb:30:in `block in <class:WillClass>'",
++        "/home/u/work/will:crash/will_crash.rb", 30, "class:WillClass", true, 1, 0);
++
++  check("./will_ruby_raise:8:in `<main>'",
++        "./will_ruby_raise", 8, "main", true, 0, 0);
++
++  /* try failing */
++  struct sr_location location;
++  sr_location_init(&location);
++  char *input = "i;dikasdfxc";
++
++  struct sr_ruby_frame *frame  = sr_ruby_frame_parse(&input, &location);
++  assert(!frame);
++  assert(location.message);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_cmp],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
++  assert(frame1 != frame2);
++
++  frame2->file_line = 9000;
++  assert(0 != sr_ruby_frame_cmp(frame1, frame2));
++
++  sr_ruby_frame_free(frame1);
++  sr_ruby_frame_free(frame2);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_dup],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_dup(frame1, false);
++
++  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
++  assert(frame1 != frame2);
++  assert(frame1->function_name != frame2->function_name);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_append],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_new();
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_new();
++
++  assert(frame1->next == frame2->next);
++  assert(frame1->next == NULL);
++
++  sr_ruby_frame_append(frame1, frame2);
++  assert(frame1->next == frame2);
++
++  sr_ruby_frame_free(frame1);
++  sr_ruby_frame_free(frame2);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_to_json],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  char *expected = "{   \"file_name\": \"/usr/share/ruby/vendor_ruby/will_crash.rb\"\n"
++                   ",   \"file_line\": 13\n"
++                   ",   \"function_name\": \"func\"\n"
++                   ",   \"block_level\": 2\n"
++                   ",   \"rescue_level\": 1\n"
++                   "}";
++
++  char *json = sr_ruby_frame_to_json(frame1);
++  assert(0 == strcmp(json, expected));
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_from_json],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "json.h"
++#include "location.h"
++#include <assert.h>
++
++void
++check(char *input)
++{
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  char *json = sr_ruby_frame_to_json(frame1);
++  char *error = NULL;
++
++  struct sr_json_value *root = sr_json_parse(json, &error);
++  assert(root);
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_from_json(root, &error);
++  assert(frame2);
++  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
++
++  return 0;
++}
++
++int
++main(void)
++{
++  check("/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'");
++  check("/usr/share/ruby/vendor:ruby/will_crash.rb:13:in `block (22 levels) in <func>'");
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_append_to_str],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include "strbuf.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  struct sr_strbuf *strbuf = sr_strbuf_new();
++  sr_ruby_frame_append_to_str(frame1, strbuf);
++  char *result = sr_strbuf_free_nobuf(strbuf);
++
++  assert(0 == strcmp(result, "rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13"));
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_generic_functions],
++[[
++#include "ruby/frame.h"
++#include "frame.h"
++#include "utils.h"
++#include "location.h"
++#include "strbuf.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  assert(sr_frame_next(frame1) == NULL);
++
++  struct sr_strbuf *strbuf = sr_strbuf_new();
++  sr_frame_append_to_str((struct sr_frame*)frame1, strbuf);
++  char *result = sr_strbuf_free_nobuf(strbuf);
++
++  assert(0 == strcmp(result, "rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13"));
++
++  sr_frame_free((struct sr_frame*)frame1);
++  return 0;
++}
++]])
+diff --git a/tests/ruby_stacktrace.at b/tests/ruby_stacktrace.at
+new file mode 100644
+index 0000000..5a2d901
+--- /dev/null
++++ b/tests/ruby_stacktrace.at
+@@ -0,0 +1,316 @@
++# Checking the satyr. -*- Autotest -*-
++
++AT_BANNER([Ruby stacktrace])
++
++AT_TESTFUN([sr_ruby_stacktrace_parse],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++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)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_parse(&input, &location);
++  assert(stacktrace);
++  assert(*input == '\0');
++  assert(0 == strcmp(stacktrace->exception_name, exc_name));
++
++  struct sr_ruby_frame *frame = stacktrace->frames;
++  int i = 0;
++  while (frame)
++  {
++    if (i==0 && top_frame)
++    {
++      assert(sr_ruby_frame_cmp(frame, top_frame) == 0);
++    }
++    else if (i == 1 && second_frame)
++    {
++      assert(sr_ruby_frame_cmp(frame, second_frame) == 0);
++    }
++
++    frame = frame->next;
++    i++;
++  }
++
++  assert(i == frame_count);
++  if (frame && bottom_frame)
++  {
++    assert(sr_ruby_frame_cmp(frame, bottom_frame) == 0);
++  }
++
++  sr_ruby_stacktrace_free(stacktrace);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  struct sr_ruby_frame top = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/ruby/vendor_ruby/will_crash.rb",
++    .file_line = 13,
++    .special_function = false,
++    .function_name = "func",
++    .block_level = 2,
++    .rescue_level = 1
++  };
++  struct sr_ruby_frame second = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/ruby/vendor_ruby/will_crash.rb",
++    .file_line = 10,
++    .special_function = false,
++    .function_name = "func",
++    .block_level = 2,
++    .rescue_level = 0
++  };
++  struct sr_ruby_frame bottom = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/bin/will_ruby_raise",
++    .file_line = 8,
++    .special_function = true,
++    .function_name = "main",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  check("../../ruby_stacktraces/ruby-01", "Wrap::MyException", 21, &top, &second, &bottom);
++
++  struct sr_ruby_frame top2 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb",
++    .file_line = 173,
++    .special_function = false,
++    .function_name = "aref_prefix",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  struct sr_ruby_frame second2 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb",
++    .file_line = 181,
++    .special_function = false,
++    .function_name = "aref",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  check("../../ruby_stacktraces/ruby-02", "NotImplementedError", 37, &top2, &second2, NULL);
++
++  check("../../ruby_stacktraces/ruby-03", "RuntimeError", 4, NULL, NULL, NULL);
++
++  struct sr_ruby_frame top4 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/rubygems/rubygems/basic_specification.rb",
++    .file_line = 50,
++    .special_function = false,
++    .function_name = "contains_requirable_file?",
++    .block_level = 3,
++    .rescue_level = 0
++  };
++  struct sr_ruby_frame second4 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/rubygems/rubygems/basic_specification.rb",
++    .file_line = 50,
++    .special_function = false,
++    .function_name = "each",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  check("../../ruby_stacktraces/ruby-04", "SignalException", 39, &top4, &second4, NULL);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_dup],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++  struct sr_ruby_stacktrace *stacktrace2 = sr_ruby_stacktrace_dup(stacktrace1);
++
++  assert(stacktrace1 != stacktrace2);
++  assert(0 == strcmp(stacktrace1->exception_name, stacktrace2->exception_name));
++
++  struct sr_ruby_frame *f1 = stacktrace1->frames;
++  struct sr_ruby_frame *f2 = stacktrace2->frames;
++
++  while (f1 && f2)
++  {
++    assert(0 == sr_ruby_frame_cmp(f1, f2));
++    f1 = f1->next;
++    f2 = f2->next;
++  }
++  assert(f1 == NULL);
++  assert(f2 == NULL);
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  sr_ruby_stacktrace_free(stacktrace2);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  check("../../ruby_stacktraces/ruby-01");
++  check("../../ruby_stacktraces/ruby-02");
++  check("../../ruby_stacktraces/ruby-03");
++  check("../../ruby_stacktraces/ruby-04");
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_get_reason],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++int
++main(void)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string("../../ruby_stacktraces/ruby-03", &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++
++  char *reason = sr_ruby_stacktrace_get_reason(stacktrace1);
++  char *expected = "RuntimeError in /usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:103";
++  assert(0 == strcmp(reason, expected));
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  free(file_contents);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_to_json],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename, char *json_filename)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++
++  char *expected = sr_file_to_string(json_filename, &error_message);
++  char *json = sr_ruby_stacktrace_to_json(stacktrace1);
++
++  assert(0 == strcmp(json, expected));
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  free(json);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  check("../../ruby_stacktraces/ruby-01", "../../ruby_stacktraces/ruby-01-expected-json");
++  check("../../ruby_stacktraces/ruby-02", "../../ruby_stacktraces/ruby-02-expected-json");
++  check("../../ruby_stacktraces/ruby-03", "../../ruby_stacktraces/ruby-03-expected-json");
++  check("../../ruby_stacktraces/ruby-04", "../../ruby_stacktraces/ruby-04-expected-json");
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_from_json],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++
++  char *json = sr_ruby_stacktrace_to_json(stacktrace1);
++  struct sr_ruby_stacktrace *stacktrace2 = sr_stacktrace_from_json_text(SR_REPORT_RUBY, json, &error_message);
++
++  assert(0 == strcmp(stacktrace1->exception_name, stacktrace2->exception_name));
++
++  struct sr_ruby_frame *f1 = stacktrace1->frames;
++  struct sr_ruby_frame *f2 = stacktrace2->frames;
++
++  while (f1 && f2)
++  {
++    assert(0 == sr_ruby_frame_cmp(f1, f2));
++    f1 = f1->next;
++    f2 = f2->next;
++  }
++  assert(f1 == NULL);
++  assert(f2 == NULL);
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  sr_ruby_stacktrace_free(stacktrace2);
++  free(json);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  check("../../ruby_stacktraces/ruby-01");
++  check("../../ruby_stacktraces/ruby-02");
++  check("../../ruby_stacktraces/ruby-03");
++  check("../../ruby_stacktraces/ruby-04");
++
++  return 0;
++}
++]])
+diff --git a/tests/ruby_stacktraces/ruby-01 b/tests/ruby_stacktraces/ruby-01
+new file mode 100644
+index 0000000..a635dd9
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-01
+@@ -0,0 +1,23 @@
++/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func': Exception
++successfully
++raised. (Wrap::MyException)
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:10:in `block (2 levels) in func'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:9:in `times'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:9:in `block in func'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:8:in `times'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:8:in `func'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:30:in `block in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:28:in `times'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:28:in `rescue in rescue in rescue in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:25:in `rescue in rescue in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:22:in `rescue in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:19:in `<class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:6:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/bin/will_ruby_raise:11:in `block (2 levels) in <module:WillModule>'
++	from /usr/bin/will_ruby_raise:10:in `times'
++	from /usr/bin/will_ruby_raise:10:in `block in <module:WillModule>'
++	from /usr/bin/will_ruby_raise:9:in `times'
++	from /usr/bin/will_ruby_raise:9:in `<module:WillModule>'
++	from /usr/bin/will_ruby_raise:8:in `<main>'
+diff --git a/tests/ruby_stacktraces/ruby-01-expected-json b/tests/ruby_stacktraces/ruby-01-expected-json
+new file mode 100644
+index 0000000..493fb18
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-01-expected-json
+@@ -0,0 +1,97 @@
++{   "exception_name": "Wrap::MyException"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 13
++        ,   "function_name": "func"
++        ,   "block_level": 2
++        ,   "rescue_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 10
++        ,   "function_name": "func"
++        ,   "block_level": 2
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 9
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 9
++        ,   "function_name": "func"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 8
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 8
++        ,   "function_name": "func"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 30
++        ,   "special_function": "class:WillClass"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 28
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 28
++        ,   "special_function": "class:WillClass"
++        ,   "rescue_level": 3
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 25
++        ,   "special_function": "class:WillClass"
++        ,   "rescue_level": 2
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 22
++        ,   "special_function": "class:WillClass"
++        ,   "rescue_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 19
++        ,   "special_function": "class:WillClass"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 6
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 11
++        ,   "special_function": "module:WillModule"
++        ,   "block_level": 2
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 10
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 10
++        ,   "special_function": "module:WillModule"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 9
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 9
++        ,   "special_function": "module:WillModule"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 8
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-02 b/tests/ruby_stacktraces/ruby-02
+new file mode 100644
+index 0000000..dd24a02
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-02
+@@ -0,0 +1,37 @@
++/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb:173:in `aref_prefix': missing aref_prefix for RDoc::SingleClass (NotImplementedError)
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb:181:in `aref'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/template/darkfish/class.rhtml:47:in `block in generate_class'
++	from /usr/share/ruby/erb.rb:850:in `eval'
++	from /usr/share/ruby/erb.rb:850:in `result'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:724:in `template_result'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:703:in `block in render_template'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `open'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `open'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `render_template'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:351:in `generate_class'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:371:in `block in generate_class_files'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:368:in `each'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:368:in `generate_class_files'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:245:in `generate'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:137:in `block in document'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:134:in `chdir'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:134:in `document'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:202:in `generate'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:56:in `block in generation_hook'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:55:in `each'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:55:in `generation_hook'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:188:in `call'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:188:in `block in install'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:187:in `each'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:187:in `install'
++	from /usr/local/share/ruby/site_ruby/rubygems/dependency_installer.rb:394:in `install'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:211:in `update_gem'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:223:in `block in update_gems'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:222:in `each'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:222:in `update_gems'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:98:in `execute'
++	from /usr/local/share/ruby/site_ruby/rubygems/command.rb:307:in `invoke_with_build_args'
++	from /usr/local/share/ruby/site_ruby/rubygems/command_manager.rb:168:in `process_args'
++	from /usr/local/share/ruby/site_ruby/rubygems/command_manager.rb:138:in `run'
++	from /usr/local/share/ruby/site_ruby/rubygems/gem_runner.rb:54:in `run'
++	from /bin/gem:21:in `<main>'
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-02-expected-json b/tests/ruby_stacktraces/ruby-02-expected-json
+new file mode 100644
+index 0000000..1bef95c
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-02-expected-json
+@@ -0,0 +1,158 @@
++{   "exception_name": "NotImplementedError"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb"
++        ,   "file_line": 173
++        ,   "function_name": "aref_prefix"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb"
++        ,   "file_line": 181
++        ,   "function_name": "aref"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/template/darkfish/class.rhtml"
++        ,   "file_line": 47
++        ,   "function_name": "generate_class"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/erb.rb"
++        ,   "file_line": 850
++        ,   "function_name": "eval"
++        }
++      , {   "file_name": "/usr/share/ruby/erb.rb"
++        ,   "file_line": 850
++        ,   "function_name": "result"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 724
++        ,   "function_name": "template_result"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 703
++        ,   "function_name": "render_template"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 698
++        ,   "function_name": "open"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 698
++        ,   "function_name": "open"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 698
++        ,   "function_name": "render_template"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 351
++        ,   "function_name": "generate_class"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 371
++        ,   "function_name": "generate_class_files"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 368
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 368
++        ,   "function_name": "generate_class_files"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 245
++        ,   "function_name": "generate"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 137
++        ,   "function_name": "document"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 134
++        ,   "function_name": "chdir"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 134
++        ,   "function_name": "document"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 202
++        ,   "function_name": "generate"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 56
++        ,   "function_name": "generation_hook"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 55
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 55
++        ,   "function_name": "generation_hook"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 188
++        ,   "function_name": "call"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 188
++        ,   "function_name": "install"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 187
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 187
++        ,   "function_name": "install"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/dependency_installer.rb"
++        ,   "file_line": 394
++        ,   "function_name": "install"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 211
++        ,   "function_name": "update_gem"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 223
++        ,   "function_name": "update_gems"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 222
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 222
++        ,   "function_name": "update_gems"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 98
++        ,   "function_name": "execute"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command.rb"
++        ,   "file_line": 307
++        ,   "function_name": "invoke_with_build_args"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command_manager.rb"
++        ,   "file_line": 168
++        ,   "function_name": "process_args"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command_manager.rb"
++        ,   "file_line": 138
++        ,   "function_name": "run"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/gem_runner.rb"
++        ,   "file_line": 54
++        ,   "function_name": "run"
++        }
++      , {   "file_name": "/bin/gem"
++        ,   "file_line": 21
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-03 b/tests/ruby_stacktraces/ruby-03
+new file mode 100644
+index 0000000..d931254
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-03
+@@ -0,0 +1,4 @@
++/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)
++	from /usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:77:in `initialize'
++	from /sbin/oo-admin-ctl-tc:24:in `new'
++	from /sbin/oo-admin-ctl-tc:24:in `<main>'
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-03-expected-json b/tests/ruby_stacktraces/ruby-03-expected-json
+new file mode 100644
+index 0000000..a1fa1f8
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-03-expected-json
+@@ -0,0 +1,19 @@
++{   "exception_name": "RuntimeError"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb"
++        ,   "file_line": 103
++        ,   "function_name": "get_interface_mtu"
++        }
++      , {   "file_name": "/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb"
++        ,   "file_line": 77
++        ,   "function_name": "initialize"
++        }
++      , {   "file_name": "/sbin/oo-admin-ctl-tc"
++        ,   "file_line": 24
++        ,   "function_name": "new"
++        }
++      , {   "file_name": "/sbin/oo-admin-ctl-tc"
++        ,   "file_line": 24
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-04 b/tests/ruby_stacktraces/ruby-04
+new file mode 100644
+index 0000000..12388a7
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-04
+@@ -0,0 +1,39 @@
++/usr/share/rubygems/rubygems/basic_specification.rb:50:in `block (3 levels) in contains_requirable_file?': SIGTERM (SignalException)
++	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `each'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `any?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `block (2 levels) in contains_requirable_file?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `each'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `any?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `block in contains_requirable_file?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `each'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `any?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `contains_requirable_file?'
++	from /usr/share/rubygems/rubygems/specification.rb:898:in `block in find_inactive_by_path'
++	from /usr/share/rubygems/rubygems/specification.rb:897:in `each'
++	from /usr/share/rubygems/rubygems/specification.rb:897:in `find'
++	from /usr/share/rubygems/rubygems/specification.rb:897:in `find_inactive_by_path'
++	from /usr/share/rubygems/rubygems.rb:183:in `try_activate'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:144:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb:2:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines.rb:25:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites.rb:18:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions.rb:10:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:5:in `block in <top (required)>'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:4:in `each'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:4:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/bin/compass:20:in `block in <top (required)>'
++	from /usr/share/gems/gems/compass-0.12.2/bin/compass:8:in `fallback_load_path'
++	from /usr/share/gems/gems/compass-0.12.2/bin/compass:19:in `<top (required)>'
++	from /usr/bin/compass:23:in `load'
++	from /usr/bin/compass:23:in `<main>'
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-04-expected-json b/tests/ruby_stacktraces/ruby-04-expected-json
+new file mode 100644
+index 0000000..22bca55
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-04-expected-json
+@@ -0,0 +1,166 @@
++{   "exception_name": "SignalException"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "contains_requirable_file?"
++        ,   "block_level": 3
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "any?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "contains_requirable_file?"
++        ,   "block_level": 2
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 49
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 49
++        ,   "function_name": "any?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 49
++        ,   "function_name": "contains_requirable_file?"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 45
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 45
++        ,   "function_name": "any?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 45
++        ,   "function_name": "contains_requirable_file?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 898
++        ,   "function_name": "find_inactive_by_path"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 897
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 897
++        ,   "function_name": "find"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 897
++        ,   "function_name": "find_inactive_by_path"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems.rb"
++        ,   "file_line": 183
++        ,   "function_name": "try_activate"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 132
++        ,   "function_name": "require"
++        ,   "rescue_level": 1
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 144
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb"
++        ,   "file_line": 2
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines.rb"
++        ,   "file_line": 25
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites.rb"
++        ,   "file_line": 18
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions.rb"
++        ,   "file_line": 10
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
++        ,   "file_line": 5
++        ,   "special_function": "top (required)"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
++        ,   "file_line": 4
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
++        ,   "file_line": 4
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
++        ,   "file_line": 20
++        ,   "special_function": "top (required)"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
++        ,   "file_line": 8
++        ,   "function_name": "fallback_load_path"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
++        ,   "file_line": 19
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/bin/compass"
++        ,   "file_line": 23
++        ,   "function_name": "load"
++        }
++      , {   "file_name": "/usr/bin/compass"
++        ,   "file_line": 23
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/testsuite.at b/tests/testsuite.at
+index 904fe64..f27ed13 100644
+--- a/tests/testsuite.at
++++ b/tests/testsuite.at
+@@ -15,6 +15,8 @@ m4_include([koops_stacktrace.at])
+ m4_include([core_frame.at])
+ m4_include([core_thread.at])
+ m4_include([core_stacktrace.at])
++m4_include([ruby_frame.at])
++m4_include([ruby_stacktrace.at])
+ m4_include([operating_system.at])
+ m4_include([normalize.at])
+ m4_include([metrics.at])
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch b/SOURCES/satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch
new file mode 100644
index 0000000..5fd7e50
--- /dev/null
+++ b/SOURCES/satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch
@@ -0,0 +1,38 @@
+From 309b839354b171cd03955537102ca73bc14b7f58 Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Mon, 16 May 2016 14:19:34 +0200
+Subject: [PATCH] Check the return value of sr_parse_char_cspan
+
+Related to: #1336390
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ lib/python_stacktrace.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/lib/python_stacktrace.c b/lib/python_stacktrace.c
+index 99aa52c..557b728 100644
+--- a/lib/python_stacktrace.c
++++ b/lib/python_stacktrace.c
+@@ -231,9 +231,15 @@ sr_python_stacktrace_parse(const char **input,
+     }
+ 
+     /* Parse exception name. */
+-    sr_parse_char_cspan(&local_input,
+-                        ":\n",
+-                        &stacktrace->exception_name);
++    if (!sr_parse_char_cspan(&local_input, ":\n", &stacktrace->exception_name))
++    {
++
++        location->message = "Unable to find the ':\\n' characters "
++                            "identifying the end of exception name.";
++        sr_python_stacktrace_free(stacktrace);
++        return NULL;
++
++    }
+ 
+     *input = local_input;
+     return stacktrace;
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-Fix-defects-found-by-coverity.patch b/SOURCES/satyr-0.13-Fix-defects-found-by-coverity.patch
new file mode 100644
index 0000000..4fa343c
--- /dev/null
+++ b/SOURCES/satyr-0.13-Fix-defects-found-by-coverity.patch
@@ -0,0 +1,282 @@
+From 481ac50e3e9ad53e4f07dce04facfaef5a2cdcfd Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Thu, 5 Mar 2015 14:08:15 +0100
+Subject: [PATCH] Fix defects found by coverity
+
+Resolves: #1336390
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+
+Conflicts:
+	lib/abrt.c
+---
+ lib/core_stacktrace.c      |  8 ++++++++
+ lib/core_unwind.c          |  6 ++++--
+ lib/java_frame.c           |  3 +++
+ lib/koops_stacktrace.c     |  3 +++
+ lib/python_frame.c         | 26 ++++++++++++++------------
+ lib/rpm.c                  |  4 +++-
+ lib/utils.c                |  1 +
+ python/py_gdb_stacktrace.c |  8 ++++++++
+ satyr.c                    |  3 +++
+ 9 files changed, 47 insertions(+), 15 deletions(-)
+
+diff --git a/lib/core_stacktrace.c b/lib/core_stacktrace.c
+index f7fd369..aeb97e4 100644
+--- a/lib/core_stacktrace.c
++++ b/lib/core_stacktrace.c
+@@ -192,7 +192,10 @@ sr_core_stacktrace_from_json(struct sr_json_value *root,
+             if (crash_thread)
+             {
+                 if (!JSON_CHECK_TYPE(crash_thread, SR_JSON_BOOLEAN, "crash_thread"))
++                {
++                    sr_core_thread_free(thread);
+                     goto fail;
++                }
+ 
+                 if (crash_thread->u.boolean)
+                     result->crash_thread = thread;
+@@ -296,6 +299,7 @@ sr_core_stacktrace_create(const char *gdb_stacktrace_text,
+     {
+         warn("Unable to parse unstrip output.");
+ 
++        sr_gdb_stacktrace_free(gdb_stacktrace);
+         return NULL;
+     }
+ 
+@@ -332,6 +336,8 @@ sr_core_stacktrace_create(const char *gdb_stacktrace_text,
+                 core_frame->function_name =
+                     sr_strdup(gdb_frame->function_name);
+             }
++
++            core_thread->frames = sr_core_frame_append(core_thread->frames, core_frame);
+         }
+ 
+         core_stacktrace->threads =
+@@ -341,6 +347,8 @@ sr_core_stacktrace_create(const char *gdb_stacktrace_text,
+         gdb_thread = gdb_thread->next;
+     }
+ 
++    sr_unstrip_free(unstrip);
++    sr_gdb_stacktrace_free(gdb_stacktrace);
+     return core_stacktrace;
+ }
+ 
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index c30f9db..b8c0235 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -127,6 +127,7 @@ find_elf_core (Dwfl_Module *mod, void **userdata, const char *modname,
+         {
+             warn("Unable to open executable '%s': %s", executable_file,
+                  elf_errmsg(-1));
++            close(fd);
+             return -1;
+         }
+ 
+@@ -399,9 +400,10 @@ struct sr_core_stacktrace *
+ sr_core_stacktrace_from_gdb(const char *gdb_output, const char *core_file,
+                             const char *exe_file, char **error_msg)
+ {
++    /* I'm not going to rewrite it now since the function is not being used. */
++    assert(error_msg);
+     /* Initialize error_msg to 'no error'. */
+-    if (error_msg)
+-        *error_msg = NULL;
++    *error_msg = NULL;
+ 
+     struct core_handle *ch = open_coredump(core_file, exe_file, error_msg);
+     if (*error_msg)
+diff --git a/lib/java_frame.c b/lib/java_frame.c
+index ee97572..d03f86a 100644
+--- a/lib/java_frame.c
++++ b/lib/java_frame.c
+@@ -359,7 +359,10 @@ sr_java_frame_parse_exception(const char **input,
+         if (exception->next == NULL)
+             exception->next = parsed;
+         else
++        {
++            assert(frame);
+             frame->next = parsed;
++        }
+ 
+         frame = parsed;
+     }
+diff --git a/lib/koops_stacktrace.c b/lib/koops_stacktrace.c
+index 611c7d1..01baa79 100644
+--- a/lib/koops_stacktrace.c
++++ b/lib/koops_stacktrace.c
+@@ -506,7 +506,10 @@ taint_flags_to_json(struct sr_koops_stacktrace *stacktrace)
+     }
+ 
+     if (strbuf->len == 0)
++    {
++        sr_strbuf_free(strbuf);
+         return sr_strdup("[]");
++    }
+ 
+     sr_strbuf_append_char(strbuf, ']');
+     char *result = sr_strbuf_free_nobuf(strbuf);
+diff --git a/lib/python_frame.c b/lib/python_frame.c
+index b0540fe..4b4be8a 100644
+--- a/lib/python_frame.c
++++ b/lib/python_frame.c
+@@ -220,11 +220,10 @@ sr_python_frame_parse(const char **input,
+     /* Parse file name */
+     if (!sr_parse_char_cspan(&local_input, "\"", &frame->file_name))
+     {
+-        sr_python_frame_free(frame);
+         location->message = sr_asprintf("Unable to find the '\"' character "
+                 "identifying the beginning of file name.");
+ 
+-        return NULL;
++        goto fail;
+     }
+ 
+     if (strlen(frame->file_name) > 0 &&
+@@ -243,7 +242,7 @@ sr_python_frame_parse(const char **input,
+     if (0 == sr_skip_string(&local_input, "\", line "))
+     {
+         location->message = sr_asprintf("Line separator not found.");
+-        return NULL;
++        goto fail;
+     }
+ 
+     location->column += strlen("\", line ");
+@@ -253,7 +252,7 @@ sr_python_frame_parse(const char **input,
+     if (0 == length)
+     {
+         location->message = sr_asprintf("Line number not found.");
+-        return NULL;
++        goto fail;
+     }
+ 
+     location->column += length;
+@@ -263,7 +262,7 @@ sr_python_frame_parse(const char **input,
+         if (local_input[0] != '\n')
+         {
+             location->message = sr_asprintf("Function name separator not found.");
+-            return NULL;
++            goto fail;
+         }
+ 
+         /* The last frame of SyntaxError stack trace does not have
+@@ -280,11 +279,10 @@ sr_python_frame_parse(const char **input,
+         /* Parse function name */
+         if (!sr_parse_char_cspan(&local_input, "\n", &frame->function_name))
+         {
+-            sr_python_frame_free(frame);
+             location->message = sr_asprintf("Unable to find the newline character "
+                     "identifying the end of function name.");
+ 
+-            return NULL;
++            goto fail;
+         }
+ 
+         location->column += strlen(frame->function_name);
+@@ -301,19 +299,23 @@ sr_python_frame_parse(const char **input,
+         }
+     }
+ 
+-    sr_skip_char(&local_input, '\n');
+-    sr_location_add(location, 1, 0);
++    if (sr_skip_char(&local_input, '\n'))
++        sr_location_add(location, 1, 0);
+ 
+     /* Parse source code line (optional). */
+     if (4 == sr_skip_string(&local_input, "    "))
+     {
+-        sr_parse_char_cspan(&local_input, "\n", &frame->line_contents);
+-        sr_skip_char(&local_input, '\n');
+-        sr_location_add(location, 1, 0);
++        if (sr_parse_char_cspan(&local_input, "\n", &frame->line_contents)
++                && sr_skip_char(&local_input, '\n'))
++            sr_location_add(location, 1, 0);
+     }
+ 
+     *input = local_input;
+     return frame;
++
++fail:
++    sr_python_frame_free(frame);
++    return NULL;
+ }
+ 
+ char *
+diff --git a/lib/rpm.c b/lib/rpm.c
+index f8f1fa6..920e145 100644
+--- a/lib/rpm.c
++++ b/lib/rpm.c
+@@ -199,7 +199,9 @@ sr_rpm_package_sort(struct sr_rpm_package *packages)
+             array[loop]->next = NULL;
+     }
+ 
+-    return array[0];
++    struct sr_rpm_package *result = array[0];
++    free(array);
++    return result;
+ }
+ 
+ static struct sr_rpm_package *
+diff --git a/lib/utils.c b/lib/utils.c
+index fa3c0a0..bdafaa7 100644
+--- a/lib/utils.c
++++ b/lib/utils.c
+@@ -354,6 +354,7 @@ sr_string_to_file(const char *filename,
+                                      filename,
+                                      error);
+ 
++        close(fd);
+         return false;
+     }
+ 
+diff --git a/python/py_gdb_stacktrace.c b/python/py_gdb_stacktrace.c
+index c799e06..5ed3533 100644
+--- a/python/py_gdb_stacktrace.c
++++ b/python/py_gdb_stacktrace.c
+@@ -7,6 +7,7 @@
+ #include "strbuf.h"
+ #include "gdb/stacktrace.h"
+ #include "gdb/thread.h"
++#include "gdb/frame.h"
+ #include "gdb/sharedlib.h"
+ #include "location.h"
+ #include "normalize.h"
+@@ -408,13 +409,20 @@ sr_py_gdb_stacktrace_find_crash_frame(PyObject *self, PyObject *args)
+         PyObject_New(struct sr_py_gdb_frame, &sr_py_gdb_frame_type);
+ 
+     if (!result)
++    {
++        sr_gdb_frame_free(frame);
+         return PyErr_NoMemory();
++    }
+ 
+     result->frame = frame;
+     this->crashframe = result;
+ 
+     if (stacktrace_rebuild_thread_python_list(this) < 0)
++    {
++        sr_gdb_frame_free(frame);
++        Py_DECREF(result);
+         return NULL;
++    }
+ 
+     return (PyObject *)result;
+ }
+diff --git a/satyr.c b/satyr.c
+index 6e1ed28..44bf869 100644
+--- a/satyr.c
++++ b/satyr.c
+@@ -210,6 +210,9 @@ debug_normalize(int argc, char **argv)
+     struct sr_strbuf *strbuf = sr_strbuf_new();
+     sr_gdb_thread_append_to_str(thread, strbuf, false);
+     puts(strbuf->buf);
++
++    free(text);
++    sr_strbuf_free(strbuf);
+ }
+ 
+ static void
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-elfutils-0.158.patch b/SOURCES/satyr-0.13-elfutils-0.158.patch
index 715b329..fafd6a1 100644
--- a/SOURCES/satyr-0.13-elfutils-0.158.patch
+++ b/SOURCES/satyr-0.13-elfutils-0.158.patch
@@ -1,3 +1,13 @@
+From 8e872be02ea4f231347aa47f38b4418175c0e43d Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 8 Jan 2014 17:27:47 +0100
+Subject: [PATCH] unwind: fix build against elfutils-0.158
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/core_unwind.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
 diff --git a/lib/core_unwind.c b/lib/core_unwind.c
 index d0c7aec..7910254 100644
 --- a/lib/core_unwind.c
@@ -11,3 +21,6 @@ index d0c7aec..7910254 100644
      {
          set_error_dwfl("dwfl_core_file_report");
          goto fail_dwfl;
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-actualize-list-of-functions.patch b/SOURCES/satyr-0.13-normalization-actualize-list-of-functions.patch
new file mode 100644
index 0000000..6ff08da
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-actualize-list-of-functions.patch
@@ -0,0 +1,188 @@
+From 45973b73fd538eb194c0ddc52457eb29b06dd84f Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Tue, 10 May 2016 15:13:53 +0200
+Subject: [PATCH] normalization: actualize list of functions
+
+Related to #1332869
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ lib/normalize.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 99 insertions(+), 3 deletions(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index e3a7a13..9d7574e 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -89,6 +89,7 @@ is_removable_dbus(const char *function_name,
+         call_match(function_name, source_file, "gerror_to_dbus_error_message", "dbus-gobject.c", NULL) ||
+         call_match(function_name, source_file, "dbus_g_method_return_error", "dbus-gobject.c", NULL) ||
+         call_match(function_name, source_file, "message_queue_dispatch", "dbus-gmain.c", NULL) ||
++        call_match(function_name, source_file, "_dbus_abort", "dbus-sysdeps.c", "libdbus", NULL) ||
+         call_match(function_name, source_file, "dbus_connection_dispatch", "dbus-connection.c", "libdbus", NULL);
+ }
+ 
+@@ -134,7 +135,17 @@ is_removable_glib(const char *function_name,
+         call_match(function_name, source_file, "g_thread_pool_thread_proxy", "gthreadpool.c", "libglib", NULL) ||
+         call_match(function_name, source_file, "g_thread_create_proxy", "gthread.c", "libglib", NULL) ||
+         call_match(function_name, source_file, "g_cclosure_marshal_VOID__BOXED", "gmarshal.c", "libgobject", NULL) ||
+-        call_match(function_name, source_file, "g_cclosure_marshal_VOID__VOID", "gclosure.c", "gmarshal.c", "libgobject", NULL);
++        call_match(function_name, source_file, "g_cclosure_marshal_VOID__VOID", "gclosure.c", "gmarshal.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "g_object_notify", "gobject.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "Glib::exception_handlers_invoke()", "libglibmm", NULL) ||
++        call_match(function_name, source_file, "g_signal_handlers_destroy", "gsignal.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "g_vasprintf", "gprintf.c", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_strdup_vprintf", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_strdup_printf", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_print", "libglib", NULL) ||
++        call_match(function_name, source_file, "invalid_closure_notify", "gsignal.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "smc_tree_abort", "gslice.c", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_thread_abort", "libglib", NULL);
+ }
+ 
+ static bool
+@@ -147,6 +158,7 @@ is_removable_libstdcpp(const char *function_name,
+         call_match(function_name, source_file, "std::terminate", "eh_terminate.cc", NULL) ||
+         call_match(function_name, source_file, "__cxxabiv1::__cxa_throw", "eh_throw.cc", NULL) ||
+         call_match(function_name, source_file, "__cxxabiv1::__cxa_rethrow", "eh_throw.cc", NULL) ||
++        call_match(function_name, source_file, "__verbose_terminate_handler", "vterminate.cc", NULL) ||
+         call_match(function_name, source_file, "__cxxabiv1::__cxa_pure_virtual", "pure.cc", NULL);
+ }
+ 
+@@ -173,6 +185,8 @@ is_removable_xorg(const char *function_name,
+         call_match(function_name, source_file, "ddxGiveUp", "xf86Init.c", NULL) ||
+         call_match(function_name, source_file, "OsAbort", "utils.c", NULL) ||
+         call_match(function_name, source_file, "handle_error", "xcb_io.c", "libX11", NULL) ||
++        call_match(function_name, source_file, "_XIOError", "XlibInt.c", "libX11", NULL) ||
++        call_match(function_name, source_file, "_XEventsQueued", "xcb_io.c", "libX11", NULL) ||
+         call_match(function_name, source_file, "handle_response", "xcb_io.c", "libX11", NULL);
+ }
+ 
+@@ -184,10 +198,82 @@ is_removable_glibc(const char *function_name,
+         call_match(function_name, source_file, "_start", "", NULL) ||
+         call_match(function_name, source_file, "__libc_start_main", "libc", NULL) ||
+         call_match(function_name, source_file, "clone", "clone.S", "libc", NULL) ||
++        call_match(function_name, source_file, "poll", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_new_fclose", "iofclose.c", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_vfprintf_internal", "vfprintf.c", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_default_xsputn", "genops.c", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_wdefault_xsputn", "wgenops.c", "libc", NULL) ||
++        call_match(function_name, source_file, "__libc_message", "libc_fatal.c", "libc", NULL) ||
+         call_match(function_name, source_file, "start_thread", "pthread_create.c", "libpthread", NULL);
+ }
+ 
+ static bool
++is_removable_other(const char *function_name,
++                   const char *source_file)
++{
++    return
++        call_match(function_name, source_file, "assert_cursor", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "assert_device_not_suspended", "intel_uncore.c", NULL) ||
++        call_match(function_name, source_file, "assert_pipe", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "assert_plane", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "assert_transcoder_disabled", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "btrfs_assert_delayed_root_empty", "delayed-inode.c", "btrfs", NULL) ||
++        call_match(function_name, source_file, "_cogl_set_error", "cogl-error.c", "libcogl", NULL) ||
++        call_match(function_name, source_file, "defaultCrashHandler", "kcrash.cpp", "libKF5Crash", NULL) ||
++        call_match(function_name, source_file, "_dl_signal_error", "dl-error.c", "ld-linux", NULL) ||
++        call_match(function_name, source_file, "error_dialog_response_cb", "", NULL) ||
++        call_match(function_name, source_file, "do_warn", "_warnings.c", "libpython",NULL) ||
++        call_match(function_name, source_file, "QMessageLogger::fatal(char const*, ...) const", "", NULL) ||
++        call_match(function_name, source_file, "nsProfileLock::FatalSignalHandler(int, siginfo_t*, void*)", "", NULL) ||
++        call_match(function_name, source_file, "qt_message_output", "qglobal.cpp", "libQtCore", NULL) ||
++        call_match(function_name, source_file, "qt_message_output(QtMsgType, char const*)", "", NULL) ||
++        call_match(function_name, source_file, "signalHandler(int, siginfo_t*, void*)", "", NULL) ||
++        call_match(function_name, source_file, "FatalSignalHandler", "nsProfileLock.cpp", "libxul", NULL) ||
++        call_match(function_name, source_file, "Foam::error::abort()", "", NULL) ||
++        call_match(function_name, source_file, "JS_AbortIfWrongThread", "libmozjs", NULL) ||
++        call_match(function_name, source_file, "Crash::defaultCrashHandler(int)", "libkdeui", "libKF5Crash", NULL) ||
++        call_match(function_name, source_file, "Py_FatalError", "pythonrun.c", "libpython", NULL) ||
++        call_match(function_name, source_file, "__btrfs_abort_transaction", "btrfs", NULL) ||
++        call_match(function_name, source_file, "assert_pch_hdmi_disabled", "", NULL) ||
++        call_match(function_name, source_file, "assert_pll", "", NULL) ||
++        call_match(function_name, source_file, "core::system::abort()", "", NULL) ||
++        call_match(function_name, source_file, "ddd_assert_fail", "assert.C", NULL) ||
++        call_match(function_name, source_file, "debug_dma_assert_idle", "", NULL) ||
++        call_match(function_name, source_file, "error_handler", "", NULL) ||
++        call_match(function_name, source_file, "fatal_error_signal", "", NULL) ||
++        call_match(function_name, source_file, "fatal_handler", "signal.c", "libfreerdp", NULL) ||
++        call_match(function_name, source_file, "gpf_notice", "", NULL) ||
++        call_match(function_name, source_file, "log", "", NULL) ||
++        call_match(function_name, source_file, "_log", "", NULL) ||
++        call_match(function_name, source_file, "log_assert_failed", "", NULL) ||
++        call_match(function_name, source_file, "mozalloc_abort", "mozalloc_abort.cpp", "libmozalloc", NULL) ||
++        call_match(function_name, source_file, "mozalloc_abort(char const*)", "libmozalloc", "content-container", "plugin-container", NULL) ||
++        call_match(function_name, source_file, "note_interrupt", "spurious.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "print_bad_pte", "memory.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "print_oops_end_marker", "panic.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "printk", "printk.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "qupzilla_signal_handler", "main.cpp", "qupzilla", NULL) ||
++        call_match(function_name, source_file, "rb_bug", "error.c", "libruby", NULL) ||
++        call_match(function_name, source_file, "sighandler", "", NULL) ||
++        call_match(function_name, source_file, "signalHandler(int)", "", NULL) ||
++        call_match(function_name, source_file, "signal_abort", "signal.c", NULL) ||
++        call_match(function_name, source_file, "signal_handler", "", NULL) ||
++        call_match(function_name, source_file, "sys_abort", "error.c", "libgfortran", NULL) ||
++        call_match(function_name, source_file, "terminate_due_to_signal", "emacs.c", "emacs", NULL) ||
++        call_match(function_name, source_file, "wl_log", "wayland-util.c", NULL) ||
++        call_match(function_name, source_file, "display_protocol_error", "wayland-client.c", NULL) ||
++        call_match(function_name, source_file, "display_handle_error", "wayland-client.c", NULL) ||
++        call_match(function_name, source_file, "x_io_error", "libmutter", NULL) ||
++        call_match(function_name, source_file, "__ioremap_calle ", "ioremap.c", NULL) ||
++        call_match(function_name, source_file, "ioremap_nocache", "ioremap.c", NULL) ||
++        call_match(function_name, source_file, "wpa_msg", "wpa_debug.c", NULL) ||
++        call_match(function_name, source_file, "js::gc::FinalizeArenas(js::FreeOp*, js::gc::ArenaHeader**, js::gc::ArenaList&, js::gc::AllocKind, js::SliceBudget&)\"js::Shape::finalize(js::FreeOp*)", "", NULL) ||
++        call_match(function_name, source_file, "WTF::StringImpl::endsWith(char const*, unsigned int, bool) const", "", NULL) ||
++        call_match(function_name, source_file, "mozilla::plugins::child::_invokedefault(_NPP*, NPObject*, _NPVariant const*, unsigned int, _NPVariant*)", "", NULL) ||
++        call_match(function_name, source_file, "xitk_signal_handler", "xitk.c", "xine", NULL);
++}
++
++static bool
+ is_removable_glibc_with_above(const char *function_name,
+                               const char *source_file)
+ {
+@@ -205,6 +291,13 @@ is_removable_glibc_with_above(const char *function_name,
+         call_match(function_name, source_file, "__snprintf_chk", "", NULL) ||
+         call_match(function_name, source_file, "___snprintf_chk", "", NULL) ||
+         call_match(function_name, source_file, "__vasprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__vsprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "___sprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__fwprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__asprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "___printf_chk", "", NULL) ||
++        call_match(function_name, source_file, "___fprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__vswprintf_chk", "", NULL) ||
+         call_match(function_name, source_file, "malloc_consolidate", "malloc.c", "libc", NULL) ||
+         call_match(function_name, source_file, "malloc_printerr", "malloc.c", "libc", NULL) ||
+         call_match(function_name, source_file, "_int_malloc", "malloc.c", "libc", NULL) ||
+@@ -295,6 +388,7 @@ sr_gdb_is_exit_frame(struct sr_gdb_frame *frame)
+         /* Terminates a function in case of buffer overflow. */
+         sr_gdb_frame_calls_func(frame, "__chk_fail", "chk_fail.c", "libc.so", NULL) ||
+         sr_gdb_frame_calls_func(frame, "__stack_chk_fail", "stack_chk_fail.c", "libc.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "do_exit", "exit.c", NULL) ||
+         sr_gdb_frame_calls_func(frame, "kill", "syscall-template.S", NULL);
+ }
+ 
+@@ -365,7 +459,8 @@ sr_normalize_gdb_thread(struct sr_gdb_thread *thread)
+             is_removable_linux(frame->function_name, frame->source_file) ||
+             is_removable_xorg(frame->function_name, frame->source_file) ||
+             is_removable_jvm(frame->function_name, frame->source_file) ||
+-            is_removable_vim(frame->function_name, frame->source_file);
++            is_removable_vim(frame->function_name, frame->source_file) ||
++            is_removable_other(frame->function_name, frame->source_file);
+ 
+         bool removable_with_above =
+             is_removable_glibc_with_above(frame->function_name, frame->source_file) ||
+@@ -510,7 +605,8 @@ sr_normalize_core_thread(struct sr_core_thread *thread)
+             is_removable_linux(frame->function_name, frame->file_name) ||
+             is_removable_xorg(frame->function_name, frame->file_name) ||
+             is_removable_jvm(frame->function_name, frame->file_name) ||
+-            is_removable_vim(frame->function_name, frame->file_name);
++            is_removable_vim(frame->function_name, frame->file_name) ||
++            is_removable_other(frame->function_name, frame->file_name);
+ 
+         bool removable_with_above =
+             is_removable_glibc_with_above(frame->function_name, frame->file_name)  ||
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-add-glibc-__assert_fail_base.patch b/SOURCES/satyr-0.13-normalization-add-glibc-__assert_fail_base.patch
new file mode 100644
index 0000000..7c7c505
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-add-glibc-__assert_fail_base.patch
@@ -0,0 +1,24 @@
+From ae3890bb65dfd13c4dd1a52b2f9fb1d1a3515253 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Bry=C5=A1a?= <mbrysa@redhat.com>
+Date: Wed, 27 May 2015 13:56:12 +0200
+Subject: [PATCH] normalization: add glibc __assert_fail_base
+
+---
+ lib/normalize.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index 0e9b497..4b22645 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -193,6 +193,7 @@ is_removable_glibc_with_above(const char *function_name,
+ {
+     return
+         call_match(function_name, source_file, "__assert_fail", "", NULL) ||
++        call_match(function_name, source_file, "__assert_fail_base", "", NULL) ||
+         call_match(function_name, source_file, "__chk_fail", "", NULL) ||
+         call_match(function_name, source_file, "__longjmp_chk", "", NULL) ||
+         call_match(function_name, source_file, "__malloc_assert", "", NULL) ||
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-add-glibc-__libc_fatal.patch b/SOURCES/satyr-0.13-normalization-add-glibc-__libc_fatal.patch
new file mode 100644
index 0000000..b24b413
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-add-glibc-__libc_fatal.patch
@@ -0,0 +1,26 @@
+From 933bd99829000bcd9bf674f0e2e61c562767a09b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Bry=C5=A1a?= <mbrysa@redhat.com>
+Date: Wed, 27 May 2015 14:37:15 +0200
+Subject: [PATCH] normalization: add glibc __libc_fatal
+
+---
+ lib/normalize.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index 4b22645..bec2ccc 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -216,7 +216,8 @@ is_removable_glibc_with_above(const char *function_name,
+         call_match(function_name, source_file, "__libc_memalign", "malloc.c", NULL) ||
+         call_match(function_name, source_file, "__libc_realloc", "malloc.c", NULL) ||
+         call_match(function_name, source_file, "__posix_memalign", "malloc.c", NULL) ||
+-        call_match(function_name, source_file, "__libc_calloc", "malloc.c", NULL);
++        call_match(function_name, source_file, "__libc_calloc", "malloc.c", NULL) ||
++        call_match(function_name, source_file, "__libc_fatal", "libc", NULL);
+ }
+ 
+ static char *
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-additional-X-GDK-functions.patch b/SOURCES/satyr-0.13-normalization-additional-X-GDK-functions.patch
new file mode 100644
index 0000000..74eba7d
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-additional-X-GDK-functions.patch
@@ -0,0 +1,40 @@
+From e436cd0b6eb389e6803b8b519e936429e694e453 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Fri, 28 Nov 2014 17:59:45 +0100
+Subject: [PATCH] normalization: additional X/GDK functions
+
+Closes #199.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/normalize.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index 81be188..0e9b497 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -100,7 +100,8 @@ is_removable_gdk(const char *function_name,
+         call_match(function_name, source_file, "gdk_x_error", "gdkmain-x11.c", NULL) ||
+         call_match(function_name, source_file, "gdk_threads_dispatch", "gdk.c", NULL) ||
+         call_match(function_name, source_file, "gdk_event_dispatch", "gdkevents-x11.c", "gdkevents.c", NULL) ||
+-        call_match(function_name, source_file, "gdk_event_source_dispatch", "gdkeventsource.c", NULL);
++        call_match(function_name, source_file, "gdk_event_source_dispatch", "gdkeventsource.c", NULL) ||
++        call_match(function_name, source_file, "_gdk_x11_display_error_event", "gdkdisplay-x11.c", "libgdk", NULL);
+ }
+ 
+ static bool
+@@ -170,7 +171,9 @@ is_removable_xorg(const char *function_name,
+         call_match(function_name, source_file, "AbortServer", "log.c", NULL) ||
+         call_match(function_name, source_file, "AbortDDX", "xf86Init.c", NULL) ||
+         call_match(function_name, source_file, "ddxGiveUp", "xf86Init.c", NULL) ||
+-        call_match(function_name, source_file, "OsAbort", "utils.c", NULL);
++        call_match(function_name, source_file, "OsAbort", "utils.c", NULL) ||
++        call_match(function_name, source_file, "handle_error", "xcb_io.c", "libX11", NULL) ||
++        call_match(function_name, source_file, "handle_response", "xcb_io.c", "libX11", NULL);
+ }
+ 
+ static bool
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-normalize-out-exit-frames.patch b/SOURCES/satyr-0.13-normalization-normalize-out-exit-frames.patch
new file mode 100644
index 0000000..c4d57e3
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-normalize-out-exit-frames.patch
@@ -0,0 +1,144 @@
+From b4120f370475bde243a1ae5bde64f4f0a641c3c3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Bry=C5=A1a?= <mbrysa@redhat.com>
+Date: Wed, 27 May 2015 18:43:52 +0200
+Subject: [PATCH] normalization: normalize out exit frames
+
+---
+ include/core/thread.h |  3 +++
+ lib/core_thread.c     | 30 +++++++++++++++++-------------
+ lib/normalize.c       | 36 +++++++++++++++++++++---------------
+ 3 files changed, 41 insertions(+), 28 deletions(-)
+
+diff --git a/include/core/thread.h b/include/core/thread.h
+index 0ba71e4..d6125df 100644
+--- a/include/core/thread.h
++++ b/include/core/thread.h
+@@ -123,6 +123,9 @@ struct sr_core_thread *
+ sr_core_thread_append(struct sr_core_thread *dest,
+                       struct sr_core_thread *item);
+ 
++bool
++sr_core_thread_is_exit_frame(struct sr_core_frame *frame);
++
+ struct sr_core_frame *
+ sr_core_thread_find_exit_frame(struct sr_core_thread *thread);
+ 
+diff --git a/lib/core_thread.c b/lib/core_thread.c
+index 54fb89c..d8aa943 100644
+--- a/lib/core_thread.c
++++ b/lib/core_thread.c
+@@ -149,6 +149,22 @@ sr_core_thread_append(struct sr_core_thread *dest,
+     return dest;
+ }
+ 
++bool
++sr_core_thread_is_exit_frame(struct sr_core_frame *frame)
++{
++    return
++        sr_core_frame_calls_func(frame, "__run_exit_handlers", NULL) ||
++        sr_core_frame_calls_func(frame, "raise", "libc.so", "libc-", "libpthread.so", NULL) ||
++        sr_core_frame_calls_func(frame, "__GI_raise", NULL) ||
++        sr_core_frame_calls_func(frame, "exit", NULL) ||
++        sr_core_frame_calls_func(frame, "abort", "libc.so", "libc-", NULL) ||
++        sr_core_frame_calls_func(frame, "__GI_abort", NULL) ||
++        /* Terminates a function in case of buffer overflow. */
++        sr_core_frame_calls_func(frame, "__chk_fail", "libc.so", NULL) ||
++        sr_core_frame_calls_func(frame, "__stack_chk_fail", "libc.so", NULL) ||
++        sr_core_frame_calls_func(frame, "kill", NULL);
++}
++
+ struct sr_core_frame *
+ sr_core_thread_find_exit_frame(struct sr_core_thread *thread)
+ {
+@@ -156,19 +172,7 @@ sr_core_thread_find_exit_frame(struct sr_core_thread *thread)
+     struct sr_core_frame *result = NULL;
+     while (frame)
+     {
+-        bool is_exit_frame =
+-            sr_core_frame_calls_func(frame, "__run_exit_handlers", NULL) ||
+-            sr_core_frame_calls_func(frame, "raise", "libc.so", "libc-", "libpthread.so", NULL) ||
+-            sr_core_frame_calls_func(frame, "__GI_raise", NULL) ||
+-            sr_core_frame_calls_func(frame, "exit", NULL) ||
+-            sr_core_frame_calls_func(frame, "abort", "libc.so", "libc-", NULL) ||
+-            sr_core_frame_calls_func(frame, "__GI_abort", NULL) ||
+-            /* Terminates a function in case of buffer overflow. */
+-            sr_core_frame_calls_func(frame, "__chk_fail", "libc.so", NULL) ||
+-            sr_core_frame_calls_func(frame, "__stack_chk_fail", "libc.so", NULL) ||
+-            sr_core_frame_calls_func(frame, "kill", NULL);
+-
+-        if (is_exit_frame)
++        if (sr_core_thread_is_exit_frame(frame))
+             result = frame;
+ 
+         frame = frame->next;
+diff --git a/lib/normalize.c b/lib/normalize.c
+index bec2ccc..e3a7a13 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -282,6 +282,22 @@ remove_func_prefix(char *function_name, const char *prefix, int num)
+     memmove(function_name, function_name + num, func_len - num + 1);
+ }
+ 
++static bool
++sr_gdb_is_exit_frame(struct sr_gdb_frame *frame)
++{
++    return
++        sr_gdb_frame_calls_func(frame, "__run_exit_handlers", "exit.c", NULL) ||
++        sr_gdb_frame_calls_func(frame, "raise", "pt-raise.c", "libc.so", "libc-", "libpthread.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "__GI_raise", "raise.c", NULL) ||
++        sr_gdb_frame_calls_func(frame, "exit", "exit.c", NULL) ||
++        sr_gdb_frame_calls_func(frame, "abort", "abort.c", "libc.so", "libc-", NULL) ||
++        sr_gdb_frame_calls_func(frame, "__GI_abort", "abort.c", NULL) ||
++        /* Terminates a function in case of buffer overflow. */
++        sr_gdb_frame_calls_func(frame, "__chk_fail", "chk_fail.c", "libc.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "__stack_chk_fail", "stack_chk_fail.c", "libc.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "kill", "syscall-template.S", NULL);
++}
++
+ void
+ sr_normalize_gdb_thread(struct sr_gdb_thread *thread)
+ {
+@@ -352,7 +368,8 @@ sr_normalize_gdb_thread(struct sr_gdb_thread *thread)
+             is_removable_vim(frame->function_name, frame->source_file);
+ 
+         bool removable_with_above =
+-            is_removable_glibc_with_above(frame->function_name, frame->source_file);
++            is_removable_glibc_with_above(frame->function_name, frame->source_file) ||
++            sr_gdb_is_exit_frame(frame);
+ 
+         if (removable_with_above)
+         {
+@@ -496,7 +513,8 @@ sr_normalize_core_thread(struct sr_core_thread *thread)
+             is_removable_vim(frame->function_name, frame->file_name);
+ 
+         bool removable_with_above =
+-            is_removable_glibc_with_above(frame->function_name, frame->file_name);
++            is_removable_glibc_with_above(frame->function_name, frame->file_name)  ||
++            sr_core_thread_is_exit_frame(frame);
+ 
+         if (removable_with_above)
+         {
+@@ -689,19 +707,7 @@ sr_glibc_thread_find_exit_frame(struct sr_gdb_thread *thread)
+     struct sr_gdb_frame *result = NULL;
+     while (frame)
+     {
+-        bool is_exit_frame =
+-            sr_gdb_frame_calls_func(frame, "__run_exit_handlers", "exit.c", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "raise", "pt-raise.c", "libc.so", "libc-", "libpthread.so", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "__GI_raise", "raise.c", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "exit", "exit.c", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "abort", "abort.c", "libc.so", "libc-", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "__GI_abort", "abort.c", NULL) ||
+-            /* Terminates a function in case of buffer overflow. */
+-            sr_gdb_frame_calls_func(frame, "__chk_fail", "chk_fail.c", "libc.so", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "__stack_chk_fail", "stack_chk_fail.c", "libc.so", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "kill", "syscall-template.S", NULL);
+-
+-        if (is_exit_frame)
++        if (sr_gdb_is_exit_frame(frame))
+             result = frame;
+ 
+         frame = frame->next;
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalize-extend-xorg-blacklist.patch b/SOURCES/satyr-0.13-normalize-extend-xorg-blacklist.patch
new file mode 100644
index 0000000..e747010
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalize-extend-xorg-blacklist.patch
@@ -0,0 +1,34 @@
+From f3f36ff858f50aed5a6c4df5144ae15aeb5255b9 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 24 Feb 2014 12:44:12 +0100
+Subject: [PATCH] normalize: extend xorg blacklist
+
+Closes rhbz#1068767.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/normalize.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index d332882..81be188 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -164,7 +164,13 @@ is_removable_xorg(const char *function_name,
+         call_match(function_name, source_file, "_XReply", "xcb_io.c", NULL) ||
+         call_match(function_name, source_file, "_XError", "XlibInt.c", NULL) ||
+         call_match(function_name, source_file, "XSync", "Sync.c", NULL) ||
+-        call_match(function_name, source_file, "process_responses", "xcb_io.c", NULL);
++        call_match(function_name, source_file, "process_responses", "xcb_io.c", NULL) ||
++        call_match(function_name, source_file, "OsSigHandler", "osinit.c", NULL) ||
++        call_match(function_name, source_file, "FatalError", "log.c", NULL) ||
++        call_match(function_name, source_file, "AbortServer", "log.c", NULL) ||
++        call_match(function_name, source_file, "AbortDDX", "xf86Init.c", NULL) ||
++        call_match(function_name, source_file, "ddxGiveUp", "xf86Init.c", NULL) ||
++        call_match(function_name, source_file, "OsAbort", "utils.c", NULL);
+ }
+ 
+ static bool
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-os-add-support-for-OS-Variant.patch b/SOURCES/satyr-0.13-os-add-support-for-OS-Variant.patch
new file mode 100644
index 0000000..1456ada
--- /dev/null
+++ b/SOURCES/satyr-0.13-os-add-support-for-OS-Variant.patch
@@ -0,0 +1,166 @@
+From 20cafc9b23448aaab4fd8f07a5a1defd0cd7038a Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Tue, 1 Sep 2015 13:05:48 +0200
+Subject: [PATCH] os: add support for OS Variant
+
+VARIANT= and VARIANT_ID= fields are optional.
+
+http://www.freedesktop.org/software/systemd/man/os-release.html
+
+Related to abrt/abrt#995
+Resolves: #1342469
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+
+Conflicts:
+	include/operating_system.h
+	lib/operating_system.c
+---
+ include/operating_system.h |  1 +
+ lib/operating_system.c     | 16 ++++++++++++++++
+ tests/operating_system.at  | 43 +++++++++++++++++++++++++++++++++++++++----
+ 3 files changed, 56 insertions(+), 4 deletions(-)
+
+diff --git a/include/operating_system.h b/include/operating_system.h
+index f26e322..84fcce0 100644
+--- a/include/operating_system.h
++++ b/include/operating_system.h
+@@ -37,6 +37,7 @@ struct sr_operating_system
+     char *cpe;
+     /* Uptime in seconds. */
+     uint64_t uptime;
++    char *variant;
+ };
+ 
+ struct sr_operating_system *
+diff --git a/lib/operating_system.c b/lib/operating_system.c
+index 64f7439..9bebe5b 100644
+--- a/lib/operating_system.c
++++ b/lib/operating_system.c
+@@ -43,6 +43,7 @@ sr_operating_system_init(struct sr_operating_system *operating_system)
+     operating_system->version = NULL;
+     operating_system->architecture = NULL;
+     operating_system->cpe = NULL;
++    operating_system->variant = NULL;
+     operating_system->uptime = 0;
+ }
+ 
+@@ -56,6 +57,7 @@ sr_operating_system_free(struct sr_operating_system *operating_system)
+     free(operating_system->version);
+     free(operating_system->architecture);
+     free(operating_system->cpe);
++    free(operating_system->variant);
+     free(operating_system);
+ }
+ 
+@@ -92,6 +94,13 @@ sr_operating_system_to_json(struct sr_operating_system *operating_system)
+         sr_strbuf_append_str(strbuf, "\n");
+     }
+ 
++    if (operating_system->variant)
++    {
++        sr_strbuf_append_str(strbuf, ",   \"variant\": ");
++        sr_json_append_escaped(strbuf, operating_system->variant);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
+     if (operating_system->uptime > 0)
+     {
+         sr_strbuf_append_strf(strbuf,
+@@ -122,6 +131,9 @@ sr_operating_system_from_json(struct sr_json_value *root, char **error_message)
+         JSON_READ_STRING(root, "architecture", &result->architecture) &&
+         JSON_READ_UINT64(root, "uptime", &result->uptime);
+ 
++    /* variant is optional - failure is not fatal */
++    JSON_READ_STRING(root, "variant", &result->variant);
++
+     if (!success)
+     {
+         sr_operating_system_free(result);
+@@ -194,6 +206,10 @@ os_release_callback(char *key, char *value, void *data)
+     {
+         operating_system->cpe = value;
+     }
++    else if (0 == strcmp(key, "VARIANT_ID"))
++    {
++        operating_system->variant = value;
++    }
+     else
+     {
+         free(value);
+diff --git a/tests/operating_system.at b/tests/operating_system.at
+index 0db2b9d..044d4bd 100644
+--- a/tests/operating_system.at
++++ b/tests/operating_system.at
+@@ -46,12 +46,14 @@ AT_TESTFUN([sr_operating_system_parse_etc_os_release],
+ [[
+ #include "operating_system.h"
+ #include "utils.h"
++#include <string.h>
+ #include <stdio.h>
+ #include <assert.h>
+ 
+ void check(const char *etc_os_release,
+            const char *expected_name,
+-           const char *expected_version)
++           const char *expected_version,
++           const char *expected_variant)
+ {
+     struct sr_operating_system *os = sr_operating_system_new();
+     bool success = sr_operating_system_parse_etc_os_release(
+@@ -60,6 +62,20 @@ void check(const char *etc_os_release,
+     assert(success);
+     assert(0 == strcmp(os->name, expected_name));
+     assert(0 == strcmp(os->version, expected_version));
++
++    if (   (    (expected_variant != NULL && os->variant == NULL)
++             || (expected_variant == NULL && os->variant != NULL)
++           )
++        || (    (expected_variant != NULL && os->variant != NULL)
++             &&  0 != strcmp(os->version, expected_version)
++            )
++        )
++    {
++        fprintf(stderr, "Expected: '%s'\n", expected_variant);
++        fprintf(stderr, "Got     : '%s'\n", os->variant);
++        abort();
++    }
++
+     sr_operating_system_free(os);
+ }
+ 
+@@ -102,9 +118,28 @@ main(void)
+ "REDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\n"
+ "REDHAT_SUPPORT_PRODUCT_VERSION=7.0\n";
+ 
+-    check(f19, "fedora", "19");
+-    check(raw, "fedora", "rawhide");
+-    check(el7, "rhel", "7.0");
++    char *f23 =
++"NAME=Fedora\n"
++"VERSION=\"23 (Workstation Edition)\"\n"
++"ID=fedora\n"
++"VERSION_ID=23\n"
++"PRETTY_NAME=\"Fedora 23 (Workstation Edition)\"\n"
++"ANSI_COLOR=\"0;34\"\n"
++"CPE_NAME=\"cpe:/o:fedoraproject:fedora:23\"\n"
++"HOME_URL=\"https://fedoraproject.org/\"\n"
++"BUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\n"
++"REDHAT_BUGZILLA_PRODUCT=\"Fedora\"\n"
++"REDHAT_BUGZILLA_PRODUCT_VERSION=23\n"
++"REDHAT_SUPPORT_PRODUCT=\"Fedora\"\n"
++"REDHAT_SUPPORT_PRODUCT_VERSION=23\n"
++"PRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy\n"
++"VARIANT=\"Workstation Edition\"\n"
++"VARIANT_ID=workstation\n";
++
++    check(f19, "fedora", "19", NULL);
++    check(raw, "fedora", "rawhide", NULL);
++    check(el7, "rhel", "7.0", NULL);
++    check(f23, "fedora", "23", "workstation");
+ 
+     return 0;
+ }
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-python-add-Ruby-support.patch b/SOURCES/satyr-0.13-python-add-Ruby-support.patch
new file mode 100644
index 0000000..cb2c8a0
--- /dev/null
+++ b/SOURCES/satyr-0.13-python-add-Ruby-support.patch
@@ -0,0 +1,914 @@
+From b0c2427cdc59be0bb1fa76e928b17d9aa1a0d4eb Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 28 Jan 2015 17:31:41 +0100
+Subject: [PATCH] python: add Ruby support
+
+Related: #1334604
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+
+Conflicts:
+	tests/Makefile.am
+---
+ python/Makefile.am          |   4 +
+ python/py_module.c          |  22 +++++
+ python/py_report.c          |   9 ++
+ python/py_ruby_frame.c      | 227 ++++++++++++++++++++++++++++++++++++++++++++
+ python/py_ruby_frame.h      |  69 ++++++++++++++
+ python/py_ruby_stacktrace.c | 182 +++++++++++++++++++++++++++++++++++
+ python/py_ruby_stacktrace.h |  71 ++++++++++++++
+ tests/Makefile.am           |  11 ++-
+ tests/python/ruby.py        | 174 +++++++++++++++++++++++++++++++++
+ tests/python_bindings.at    |   1 +
+ 10 files changed, 769 insertions(+), 1 deletion(-)
+ create mode 100644 python/py_ruby_frame.c
+ create mode 100644 python/py_ruby_frame.h
+ create mode 100644 python/py_ruby_stacktrace.c
+ create mode 100644 python/py_ruby_stacktrace.h
+ create mode 100755 tests/python/ruby.py
+
+diff --git a/python/Makefile.am b/python/Makefile.am
+index 83273a2..9380c12 100644
+--- a/python/Makefile.am
++++ b/python/Makefile.am
+@@ -44,6 +44,10 @@ _satyr_la_SOURCES = \
+     py_java_thread.c \
+     py_java_stacktrace.h \
+     py_java_stacktrace.c \
++    py_ruby_frame.h \
++    py_ruby_frame.c \
++    py_ruby_stacktrace.h \
++    py_ruby_stacktrace.c \
+     py_rpm_package.h \
+     py_rpm_package.c \
+     py_metrics.h \
+diff --git a/python/py_module.c b/python/py_module.c
+index 83697ef..144465b 100644
+--- a/python/py_module.c
++++ b/python/py_module.c
+@@ -17,6 +17,8 @@
+ #include "py_core_frame.h"
+ #include "py_core_thread.h"
+ #include "py_core_stacktrace.h"
++#include "py_ruby_frame.h"
++#include "py_ruby_stacktrace.h"
+ #include "py_rpm_package.h"
+ #include "py_metrics.h"
+ #include "py_operating_system.h"
+@@ -187,6 +189,18 @@ init_satyr()
+         return;
+     }
+ 
++    if (PyType_Ready(&sr_py_ruby_frame_type) < 0)
++    {
++        puts("PyType_Ready(&sr_py_ruby_frame_type) < 0");
++        return;
++    }
++
++    if (PyType_Ready(&sr_py_ruby_stacktrace_type) < 0)
++    {
++        puts("PyType_Ready(&sr_py_ruby_stacktrace_type) < 0");
++        return;
++    }
++
+     if (PyType_Ready(&sr_py_operating_system_type) < 0)
+     {
+         puts("PyType_Ready(&sr_py_operating_system_type) < 0");
+@@ -301,6 +315,14 @@ init_satyr()
+     PyModule_AddObject(module, "JavaStacktrace",
+                        (PyObject *)&sr_py_java_stacktrace_type);
+ 
++    Py_INCREF(&sr_py_ruby_frame_type);
++    PyModule_AddObject(module, "RubyFrame",
++                       (PyObject *)&sr_py_ruby_frame_type);
++
++    Py_INCREF(&sr_py_ruby_stacktrace_type);
++    PyModule_AddObject(module, "RubyStacktrace",
++                       (PyObject *)&sr_py_ruby_stacktrace_type);
++
+     Py_INCREF(&sr_py_core_frame_type);
+     PyModule_AddObject(module, "CoreFrame",
+                        (PyObject *)&sr_py_core_frame_type);
+diff --git a/python/py_report.c b/python/py_report.c
+index 8aa35dd..74cd9dd 100644
+--- a/python/py_report.c
++++ b/python/py_report.c
+@@ -28,6 +28,7 @@
+ #include "py_python_stacktrace.h"
+ #include "py_koops_stacktrace.h"
+ #include "py_java_stacktrace.h"
++#include "py_ruby_stacktrace.h"
+ 
+ #include "report.h"
+ #include "operating_system.h"
+@@ -284,6 +285,10 @@ report_prepare_subobjects(struct sr_py_report *report)
+             if (stacktrace_prepare(report, &sr_py_koops_stacktrace_type, false) < 0)
+                 return -1;
+             break;
++        case SR_REPORT_RUBY:
++            if (stacktrace_prepare(report, &sr_py_ruby_stacktrace_type, false) < 0)
++                return -1;
++            break;
+         default:
+             report->report->stacktrace = NULL;
+             break;
+@@ -347,6 +352,10 @@ report_to_python_obj(struct sr_report *report)
+             ro->stacktrace = koops_stacktrace_to_python_obj(
+                     (struct sr_koops_stacktrace *)report->stacktrace);
+             break;
++        case SR_REPORT_RUBY:
++            ro->stacktrace = ruby_stacktrace_to_python_obj(
++                    (struct sr_ruby_stacktrace *)report->stacktrace);
++            break;
+         default:
+             Py_INCREF(Py_None);
+             ro->stacktrace = Py_None;
+diff --git a/python/py_ruby_frame.c b/python/py_ruby_frame.c
+new file mode 100644
+index 0000000..9ed45ad
+--- /dev/null
++++ b/python/py_ruby_frame.c
+@@ -0,0 +1,227 @@
++/*
++    py_ruby_frame.c
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#include "py_common.h"
++#include "py_base_frame.h"
++#include "py_ruby_frame.h"
++#include "location.h"
++#include "strbuf.h"
++#include "utils.h"
++#include "ruby/frame.h"
++
++#define frame_doc "satyr.RubyFrame - class representing a ruby frame\n\n" \
++                  "Usage:\n\n" \
++                  "satyr.RubyFrame() - creates an empty frame\n\n" \
++                  "satyr.RubyFrame(str) - parses str and fills the frame object"
++
++#define f_dup_doc "Usage: frame.dup()\n\n" \
++                  "Returns: satyr.RubyFrame - a new clone of frame\n\n" \
++                  "Clones the frame object. All new structures are independent " \
++                  "of the original object."
++
++
++static PyMethodDef
++frame_methods[] =
++{
++    /* methods */
++    { "dup", sr_py_ruby_frame_dup, METH_NOARGS,  f_dup_doc },
++    { NULL },
++};
++
++/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
++#define GSOFF_PY_STRUCT sr_py_ruby_frame
++#define GSOFF_PY_MEMBER frame
++#define GSOFF_C_STRUCT sr_ruby_frame
++GSOFF_START
++GSOFF_MEMBER(file_name),
++GSOFF_MEMBER(file_line),
++GSOFF_MEMBER(special_function),
++GSOFF_MEMBER(function_name),
++GSOFF_MEMBER(block_level),
++GSOFF_MEMBER(rescue_level)
++GSOFF_END
++
++static PyGetSetDef
++frame_getset[] =
++{
++    SR_ATTRIBUTE_STRING(file_name,        "Source file name (string)"                              ),
++    SR_ATTRIBUTE_UINT32(file_line,        "Source line number (positive integer)"                  ),
++    SR_ATTRIBUTE_BOOL  (special_function, "True if the frame doesn't belong to an actual function" ),
++    SR_ATTRIBUTE_STRING(function_name,    "Function name (string)"                                 ),
++    SR_ATTRIBUTE_UINT32(block_level,      "Block nesting level (integer)"                          ),
++    SR_ATTRIBUTE_UINT32(rescue_level,     "Rescue block nesting level (integer)"                   ),
++    { NULL },
++};
++
++PyTypeObject
++sr_py_ruby_frame_type =
++{
++    PyObject_HEAD_INIT(NULL)
++    0,
++    "satyr.RubyFrame",          /* tp_name */
++    sizeof(struct sr_py_ruby_frame), /* tp_basicsize */
++    0,                          /* tp_itemsize */
++    sr_py_ruby_frame_free,      /* tp_dealloc */
++    NULL,                       /* tp_print */
++    NULL,                       /* tp_getattr */
++    NULL,                       /* tp_setattr */
++    NULL,                       /* tp_compare */
++    NULL,                       /* tp_repr */
++    NULL,                       /* tp_as_number */
++    NULL,                       /* tp_as_sequence */
++    NULL,                       /* tp_as_mapping */
++    NULL,                       /* tp_hash */
++    NULL,                       /* tp_call */
++    sr_py_ruby_frame_str,       /* tp_str */
++    NULL,                       /* tp_getattro */
++    NULL,                       /* tp_setattro */
++    NULL,                       /* tp_as_buffer */
++    Py_TPFLAGS_DEFAULT,         /* tp_flags */
++    frame_doc,                  /* tp_doc */
++    NULL,                       /* tp_traverse */
++    NULL,                       /* tp_clear */
++    NULL,                       /* tp_richcompare */
++    0,                          /* tp_weaklistoffset */
++    NULL,                       /* tp_iter */
++    NULL,                       /* tp_iternext */
++    frame_methods,              /* tp_methods */
++    NULL,                       /* tp_members */
++    frame_getset,               /* tp_getset */
++    &sr_py_base_frame_type,     /* tp_base */
++    NULL,                       /* tp_dict */
++    NULL,                       /* tp_descr_get */
++    NULL,                       /* tp_descr_set */
++    0,                          /* tp_dictoffset */
++    NULL,                       /* tp_init */
++    NULL,                       /* tp_alloc */
++    sr_py_ruby_frame_new,       /* tp_new */
++    NULL,                       /* tp_free */
++    NULL,                       /* tp_is_gc */
++    NULL,                       /* tp_bases */
++    NULL,                       /* tp_mro */
++    NULL,                       /* tp_cache */
++    NULL,                       /* tp_subclasses */
++    NULL,                       /* tp_weaklist */
++};
++
++/* constructor */
++PyObject *
++sr_py_ruby_frame_new(PyTypeObject *object, PyObject *args, PyObject *kwds)
++{
++    struct sr_py_ruby_frame *fo = (struct sr_py_ruby_frame*)
++        PyObject_New(struct sr_py_ruby_frame, &sr_py_ruby_frame_type);
++
++    if (!fo)
++        return PyErr_NoMemory();
++
++    const char *str = NULL;
++    if (!PyArg_ParseTuple(args, "|s", &str))
++        return NULL;
++
++    if (str)
++    {
++        struct sr_location location;
++        sr_location_init(&location);
++        fo->frame = sr_ruby_frame_parse(&str, &location);
++
++        if (!fo->frame)
++        {
++            PyErr_SetString(PyExc_ValueError, location.message);
++            return NULL;
++        }
++    }
++    else
++        fo->frame = sr_ruby_frame_new();
++
++    return (PyObject*)fo;
++}
++
++/* destructor */
++void
++sr_py_ruby_frame_free(PyObject *object)
++{
++    struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)object;
++    sr_ruby_frame_free(this->frame);
++    PyObject_Del(object);
++}
++
++/* str */
++PyObject *
++sr_py_ruby_frame_str(PyObject *self)
++{
++    struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)self;
++    struct sr_strbuf *buf = sr_strbuf_new();
++
++
++    if (this->frame->file_name)
++    {
++        sr_strbuf_append_str(buf, this->frame->file_name);
++    }
++
++    if (this->frame->file_line)
++    {
++        sr_strbuf_append_strf(buf, ":%d", this->frame->file_line);
++    }
++
++    if (this->frame->function_name)
++    {
++        sr_strbuf_append_str(buf, ":in `");
++
++        int i;
++        for (i = 0; i < this->frame->rescue_level; i++)
++        {
++            sr_strbuf_append_str(buf, "rescue in ");
++        }
++
++        if (this->frame->block_level == 1)
++        {
++            sr_strbuf_append_str(buf, "block in ");
++        }
++        else if (this->frame->block_level > 1)
++        {
++            sr_strbuf_append_strf(buf, "block (%d levels) in ", this->frame->block_level);
++        }
++
++        sr_strbuf_append_strf(buf, "%s%s%s'",
++                              (this->frame->special_function ? "<" : ""),
++                              this->frame->function_name,
++                              (this->frame->special_function ? ">" : ""));
++    }
++
++    char *str = sr_strbuf_free_nobuf(buf);
++    PyObject *result = Py_BuildValue("s", str);
++    free(str);
++    return result;
++}
++
++/* methods */
++PyObject *
++sr_py_ruby_frame_dup(PyObject *self, PyObject *args)
++{
++    struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)self;
++    struct sr_py_ruby_frame *fo = (struct sr_py_ruby_frame*)
++        PyObject_New(struct sr_py_ruby_frame, &sr_py_ruby_frame_type);
++
++    if (!fo)
++        return PyErr_NoMemory();
++
++    fo->frame = sr_ruby_frame_dup(this->frame, false);
++
++    return (PyObject*)fo;
++}
+diff --git a/python/py_ruby_frame.h b/python/py_ruby_frame.h
+new file mode 100644
+index 0000000..7f460ff
+--- /dev/null
++++ b/python/py_ruby_frame.h
+@@ -0,0 +1,69 @@
++/*
++    py_ruby_frame.h
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_PY_RUBY_FRAME_H
++#define SATYR_PY_RUBY_FRAME_H
++
++/**
++ * @file
++ * @brief Python bindings for RUBY frame.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include <Python.h>
++#include <structmember.h>
++
++PyTypeObject sr_py_ruby_frame_type;
++
++/* The beginning of this structure has to have the same layout as
++ * sr_py_base_frame.
++ */
++struct sr_py_ruby_frame
++{
++    PyObject_HEAD
++    struct sr_ruby_frame *frame;
++};
++
++/**
++ * Constructor.
++ */
++PyObject *sr_py_ruby_frame_new(PyTypeObject *object,
++                               PyObject *args, PyObject *kwds);
++
++/**
++ * Destructor.
++ */
++void sr_py_ruby_frame_free(PyObject *object);
++
++/**
++ * str
++ */
++PyObject *sr_py_ruby_frame_str(PyObject *self);
++
++/* methods */
++PyObject *sr_py_ruby_frame_dup(PyObject *self, PyObject *args);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/python/py_ruby_stacktrace.c b/python/py_ruby_stacktrace.c
+new file mode 100644
+index 0000000..97fbb44
+--- /dev/null
++++ b/python/py_ruby_stacktrace.c
+@@ -0,0 +1,182 @@
++#include "py_common.h"
++#include "py_ruby_frame.h"
++#include "py_ruby_stacktrace.h"
++#include "py_base_stacktrace.h"
++#include "utils.h"
++#include "strbuf.h"
++#include "ruby/frame.h"
++#include "ruby/stacktrace.h"
++#include "location.h"
++#include "stacktrace.h"
++
++#define stacktrace_doc "satyr.RubyStacktrace - class representing a ruby stacktrace\n\n" \
++                       "Usage:\n\n" \
++                       "satyr.RubyStacktrace() - creates an empty ruby stacktrace\n\n" \
++                       "satyr.RubyStacktrace(str) - parses str and fills the ruby stacktrace object"
++
++#define f_dup_doc "Usage: stacktrace.dup()\n\n" \
++                  "Returns: satyr.RubyStacktrace - a new clone of ruby stacktrace\n\n" \
++                  "Clones the RubyStacktrace object. All new structures are independent " \
++                  "of the original object."
++
++
++static PyMethodDef
++ruby_stacktrace_methods[] =
++{
++    /* methods */
++    { "dup", sr_py_ruby_stacktrace_dup, METH_NOARGS, f_dup_doc },
++    { NULL },
++};
++
++/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
++#define GSOFF_PY_STRUCT sr_py_ruby_stacktrace
++#define GSOFF_PY_MEMBER stacktrace
++#define GSOFF_C_STRUCT sr_ruby_stacktrace
++GSOFF_START
++GSOFF_MEMBER(exception_name)
++GSOFF_END
++
++static PyGetSetDef
++ruby_stacktrace_getset[] =
++{
++    SR_ATTRIBUTE_STRING(exception_name, "Exception type (string)"              ),
++    { NULL },
++};
++
++PyTypeObject sr_py_ruby_stacktrace_type = {
++    PyObject_HEAD_INIT(NULL)
++    0,
++    "satyr.RubyStacktrace",         /* tp_name */
++    sizeof(struct sr_py_ruby_stacktrace), /* tp_basicsize */
++    0,                              /* tp_itemsize */
++    sr_py_ruby_stacktrace_free,     /* tp_dealloc */
++    NULL,                           /* tp_print */
++    NULL,                           /* tp_getattr */
++    NULL,                           /* tp_setattr */
++    NULL,                           /* tp_compare */
++    NULL,                           /* tp_repr */
++    NULL,                           /* tp_as_number */
++    NULL,                           /* tp_as_sequence */
++    NULL,                           /* tp_as_mapping */
++    NULL,                           /* tp_hash */
++    NULL,                           /* tp_call */
++    sr_py_ruby_stacktrace_str,      /* tp_str */
++    NULL,                           /* tp_getattro */
++    NULL,                           /* tp_setattro */
++    NULL,                           /* tp_as_buffer */
++    Py_TPFLAGS_DEFAULT,             /* tp_flags */
++    stacktrace_doc,                 /* tp_doc */
++    NULL,                           /* tp_traverse */
++    NULL,                           /* tp_clear */
++    NULL,                           /* tp_richcompare */
++    0,                              /* tp_weaklistoffset */
++    NULL,                           /* tp_iter */
++    NULL,                           /* tp_iternext */
++    ruby_stacktrace_methods,        /* tp_methods */
++    NULL,                           /* tp_members */
++    ruby_stacktrace_getset,         /* tp_getset */
++    &sr_py_single_stacktrace_type,  /* tp_base */
++    NULL,                           /* tp_dict */
++    NULL,                           /* tp_descr_get */
++    NULL,                           /* tp_descr_set */
++    0,                              /* tp_dictoffset */
++    NULL,                           /* tp_init */
++    NULL,                           /* tp_alloc */
++    sr_py_ruby_stacktrace_new,      /* tp_new */
++    NULL,                           /* tp_free */
++    NULL,                           /* tp_is_gc */
++    NULL,                           /* tp_bases */
++    NULL,                           /* tp_mro */
++    NULL,                           /* tp_cache */
++    NULL,                           /* tp_subclasses */
++    NULL,                           /* tp_weaklist */
++};
++
++PyObject *
++ruby_stacktrace_to_python_obj(struct sr_ruby_stacktrace *stacktrace)
++{
++    struct sr_py_ruby_stacktrace *bo = PyObject_New(struct sr_py_ruby_stacktrace,
++                                                    &sr_py_ruby_stacktrace_type);
++    if (!bo)
++        return PyErr_NoMemory();
++
++    bo->frame_type = &sr_py_ruby_frame_type;
++
++    bo->stacktrace = stacktrace;
++    bo->frames = frames_to_python_list((struct sr_thread *)bo->stacktrace,
++                                       bo->frame_type);
++    if (!bo->frames)
++        return NULL;
++
++    return (PyObject *)bo;
++}
++
++/* constructor */
++PyObject *
++sr_py_ruby_stacktrace_new(PyTypeObject *object,
++                            PyObject *args,
++                            PyObject *kwds)
++{
++    const char *str = NULL;
++    if (!PyArg_ParseTuple(args, "|s", &str))
++        return NULL;
++
++    struct sr_ruby_stacktrace *stacktrace;
++
++    if (str)
++    {
++        struct sr_location location;
++        sr_location_init(&location);
++        stacktrace = sr_ruby_stacktrace_parse(&str, &location);
++        if (!stacktrace)
++        {
++            PyErr_SetString(PyExc_ValueError, location.message);
++            return NULL;
++        }
++    }
++    else
++        stacktrace = sr_ruby_stacktrace_new();
++
++    return ruby_stacktrace_to_python_obj(stacktrace);
++}
++
++/* destructor */
++void
++sr_py_ruby_stacktrace_free(PyObject *object)
++{
++    struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace*)object;
++    /* the list will decref all of its elements */
++    Py_DECREF(this->frames);
++    this->stacktrace->frames = NULL;
++    sr_ruby_stacktrace_free(this->stacktrace);
++    PyObject_Del(object);
++}
++
++/* str */
++PyObject *
++sr_py_ruby_stacktrace_str(PyObject *self)
++{
++    struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace *)self;
++    struct sr_strbuf *buf = sr_strbuf_new();
++    sr_strbuf_append_strf(buf, "Ruby stacktrace with %zd frames",
++                         (ssize_t)(PyList_Size(this->frames)));
++    char *str = sr_strbuf_free_nobuf(buf);
++    PyObject *result = Py_BuildValue("s", str);
++    free(str);
++    return result;
++}
++
++/* methods */
++PyObject *
++sr_py_ruby_stacktrace_dup(PyObject *self, PyObject *args)
++{
++    struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace*)self;
++    if (frames_prepare_linked_list((struct sr_py_base_thread *)this) < 0)
++        return NULL;
++
++    struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_dup(this->stacktrace);
++    if (!stacktrace)
++        return NULL;
++
++    return ruby_stacktrace_to_python_obj(stacktrace);
++}
+diff --git a/python/py_ruby_stacktrace.h b/python/py_ruby_stacktrace.h
+new file mode 100644
+index 0000000..f4eebce
+--- /dev/null
++++ b/python/py_ruby_stacktrace.h
+@@ -0,0 +1,71 @@
++/*
++    py_ruby_stacktrace.h
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_PY_RUBY_STACKTRACE_H
++#define SATYR_PY_RUBY_STACKTRACE_H
++
++/**
++ * @file
++ * @brief Python bindings for Ruby stack trace.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include <Python.h>
++#include <structmember.h>
++
++PyTypeObject sr_py_ruby_stacktrace_type;
++
++/* The beginning of this structure has to have the same layout as
++ * sr_py_base_thread.
++ */
++struct sr_py_ruby_stacktrace
++{
++    PyObject_HEAD
++    struct sr_ruby_stacktrace *stacktrace;
++    PyObject *frames;
++    PyTypeObject *frame_type;
++};
++
++/* helpers */
++PyObject *ruby_stacktrace_to_python_obj(struct sr_ruby_stacktrace *stacktrace);
++
++/* constructor */
++PyObject *sr_py_ruby_stacktrace_new(PyTypeObject *object,
++                                    PyObject *args,
++                                    PyObject *kwds);
++
++/* destructor */
++void sr_py_ruby_stacktrace_free(PyObject *object);
++
++/* str */
++PyObject *sr_py_ruby_stacktrace_str(PyObject *self);
++
++/* methods */
++PyObject *sr_py_ruby_stacktrace_dup(PyObject *self, PyObject *args);
++PyObject *sr_py_ruby_stacktrace_normalize(PyObject *self, PyObject *args);
++
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index 0eb33a5..b261880 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -4,7 +4,16 @@ EXTRA_DIST = gdb_stacktraces \
+              json_files \
+              kerneloopses \
+              programs \
+-             python \
++             python/core.py \
++             python/gdb.py \
++             python/java.py \
++             python/koops.py \
++             python/metrics.py \
++             python/misc.py \
++             python/python.py \
++             python/report.py \
++             python/ruby.py \
++             python/test_helpers.py \
+              python_stacktraces
+ 
+ ## ------------ ##
+diff --git a/tests/python/ruby.py b/tests/python/ruby.py
+new file mode 100755
+index 0000000..8a20593
+--- /dev/null
++++ b/tests/python/ruby.py
+@@ -0,0 +1,174 @@
++#!/usr/bin/env python
++
++import unittest
++from test_helpers import *
++
++path = '../ruby_stacktraces/ruby-01'
++contents = load_input_contents(path)
++frames_expected = 21
++expected_short_text = '''#1 rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13
++#2 block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:10
++#3 times in /usr/share/ruby/vendor_ruby/will_crash.rb:9
++#4 block in func in /usr/share/ruby/vendor_ruby/will_crash.rb:9
++#5 times in /usr/share/ruby/vendor_ruby/will_crash.rb:8
++#6 func in /usr/share/ruby/vendor_ruby/will_crash.rb:8
++'''
++
++class TestRubyStacktrace(BindingsTestCase):
++    def setUp(self):
++        self.trace = satyr.RubyStacktrace(contents)
++
++    def test_correct_frame_count(self):
++        self.assertEqual(frame_count(self.trace), frames_expected)
++
++    def test_dup(self):
++        dup = self.trace.dup()
++        self.assertNotEqual(id(dup.frames), id(self.trace.frames))
++        self.assertTrue(all(map(lambda t1, t2: t1.equals(t2), dup.frames, self.trace.frames)))
++
++        dup.frames = dup.frames[:5]
++        dup2 = dup.dup()
++        self.assertEqual(len(dup.frames), len(dup2.frames))
++        self.assertNotEqual(id(dup.frames), id(dup2.frames))
++
++    def test_prepare_linked_list(self):
++        dup = self.trace.dup()
++        dup.frames = dup.frames[:5]
++        dup2 = dup.dup()
++        self.assertTrue(len(dup2.frames) <= 5)
++
++    def test_str(self):
++        out = str(self.trace)
++        self.assertTrue(('Ruby stacktrace with %d frames' % frames_expected) in out)
++
++    def test_getset(self):
++        self.assertGetSetCorrect(self.trace, 'exception_name', 'Wrap::MyException', 'WhateverException')
++
++    def test_special_functions(self):
++        trace = load_input_contents('../ruby_stacktraces/ruby-01')
++        trace = satyr.RubyStacktrace(trace)
++
++        f = trace.frames[0]
++        self.assertEqual(f.file_name, '/usr/share/ruby/vendor_ruby/will_crash.rb')
++        self.assertEqual(f.function_name, 'func')
++        self.assertFalse(f.special_function)
++        self.assertEqual(f.block_level, 2)
++        self.assertEqual(f.rescue_level, 1)
++
++        f = trace.frames[-1]
++        self.assertEqual(f.file_name, '/usr/bin/will_ruby_raise')
++        self.assertEqual(f.function_name, 'main')
++        self.assertTrue(f.special_function)
++        self.assertEqual(f.block_level, 0)
++        self.assertEqual(f.rescue_level, 0)
++
++    def test_to_short_text(self):
++        self.assertEqual(self.trace.to_short_text(6), expected_short_text)
++
++    def test_bthash(self):
++        self.assertEqual(self.trace.get_bthash(), '6124e03542a0381b14ebaf2c5b5e9f467ebba33b')
++
++    def test_duphash(self):
++        expected_plain = '''Thread
++/usr/share/ruby/vendor_ruby/will_crash.rb:13
++/usr/share/ruby/vendor_ruby/will_crash.rb:10
++/usr/share/ruby/vendor_ruby/will_crash.rb:9
++'''
++        self.assertEqual(self.trace.get_duphash(flags=satyr.DUPHASH_NOHASH, frames=3), expected_plain)
++        self.assertEqual(self.trace.get_duphash(), 'c877cda04fbce8d51c4d9b1d628b0f618677607e')
++
++    def test_crash_thread(self):
++        self.assertTrue(self.trace.crash_thread is self.trace)
++
++    def test_from_json(self):
++        trace = satyr.RubyStacktrace.from_json('{}')
++        self.assertEqual(trace.frames, [])
++
++        json_text = '''
++        {
++            "exception_name": "NotImplementedError",
++            "stacktrace": [
++                {
++                    "file_name": "/usr/share/foobar/mod/file.rb",
++                    "function_name": "do_nothing",
++                    "file_line": 42,
++                },
++                {
++                    "special_function": "top (required)",
++                    "file_line": 451
++                },
++                {
++                    "unknown_key": 19876543,
++                    "block_level": 42,
++                    "rescue_level": 6
++                }
++            ]
++        }
++'''
++        trace = satyr.RubyStacktrace.from_json(json_text)
++        self.assertEqual(trace.exception_name, 'NotImplementedError')
++        self.assertEqual(len(trace.frames), 3)
++
++        self.assertEqual(trace.frames[0].file_name, '/usr/share/foobar/mod/file.rb')
++        self.assertEqual(trace.frames[0].function_name, 'do_nothing')
++        self.assertEqual(trace.frames[0].file_line, 42)
++        self.assertFalse(trace.frames[0].special_function)
++        self.assertEqual(trace.frames[0].block_level, 0)
++        self.assertEqual(trace.frames[0].rescue_level, 0)
++
++        self.assertEqual(trace.frames[1].file_name, None)
++        self.assertEqual(trace.frames[1].function_name, 'top (required)')
++        self.assertEqual(trace.frames[1].file_line, 451)
++        self.assertTrue(trace.frames[1].special_function)
++        self.assertEqual(trace.frames[1].block_level, 0)
++        self.assertEqual(trace.frames[1].rescue_level, 0)
++
++        self.assertEqual(trace.frames[2].file_name, None)
++        self.assertEqual(trace.frames[2].function_name, None)
++        self.assertEqual(trace.frames[2].file_line, 0)
++        self.assertFalse(trace.frames[2].special_function)
++        self.assertEqual(trace.frames[2].block_level, 42)
++        self.assertEqual(trace.frames[2].rescue_level, 6)
++
++    def test_hash(self):
++        self.assertHashable(self.trace)
++
++
++class TestRubyFrame(BindingsTestCase):
++    def setUp(self):
++        self.frame = satyr.RubyStacktrace(contents).frames[1]
++
++    def test_str(self):
++        out = str(self.frame)
++        self.assertEqual(out, "/usr/share/ruby/vendor_ruby/will_crash.rb:10:in `block (2 levels) in func'")
++
++    def test_dup(self):
++        dup = self.frame.dup()
++        self.assertEqual(dup.function_name,
++            self.frame.function_name)
++
++        dup.function_name = 'other'
++        self.assertNotEqual(dup.function_name,
++            self.frame.function_name)
++
++    def test_cmp(self):
++        dup = self.frame.dup()
++        self.assertTrue(dup.equals(dup))
++        self.assertTrue(dup.equals(self.frame))
++        self.assertNotEqual(id(dup), id(self.frame))
++        dup.function_name = 'another'
++        self.assertFalse(dup.equals(self.frame))
++
++    def test_getset(self):
++        self.assertGetSetCorrect(self.frame, 'file_name', '/usr/share/ruby/vendor_ruby/will_crash.rb', 'java.rs')
++        self.assertGetSetCorrect(self.frame, 'file_line', 10, 6667)
++        self.assertGetSetCorrect(self.frame, 'special_function', False, True)
++        self.assertGetSetCorrect(self.frame, 'function_name', 'func', 'iiiiii')
++        self.assertGetSetCorrect(self.frame, 'block_level', 2, 666)
++        self.assertGetSetCorrect(self.frame, 'rescue_level', 0, 9000)
++
++    def test_hash(self):
++        self.assertHashable(self.frame)
++
++if __name__ == '__main__':
++    unittest.main()
+diff --git a/tests/python_bindings.at b/tests/python_bindings.at
+index a5e19c3..906f561 100644
+--- a/tests/python_bindings.at
++++ b/tests/python_bindings.at
+@@ -12,5 +12,6 @@ AT_TEST_PYTHON([koops])
+ AT_TEST_PYTHON([python])
+ AT_TEST_PYTHON([java])
+ AT_TEST_PYTHON([core])
++AT_TEST_PYTHON([ruby])
+ AT_TEST_PYTHON([metrics])
+ AT_TEST_PYTHON([report])
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch b/SOURCES/satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch
new file mode 100644
index 0000000..3dd268c
--- /dev/null
+++ b/SOURCES/satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch
@@ -0,0 +1,44 @@
+From c1e7562809a1ea9955d43bc21fa2b2ac88d95ee5 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Fri, 20 Feb 2015 15:50:32 +0100
+Subject: [PATCH] tests: fix failure on gcc5 on x86_64
+
+Resolves #1334604
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ tests/ruby_stacktrace.at | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/tests/ruby_stacktrace.at b/tests/ruby_stacktrace.at
+index 5a2d901..09d5638 100644
+--- a/tests/ruby_stacktrace.at
++++ b/tests/ruby_stacktrace.at
+@@ -261,6 +261,7 @@ main(void)
+ 
+ AT_TESTFUN([sr_ruby_stacktrace_from_json],
+ [[
++#include "stacktrace.h"
+ #include "ruby/stacktrace.h"
+ #include "ruby/frame.h"
+ #include "utils.h"
+@@ -268,13 +269,14 @@ AT_TESTFUN([sr_ruby_stacktrace_from_json],
+ #include <assert.h>
+ #include <stdlib.h>
+ #include <stdio.h>
++#include <string.h>
+ 
+ void
+ check(char *filename)
+ {
+   char *error_message = NULL;
+-  const char *file_contents = sr_file_to_string(filename, &error_message);
+-  const char *input = file_contents;
++  char *file_contents = sr_file_to_string(filename, &error_message);
++  char *input = file_contents;
+   struct sr_location location;
+   sr_location_init(&location);
+ 
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch b/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch
index 9a1abac..735c3ca 100644
--- a/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch
+++ b/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch
@@ -1,7 +1,18 @@
-diff -ur satyr-0.13/configure satyr-0.13-patched/configure
---- satyr-0.13/configure	2014-01-22 18:44:45.710649231 +0100
-+++ satyr-0.13-patched/configure	2014-01-22 18:40:12.606671169 +0100
-@@ -13261,10 +13255,10 @@
+From 7351e5b5ad04bd840e75fafb8ab19d4e0c90d717 Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Fri, 13 May 2016 14:42:23 +0200
+Subject: [PATCH] unwinder refresh config h
+
+---
+ configure       | 6 +++---
+ lib/config.h.in | 4 ++--
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/configure b/configure
+index 2418533..f79cfeb 100755
+--- a/configure
++++ b/configure
+@@ -13261,10 +13261,10 @@ _ACEOF
  
  fi
  
@@ -15,9 +26,10 @@ diff -ur satyr-0.13/configure satyr-0.13-patched/configure
  
  fi
  
-diff -ur satyr-0.13/lib/config.h.in satyr-0.13-patched/lib/config.h.in
---- satyr-0.13/lib/config.h.in	2014-01-22 18:44:45.796649204 +0100
-+++ satyr-0.13-patched/lib/config.h.in	2014-01-22 18:40:13.001671173 +0100
+diff --git a/lib/config.h.in b/lib/config.h.in
+index b5c34c6..b7b6255 100644
+--- a/lib/config.h.in
++++ b/lib/config.h.in
 @@ -6,8 +6,8 @@
  /* Define to 1 if you have the <dwarf.h> header file. */
  #undef HAVE_DWARF_H
@@ -29,3 +41,6 @@ diff -ur satyr-0.13/lib/config.h.in satyr-0.13-patched/lib/config.h.in
  
  /* Define to 1 if you have the <elfutils/libdwfl.h> header file. */
  #undef HAVE_ELFUTILS_LIBDWFL_H
+-- 
+1.8.3.1
+
diff --git a/SPECS/satyr.spec b/SPECS/satyr.spec
index 41b18e5..f0cf641 100644
--- a/SPECS/satyr.spec
+++ b/SPECS/satyr.spec
@@ -19,7 +19,7 @@
 
 Name: satyr
 Version: 0.13
-Release: 12%{?dist}
+Release: 14%{?dist}
 Summary: Tools to create anonymous, machine-friendly problem reports
 Group: System Environment/Libraries
 License: GPLv2+
@@ -38,6 +38,11 @@ BuildRequires: gcc-c++
 BuildRequires: python-sphinx
 %endif
 
+# git is need for '%%autosetup -S git' which automatically applies all the
+# patches above. Please, be aware that the patches must be generated
+# by 'git format-patch'
+BuildRequires: git
+
 Patch0: satyr-0.13-elfutils-0.158.patch
 Patch1: satyr-0.13-elfutils-unwinder.patch
 Patch2: satyr-0.13-disable-fingerprints.patch
@@ -79,6 +84,28 @@ Patch17: satyr-0.13-disable-hook-unwind-on-kernels-w-o-PTRACE_SEIZE.patch
 Patch18: satyr-0.13-abrt-refactorize-unwinding-from-core-hook.patch
 Patch19: satyr-0.13-core_unwind-fix-the-missing-frame-build_id-and-file.patch
 
+# 1334604, add support for Ruby
+Patch20: satyr-0.13-Add-support-for-Ruby-report-type.patch
+Patch21: satyr-0.13-python-add-Ruby-support.patch
+
+# 1332869, actualize list of normalization function in satyr
+Patch22: satyr-0.13-normalize-extend-xorg-blacklist.patch
+Patch23: satyr-0.13-normalization-additional-X-GDK-functions.patch
+Patch24: satyr-0.13-normalization-add-glibc-__assert_fail_base.patch
+Patch25: satyr-0.13-normalization-add-glibc-__libc_fatal.patch
+Patch26: satyr-0.13-normalization-normalize-out-exit-frames.patch
+Patch27: satyr-0.13-normalization-actualize-list-of-functions.patch
+
+# 1334604, add support for Ruby testsuite fix
+Patch28: satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch
+
+# 1336390, fix defects found by coverity
+Patch29: satyr-0.13-Fix-defects-found-by-coverity.patch
+Patch30: satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch
+
+# 1342469, support for VARIANT and VARIANT_ID
+Patch31: satyr-0.13-os-add-support-for-OS-Variant.patch
+
 %description
 Satyr is a library that can be used to create and process microreports.
 Microreports consist of structured data suitable to be analyzed in a fully
@@ -105,29 +132,16 @@ Requires: %{name}%{?_isa} = %{version}-%{release}
 Python bindings for %{name}.
 
 %prep
-%setup -q
-%patch0 -p1
-%patch1 -p1
-%patch2 -p1
-%patch3 -p1
-%patch4 -p1
-%patch5 -p1
-%patch6 -p1
-%patch7 -p1
-%patch8 -p1
-%patch9 -p1
-%patch10 -p1
-%patch11 -p1
-%patch12 -p1
-%patch13 -p1
-%patch14 -p1
-%patch15 -p1
-%patch16 -p1
-%patch17 -p1
-%patch18 -p1
-%patch19 -p1
+# http://www.rpm.org/wiki/PackagerDocs/Autosetup
+# Default '__scm_apply_git' is 'git apply && git commit' but this workflow
+# doesn't allow us to create a new file within a patch, so we have to use
+# 'git am' (see /usr/lib/rpm/macros for more details)
+%define __scm_apply_git(qp:m:) %{__git} am
+%autosetup -S git
 
 %build
+autoreconf
+
 %configure \
 %if ! %{?enable_python_manpage}
         --disable-python-manpage \
@@ -143,7 +157,13 @@ make install DESTDIR=%{buildroot}
 find %{buildroot} -name "*.la" | xargs rm --
 
 %check
-make check
+make check || {
+    # find and print the logs of failed test
+    # do not cat tests/testsuite.log because it contains a lot of bloat
+    find tests -name "testsuite.log" -print -exec cat '{}' \;
+    exit 1
+}
+
 
 %post -p /sbin/ldconfig
 %postun -p /sbin/ldconfig
@@ -168,6 +188,18 @@ make check
 %endif
 
 %changelog
+* Mon Jun 06 2016 Matej Habrnal <mhabrnal@redhat.com> - 0.13-14
+- add support for OS Variant
+  - Related: #1342469
+
+* Thu May 12 2016 Matej Habrnal <mhabrnal@redhat.com> - 0.13-13
+- add support for Ruby
+  - Related: #1334604
+- actualize list of normalization function in satyr
+  - Related: #1332869
+- fix defects found by coverity
+  - Related: #1336390
+
 * Wed Sep 9 2015 Richard Marko <rmarko@redhat.com> - 0.13-12
 - apply last patch
   - Related: #1210599