[SATYR PATCH 8/8] python: bindings for struct sr_report

Martin Milata mmilata at redhat.com
Mon Aug 26 14:49:07 UTC 2013


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



More information about the Crash-catcher mailing list