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