Closes #86 and closes #92.
---
python/Makefile.am | 2 +
python/py_common.c | 7 +
python/py_common.h | 4 +
python/py_module.c | 10 +
python/py_report.c | 491 +++++++++++++++++++++++++++++++++++++++++++++
python/py_report.h | 79 ++++++++
tests/json_files/ureport-1 | 107 ++++++++++
tests/python/report.py | 49 +++++
8 files changed, 749 insertions(+)
create mode 100644 python/py_report.c
create mode 100644 python/py_report.h
create mode 100644 tests/json_files/ureport-1
diff --git a/python/Makefile.am b/python/Makefile.am
index 34491c1..a2e95b8 100644
--- a/python/Makefile.am
+++ b/python/Makefile.am
@@ -48,6 +48,8 @@ _satyr_la_SOURCES = \
py_metrics.c \
py_operating_system.h \
py_operating_system.c \
+ py_report.h \
+ py_report.c \
py_common.h \
py_common.c \
py_module.c
diff --git a/python/py_common.c b/python/py_common.c
index 80b76f1..f8fdbb3 100644
--- a/python/py_common.c
+++ b/python/py_common.c
@@ -199,6 +199,13 @@ sr_py_setter_uint64(PyObject *self, PyObject *rhs, void *data)
}
int
+sr_py_setter_readonly(PyObject *self, PyObject *rhs, void *data)
+{
+ PyErr_SetString(PyExc_AttributeError, "This attribute is read-only.");
+ return -1;
+}
+
+int
normalize_cmp(int n)
{
if (n > 0)
diff --git a/python/py_common.h b/python/py_common.h
index 757df21..a54baa1 100644
--- a/python/py_common.h
+++ b/python/py_common.h
@@ -128,6 +128,10 @@ PyObject *sr_py_getter_uint64(PyObject *self, void *data);
/* NOTE: when rhs is None, the attribute is set to UINT64_MAX */
int sr_py_setter_uint64(PyObject *self, PyObject *rhs, void *data);
+/* Just throws an exception. */
+int
+sr_py_setter_readonly(PyObject *self, PyObject *rhs, void *data);
+
/*
* Satyr's comparison functions return arbitrary numbers, while python
* requires the result to be in {-1, 0, 1}.
diff --git a/python/py_module.c b/python/py_module.c
index e566376..841adf9 100644
--- a/python/py_module.c
+++ b/python/py_module.c
@@ -20,6 +20,7 @@
#include "py_rpm_package.h"
#include "py_metrics.h"
#include "py_operating_system.h"
+#include "py_report.h"
#include "distance.h"
#include "thread.h"
@@ -166,6 +167,12 @@ init_satyr()
return;
}
+ if (PyType_Ready(&sr_py_report_type) < 0)
+ {
+ puts("PyType_Ready(&sr_py_report_type) < 0");
+ return;
+ }
+
if (PyType_Ready(&sr_py_rpm_package_type) < 0)
{
puts("PyType_Ready(&sr_py_rpm_package_type) < 0");
@@ -283,6 +290,9 @@ init_satyr()
PyModule_AddObject(module, "OperatingSystem",
(PyObject *)&sr_py_operating_system_type);
+ Py_INCREF(&sr_py_report_type);
+ PyModule_AddObject(module, "Report", (PyObject *)&sr_py_report_type);
+
Py_INCREF(&sr_py_rpm_package_type);
PyModule_AddObject(module, "RpmPackage",
(PyObject *)&sr_py_rpm_package_type);
diff --git a/python/py_report.c b/python/py_report.c
new file mode 100644
index 0000000..678d661
--- /dev/null
+++ b/python/py_report.c
@@ -0,0 +1,491 @@
+/*
+ py_report.c
+
+ Copyright (C) 2013 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_report.h"
+#include "py_rpm_package.h"
+#include "py_operating_system.h"
+
+#include "py_base_stacktrace.h"
+#include "py_core_stacktrace.h"
+#include "py_python_stacktrace.h"
+#include "py_koops_stacktrace.h"
+#include "py_java_stacktrace.h"
+
+#include "report.h"
+#include "operating_system.h"
+#include "strbuf.h"
+#include "rpm.h"
+
+#define report_doc "satyr.Report - report containing all data relevant to a software problem\n" \
+ "Usage:\n" \
+ "satyr.Report() - creates an empty report object\n" \
+ "satyr.Report(json_string) - creates a report from its JSON representation"
+
+#define to_json_doc "Usage: report.to_json()\n" \
+ "Returns: string - the report serialized as JSON"
+
+/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
+#define GSOFF_PY_STRUCT sr_py_report
+#define GSOFF_PY_MEMBER report
+#define GSOFF_C_STRUCT sr_report
+GSOFF_START
+GSOFF_MEMBER(reporter_name),
+GSOFF_MEMBER(reporter_version),
+GSOFF_MEMBER(user_root),
+GSOFF_MEMBER(user_local),
+GSOFF_MEMBER(component_name)
+GSOFF_END
+
+static PyGetSetDef
+report_getset[] =
+{
+ SR_ATTRIBUTE_STRING(reporter_name, "Name of the reporting software (string)" ),
+ SR_ATTRIBUTE_STRING(reporter_version, "Version of the reporting software (string)" ),
+ SR_ATTRIBUTE_BOOL(user_root, "Did the problem originate in a program running as root? (bool)" ),
+ SR_ATTRIBUTE_BOOL(user_local, "Did the problem originate in a program executed by local user? (bool)" ),
+ SR_ATTRIBUTE_STRING(component_name, "Name of the software component this report pertains to (string)" ),
+ { (char*)"report_version", sr_py_report_get_version, sr_py_setter_readonly, (char*)"Version of the report (int)", NULL },
+ { (char*)"report_type", sr_py_report_get_type, sr_py_report_set_type, (char*)"Report type (string)", NULL },
+ { NULL },
+};
+
+static PyMemberDef
+report_members[] =
+{
+ { (char*)"stacktrace", T_OBJECT_EX, offsetof(struct sr_py_report, stacktrace), 0, (char*)"Problem stacktrace" },
+ { (char*)"operating_system", T_OBJECT_EX, offsetof(struct sr_py_report, operating_system), 0, (char *)"Operating system" },
+ { (char*)"packages", T_OBJECT_EX, offsetof(struct sr_py_report, packages), 0, (char *)"List of packages" },
+ { NULL },
+};
+
+static PyMethodDef
+report_methods[] =
+{
+ { "to_json", sr_py_report_to_json, METH_NOARGS, to_json_doc },
+ { NULL },
+};
+
+PyTypeObject
+sr_py_report_type =
+{
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "satyr.Report", /* tp_name */
+ sizeof(struct sr_py_report), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ sr_py_report_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_report_str, /* tp_str */
+ NULL, /* tp_getattro */
+ NULL, /* tp_setattro */
+ NULL, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ report_doc, /* tp_doc */
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ report_methods, /* tp_methods */
+ report_members, /* tp_members */
+ report_getset, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ NULL, /* tp_init */
+ NULL, /* tp_alloc */
+ sr_py_report_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 */
+};
+
+static PyObject *
+rpms_to_python_list(struct sr_rpm_package *rpm)
+{
+ PyObject *result = PyList_New(0);
+ if (!result)
+ return PyErr_NoMemory();
+
+ struct sr_py_rpm_package *item;
+ while (rpm)
+ {
+ item = PyObject_New(struct sr_py_rpm_package, &sr_py_rpm_package_type);
+ if (!item)
+ return PyErr_NoMemory();
+
+ item->rpm_package = rpm;
+ if (PyList_Append(result, (PyObject *)item) < 0)
+ return NULL;
+
+ rpm = rpm->next;
+ }
+
+ return result;
+}
+
+int
+rpms_prepare_linked_list(struct sr_py_report *report)
+{
+ if (!PyList_Check(report->packages))
+ {
+ PyErr_SetString(PyExc_TypeError, "Attribute 'packages' is not a list.");
+ return -1;
+ }
+
+ int i;
+ PyObject *item;
+ struct sr_py_rpm_package *current = NULL, *prev = NULL;
+
+ for (i = 0; i < PyList_Size(report->packages); ++i)
+ {
+ item = PyList_GetItem(report->packages, i);
+ if (!item)
+ return -1;
+
+ Py_INCREF(item);
+
+ if (!PyObject_TypeCheck(item, &sr_py_rpm_package_type))
+ {
+ Py_XDECREF(item);
+ Py_XDECREF(prev);
+ PyErr_SetString(PyExc_TypeError, "packages must be a list of RpmPackage objects");
+ return -1;
+ }
+
+ current = (struct sr_py_rpm_package*)item;
+ if (i == 0)
+ report->report->rpm_packages = current->rpm_package;
+ else
+ prev->rpm_package->next = current->rpm_package;
+
+ Py_XDECREF(prev);
+ prev = current;
+ }
+
+ if (current)
+ {
+ current->rpm_package->next = NULL;
+ Py_DECREF(current);
+ }
+
+ return 0;
+}
+
+static int
+stacktrace_prepare(struct sr_py_report *report, PyTypeObject *type, bool multi_thread)
+{
+ if (!PyObject_TypeCheck(report->stacktrace, type))
+ {
+ PyErr_Format(PyExc_TypeError, "stacktrace must be an %s object", type->tp_name);
+ return -1;
+ }
+
+ if (multi_thread)
+ {
+ report->report->stacktrace =
+ ((struct sr_py_multi_stacktrace *)report->stacktrace)->stacktrace;
+ }
+ else
+ {
+ report->report->stacktrace =
+ (struct sr_stacktrace *)((struct sr_py_base_thread *)report->stacktrace)->thread;
+ }
+
+ return 0;
+}
+
+static int
+report_prepare_subobjects(struct sr_py_report *report)
+{
+ /* packages */
+ if (rpms_prepare_linked_list(report) < 0)
+ return -1;
+
+ /* operating_system */
+ if (report->operating_system == Py_None)
+ {
+ report->report->operating_system = NULL;
+ }
+ else
+ {
+ if (!PyObject_TypeCheck(report->operating_system, &sr_py_operating_system_type))
+ {
+ PyErr_SetString(PyExc_TypeError, "operating_system must be an OperatingSystem object");
+ return -1;
+ }
+
+ report->report->operating_system =
+ ((struct sr_py_operating_system *)report->operating_system)->operating_system;
+ }
+
+ /* stacktrace */
+ if (report->stacktrace == Py_None)
+ {
+ report->report->stacktrace = NULL;
+ }
+ else
+ {
+ switch (report->report->report_type)
+ {
+ case SR_REPORT_CORE:
+ if(stacktrace_prepare(report, &sr_py_core_stacktrace_type, true) < 0)
+ return -1;
+ break;
+ case SR_REPORT_JAVA:
+ if (stacktrace_prepare(report, &sr_py_java_stacktrace_type, true) < 0)
+ return -1;
+ break;
+ case SR_REPORT_PYTHON:
+ if (stacktrace_prepare(report, &sr_py_python_stacktrace_type, false) < 0)
+ return -1;
+ break;
+ case SR_REPORT_KERNELOOPS:
+ if (stacktrace_prepare(report, &sr_py_koops_stacktrace_type, false) < 0)
+ return -1;
+ break;
+ default:
+ report->report->stacktrace = NULL;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+PyObject *
+report_to_python_obj(struct sr_report *report)
+{
+ struct sr_py_report *ro =
+ PyObject_New(struct sr_py_report, &sr_py_report_type);
+ if (!ro)
+ return PyErr_NoMemory();
+
+ ro->report = report;
+
+ /* wrap operating system in a python object */
+ if (report->operating_system)
+ {
+ struct sr_py_operating_system *os =
+ PyObject_New(struct sr_py_operating_system, &sr_py_operating_system_type);
+ if (!os)
+ return NULL;
+
+ os->operating_system = report->operating_system;
+ ro->operating_system = (PyObject *)os;
+ }
+ else
+ {
+ Py_INCREF(Py_None);
+ ro->operating_system = Py_None;
+ }
+
+ /* packages */
+ ro->packages = rpms_to_python_list(report->rpm_packages);
+ if (ro->packages == NULL)
+ return NULL;
+
+ /* stacktrace */
+ /* XXX: this could be rewritten in a generic way */
+ if (report->stacktrace != NULL)
+ {
+ switch (report->report_type)
+ {
+ case SR_REPORT_CORE:
+ ro->stacktrace = core_stacktrace_to_python_obj(
+ (struct sr_core_stacktrace *)report->stacktrace);
+ break;
+ case SR_REPORT_JAVA:
+ ro->stacktrace = java_stacktrace_to_python_obj(
+ (struct sr_java_stacktrace *)report->stacktrace);
+ break;
+ case SR_REPORT_PYTHON:
+ ro->stacktrace = python_stacktrace_to_python_obj(
+ (struct sr_python_stacktrace *)report->stacktrace);
+ break;
+ case SR_REPORT_KERNELOOPS:
+ ro->stacktrace = koops_stacktrace_to_python_obj(
+ (struct sr_koops_stacktrace *)report->stacktrace);
+ break;
+ default:
+ Py_INCREF(Py_None);
+ ro->stacktrace = Py_None;
+ break;
+ }
+
+ if (ro->stacktrace == NULL)
+ return NULL;
+ }
+ else
+ {
+ Py_INCREF(Py_None);
+ ro->stacktrace = Py_None;
+ }
+
+ return (PyObject *)ro;
+}
+
+/* constructor */
+PyObject *
+sr_py_report_new(PyTypeObject *object, PyObject *args, PyObject *kwds)
+{
+ const char *report_json = NULL;
+ if (!PyArg_ParseTuple(args, "|s", &report_json))
+ return NULL;
+
+ struct sr_report *report;
+
+ if (report_json)
+ {
+ char *error_msg;
+ report = sr_report_from_json_text(report_json, &error_msg);
+ if (!report)
+ {
+ PyErr_SetString(PyExc_ValueError, error_msg);
+ free(error_msg);
+ return NULL;
+ }
+ }
+ else
+ {
+ report = sr_report_new();
+ report->operating_system = sr_operating_system_new();
+ }
+
+ return report_to_python_obj(report);
+}
+
+/* destructor */
+void
+sr_py_report_free(PyObject *object)
+{
+ struct sr_py_report *this = (struct sr_py_report *)object;
+
+ /* python will free the subordinate objects once their refcount reaches zero */
+ /* XXX: is this correct wrt the warning on
+ * http://docs.python.org/2.7/c-api/refcounting.html?highlight=py_decref#Py_DECREF ? */
+ Py_DECREF(this->packages);
+ Py_DECREF(this->operating_system);
+ Py_DECREF(this->stacktrace);
+
+ /* if there was no other reference, these structs are free'd by now */
+ this->report->rpm_packages = NULL;
+ this->report->operating_system = NULL;
+ this->report->stacktrace = NULL;
+
+ sr_report_free(this->report);
+ PyObject_Del(object);
+}
+
+/* str */
+PyObject *
+sr_py_report_str(PyObject *object)
+{
+ struct sr_py_report *this = (struct sr_py_report*)object;
+ struct sr_strbuf *buf = sr_strbuf_new();
+
+ char *type = sr_report_type_to_string(this->report->report_type);
+ sr_strbuf_append_strf(buf, "Report, type: %s", type);
+ free(type);
+
+ if (this->report->component_name)
+ sr_strbuf_append_strf(buf, ", component: %s", this->report->component_name);
+
+ char *str = sr_strbuf_free_nobuf(buf);
+ PyObject *result = Py_BuildValue("s", str);
+ free(str);
+ return result;
+}
+
+PyObject *
+sr_py_report_get_version(PyObject *self, void *data)
+{
+ return PyInt_FromLong(((struct sr_py_report*)self)->report->report_version);
+}
+
+PyObject *
+sr_py_report_get_type(PyObject *self, void *data)
+{
+ struct sr_py_report *report = (struct sr_py_report *)self;
+
+ char *type = sr_report_type_to_string(report->report->report_type);
+ PyObject *res = PyString_FromString(type);
+
+ free(type);
+ return res;
+}
+
+int sr_py_report_set_type(PyObject *self, PyObject *rhs, void *data)
+{
+ struct sr_py_report *report = (struct sr_py_report *)self;
+
+ if (rhs == NULL)
+ {
+ PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute.");
+ return -1;
+ }
+
+ char *type_str = PyString_AsString(rhs);
+ if (!type_str)
+ return -1;
+
+ enum sr_report_type type = sr_report_type_from_string(type_str);
+ if (type == SR_REPORT_INVALID)
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid report type.");
+ return -1;
+ }
+
+ report->report->report_type = type;
+ return 0;
+}
+
+PyObject *
+sr_py_report_to_json(PyObject *self, PyObject *args)
+{
+ struct sr_py_report *this = (struct sr_py_report *)self;
+ if (report_prepare_subobjects(this) < 0)
+ return NULL;
+
+ char *json = sr_report_to_json(this->report);
+ if (!json)
+ return NULL;
+
+ PyObject *result = PyString_FromString(json);
+ free(json);
+ return result;
+}
diff --git a/python/py_report.h b/python/py_report.h
new file mode 100644
index 0000000..a451dbc
--- /dev/null
+++ b/python/py_report.h
@@ -0,0 +1,79 @@
+/*
+ py_report.h
+
+ Copyright (C) 2013 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_REPORT_H
+#define SATYR_PY_REPORT_H
+
+/**
+ * @file
+ * @brief Python bindings for struct sr_report.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <Python.h>
+#include <structmember.h>
+
+PyTypeObject sr_py_report_type;
+
+struct sr_py_report
+{
+ PyObject_HEAD
+ struct sr_report *report;
+ PyObject *operating_system;
+ PyObject *packages;
+ PyObject *stacktrace;
+};
+
+/**
+ * Constructor.
+ */
+PyObject *sr_py_report_new(PyTypeObject *object,
+ PyObject *args, PyObject *kwds);
+
+/**
+ * Destructor.
+ */
+void sr_py_report_free(PyObject *object);
+
+/**
+ * str
+ */
+PyObject *sr_py_report_str(PyObject *self);
+
+/**
+ * getters & setters
+ */
+PyObject *sr_py_report_get_version(PyObject *self, void *data);
+PyObject *sr_py_report_get_type(PyObject *self, void *data);
+int sr_py_report_set_type(PyObject *self, PyObject *rhs, void *data);
+
+/**
+ * Methods.
+ */
+PyObject *
+sr_py_report_to_json(PyObject *self, PyObject *args);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tests/json_files/ureport-1 b/tests/json_files/ureport-1
new file mode 100644
index 0000000..e71072c
--- /dev/null
+++ b/tests/json_files/ureport-1
@@ -0,0 +1,107 @@
+{
+ "ureport_version": 2,
+
+ "reason": "Program /usr/bin/sleep was terminated by signal 11",
+
+ "os": {
+ "name": "fedora",
+ "version": "18",
+ "architecture": "x86_64"
+ },
+
+ "problem": {
+ "type": "core",
+
+ "executable": "/usr/bin/sleep",
+
+ "signal": 11,
+
+ "component": "coreutils",
+
+ "user": {
+ "local": true,
+ "root": false
+ },
+
+ "stacktrace": [
+ {
+ "crash_thread": true,
+
+ "frames": [
+ {
+ "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
+ "build_id_offset": 767024,
+ "file_name": "/lib64/libc.so.6",
+ "address": 251315074096,
+ "fingerprint": "6c1eb9626919a2a5f6a4fc4c2edc9b21b33b7354",
+ "function_name": "__nanosleep"
+ },
+ {
+ "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
+ "build_id_offset": 16567,
+ "file_name": "/usr/bin/sleep",
+ "address": 4210871,
+ "fingerprint": "d24433b82a2c751fc580f47154823e0bed641a54",
+ "function_name": "close_stdout"
+ },
+ {
+ "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
+ "build_id_offset": 16202,
+ "file_name": "/usr/bin/sleep",
+ "address": 4210506,
+ "fingerprint": "562719fb960d1c4dbf30c04b3cff37c82acc3d2d",
+ "function_name": "close_stdout"
+ },
+ {
+ "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
+ "build_id_offset": 6404,
+ "fingerprint": "2e8fb95adafe21d035b9bcb9993810fecf4be657",
+ "file_name": "/usr/bin/sleep",
+ "address": 4200708
+ },
+ {
+ "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
+ "build_id_offset": 137733,
+ "file_name": "/lib64/libc.so.6",
+ "address": 251314444805,
+ "fingerprint": "075acda5d3230e115cf7c88597eaba416bdaa6bb",
+ "function_name": "__libc_start_main"
+ }
+ ]
+ }
+ ]
+ },
+
+ "packages": [
+ {
+ "name": "coreutils",
+ "epoch": 0,
+ "version": "8.17",
+ "architecture": "x86_64",
+ "package_role": "affected",
+ "release": "8.fc18",
+ "install_time": 1371464601
+ },
+ {
+ "name": "glibc",
+ "epoch": 0,
+ "version": "2.16",
+ "architecture": "x86_64",
+ "release": "31.fc18",
+ "install_time": 1371464176
+ },
+ {
+ "name": "glibc-common",
+ "epoch": 0,
+ "version": "2.16",
+ "architecture": "x86_64",
+ "release": "31.fc18",
+ "install_time": 1371464184
+ }
+ ],
+
+ "reporter": {
+ "version": "0.3",
+ "name": "satyr"
+ }
+}
diff --git a/tests/python/report.py b/tests/python/report.py
index da00b08..9da2fa8 100755
--- a/tests/python/report.py
+++ b/tests/python/report.py
@@ -33,6 +33,55 @@ class TestRpmPackage(BindingsTestCase):
self.assertGetSetCorrect(self.rpm, 'install_time', 0, 1234565)
self.assertGetSetCorrect(self.rpm, 'role', satyr.ROLE_UNKNOWN, satyr.ROLE_AFFECTED)
+class TestReport(BindingsTestCase):
+ def setUp(self):
+ self.report_json = load_input_contents('../json_files/ureport-1')
+ self.report = satyr.Report(self.report_json)
+
+ def test_to_json(self):
+ '''
+ Check that loading a report and converting it to json again produces
+ the same output.
+ '''
+ import json
+
+ source = json.loads(self.report_json)
+ output = json.loads(self.report.to_json())
+ self.assertEqual(source, output)
+
+ def test_getset(self):
+ self.assertGetSetCorrect(self.report, 'reporter_name', 'satyr', 'abrt')
+ self.assertGetSetCorrect(self.report, 'reporter_version', '0.3', '0.2')
+ self.assertGetSetCorrect(self.report, 'user_root', False, True)
+ self.assertGetSetCorrect(self.report, 'user_local', True, False)
+ self.assertGetSetCorrect(self.report, 'component_name', 'coreutils', 'zsh')
+
+ self.assertGetSetCorrect(self.report, 'report_type', 'core', 'python')
+ self.assertRaises(ValueError, self.report.__setattr__, 'report_type', 'C#')
+
+ self.assertEqual(self.report.report_version, 2)
+ self.assertRaises(AttributeError, self.report.__setattr__, 'report_version', 42)
+
+ def test_attributes(self):
+ self.assertTrue(self.report.stacktrace != None)
+ self.assertTrue(isinstance(self.report.stacktrace, satyr.CoreStacktrace))
+ self.assertTrue(len(self.report.stacktrace.threads) == 1)
+ self.assertTrue(len(self.report.stacktrace.threads[0].frames) == 5)
+
+ self.assertTrue(self.report.operating_system != None)
+ self.assertTrue(isinstance(self.report.operating_system, satyr.OperatingSystem))
+ self.assertEqual(self.report.operating_system.name, 'fedora')
+ self.assertEqual(self.report.operating_system.version, '18')
+ self.assertEqual(self.report.operating_system.architecture, 'x86_64')
+ self.assertEqual(self.report.operating_system.cpe, None)
+
+ self.assertTrue(self.report.packages != None)
+ self.assertTrue(isinstance(self.report.packages, list))
+ self.assertTrue(isinstance(self.report.packages[0], satyr.RpmPackage))
+ self.assertTrue(len(self.report.packages) == 3)
+ self.assertEqual(self.report.packages[0].name, 'coreutils')
+ self.assertEqual(self.report.packages[0].role, satyr.ROLE_AFFECTED)
+
if __name__ == '__main__':
unittest.main()
--
1.8.3.1