Blame SOURCES/0004-Add-support-for-modular-errata-RhBug1656584.patch

6cec19
From a48db44b73785b5d5fbe8ae827522695fa0fd9ce Mon Sep 17 00:00:00 2001
6cec19
From: Aleš Matěj <amatej@redhat.com>
6cec19
Date: Tue, 8 Jan 2019 15:44:55 +0100
6cec19
Subject: [PATCH] Add support for modular errata (RhBug:1656584)
6cec19
6cec19
---
6cec19
 src/python/CMakeLists.txt                         |   1 +
6cec19
 src/python/__init__.py                            |   3 +++
6cec19
 src/python/createrepo_cmodule.c                   |   8 ++++++++
6cec19
 src/python/updatecollection-py.c                  |  43 +++++++++++++++++++++++++++++++++++++++++++
6cec19
 src/python/updatecollectionmodule-py.c            | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6cec19
 src/python/updatecollectionmodule-py.h            |  33 +++++++++++++++++++++++++++++++++
6cec19
 src/updateinfo.c                                  |  44 ++++++++++++++++++++++++++++++++++++++++++++
6cec19
 src/updateinfo.h                                  |  24 ++++++++++++++++++++++++
6cec19
 src/xml_dump_updateinfo.c                         |  20 ++++++++++++++++++++
6cec19
 src/xml_parser_internal.h                         |   2 ++
6cec19
 src/xml_parser_updateinfo.c                       |  54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
6cec19
 tests/fixtures.h                                  |   1 +
6cec19
 tests/python/tests/test_updatecollection.py       |  16 ++++++++++++++++
6cec19
 tests/python/tests/test_updatecollectionmodule.py |  31 +++++++++++++++++++++++++++++++
6cec19
 tests/python/tests/test_updateinfo.py             | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6cec19
 tests/test_xml_parser_updateinfo.c                |  86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6cec19
 tests/testdata/updateinfo_files/updateinfo_03.xml | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6cec19
 17 files changed, 957 insertions(+)
6cec19
 create mode 100644 src/python/updatecollectionmodule-py.c
6cec19
 create mode 100644 src/python/updatecollectionmodule-py.h
6cec19
 create mode 100644 tests/python/tests/test_updatecollectionmodule.py
6cec19
 create mode 100644 tests/testdata/updateinfo_files/updateinfo_03.xml
6cec19
6cec19
diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt
6cec19
index 9f1ac64..ebf4d4c 100644
6cec19
--- a/src/python/CMakeLists.txt
6cec19
+++ b/src/python/CMakeLists.txt
6cec19
@@ -50,6 +50,7 @@ SET (createrepo_cmodule_SRCS
6cec19
      sqlite-py.c
6cec19
      typeconversion.c
6cec19
      updatecollection-py.c
6cec19
+     updatecollectionmodule-py.c
6cec19
      updatecollectionpackage-py.c
6cec19
      updateinfo-py.c
6cec19
      updaterecord-py.c
6cec19
diff --git a/src/python/__init__.py b/src/python/__init__.py
6cec19
index 6c29e74..65d7f82 100644
6cec19
--- a/src/python/__init__.py
6cec19
+++ b/src/python/__init__.py
6cec19
@@ -206,6 +206,9 @@ class OtherSqlite(Sqlite):
6cec19
 
6cec19
 UpdateCollection = _createrepo_c.UpdateCollection
6cec19
 
6cec19
+# UpdateCollectionModule class
6cec19
+
6cec19
+UpdateCollectionModule = _createrepo_c.UpdateCollectionModule
6cec19
 
6cec19
 # UpdateCollectionPackage class
6cec19
 
6cec19
diff --git a/src/python/createrepo_cmodule.c b/src/python/createrepo_cmodule.c
6cec19
index fe4d2ad..9be5f46 100644
6cec19
--- a/src/python/createrepo_cmodule.c
6cec19
+++ b/src/python/createrepo_cmodule.c
6cec19
@@ -35,6 +35,7 @@
6cec19
 #include "repomdrecord-py.h"
6cec19
 #include "sqlite-py.h"
6cec19
 #include "updatecollection-py.h"
6cec19
+#include "updatecollectionmodule-py.h"
6cec19
 #include "updatecollectionpackage-py.h"
6cec19
 #include "updateinfo-py.h"
6cec19
 #include "updaterecord-py.h"
6cec19
@@ -185,6 +186,13 @@ init_createrepo_c(void)
6cec19
     PyModule_AddObject(m, "UpdateCollection",
6cec19
                        (PyObject *)&UpdateCollection_Type);
6cec19
 
6cec19
+    /* _createrepo_c.UpdateCollectionModule */
6cec19
+    if (PyType_Ready(&UpdateCollectionModule_Type) < 0)
6cec19
+        return FAILURE;
6cec19
+    Py_INCREF(&UpdateCollectionModule_Type);
6cec19
+    PyModule_AddObject(m, "UpdateCollectionModule",
6cec19
+                       (PyObject *)&UpdateCollectionModule_Type);
6cec19
+
6cec19
     /* _createrepo_c.UpdateCollectionPackage */
6cec19
     if (PyType_Ready(&UpdateCollectionPackage_Type) < 0)
6cec19
         return FAILURE;
6cec19
diff --git a/src/python/updatecollection-py.c b/src/python/updatecollection-py.c
6cec19
index 3a791be..ca97657 100644
6cec19
--- a/src/python/updatecollection-py.c
6cec19
+++ b/src/python/updatecollection-py.c
6cec19
@@ -22,6 +22,7 @@
6cec19
 #include <stddef.h>
6cec19
 
6cec19
 #include "updatecollection-py.h"
6cec19
+#include "updatecollectionmodule-py.h"
6cec19
 #include "updatecollectionpackage-py.h"
6cec19
 #include "exception-py.h"
6cec19
 #include "typeconversion.h"
6cec19
@@ -188,6 +189,13 @@ typedef int (*ConversionToCheckFunc)(PyObject *);
6cec19
 typedef void *(*ConversionToFunc)(PyObject *, GStringChunk *);
6cec19
 
6cec19
 PyObject *
6cec19
+PyObject_FromUpdateCollectionModule(cr_UpdateCollectionModule *module)
6cec19
+{
6cec19
+    return Object_FromUpdateCollectionModule(
6cec19
+                        cr_updatecollectionmodule_copy(module));
6cec19
+}
6cec19
+
6cec19
+PyObject *
6cec19
 PyObject_FromUpdateCollectionPackage(cr_UpdateCollectionPackage *pkg)
6cec19
 {
6cec19
     return Object_FromUpdateCollectionPackage(
6cec19
@@ -249,6 +257,23 @@ get_list(_UpdateCollectionObject *self, void *conv)
6cec19
     return list;
6cec19
 }
6cec19
 
6cec19
+static PyObject *
6cec19
+get_module(_UpdateCollectionObject *self, void *member_offset)
6cec19
+{
6cec19
+    if (check_UpdateCollectionStatus(self))
6cec19
+        return NULL;
6cec19
+
6cec19
+    cr_UpdateCollection *collection = self->collection;
6cec19
+
6cec19
+    cr_UpdateCollectionModule *module = *((cr_UpdateCollectionModule **) ((size_t) collection + (size_t) member_offset));
6cec19
+    if (module == NULL)
6cec19
+        Py_RETURN_NONE;
6cec19
+
6cec19
+    PyObject *py_module = PyObject_FromUpdateCollectionModule(module);
6cec19
+
6cec19
+    return py_module;
6cec19
+}
6cec19
+
6cec19
 static int
6cec19
 set_str(_UpdateCollectionObject *self, PyObject *value, void *member_offset)
6cec19
 {
6cec19
@@ -265,11 +290,29 @@ set_str(_UpdateCollectionObject *self, PyObject *value, void *member_offset)
6cec19
     return 0;
6cec19
 }
6cec19
 
6cec19
+static int
6cec19
+set_module(_UpdateCollectionObject *self, PyObject *value, void *member_offset)
6cec19
+{
6cec19
+    if (check_UpdateCollectionStatus(self))
6cec19
+        return -1;
6cec19
+    if (!UpdateCollectionModuleObject_Check(value) && value != Py_None) {
6cec19
+        PyErr_SetString(PyExc_TypeError, "Module or None expected!");
6cec19
+        return -1;
6cec19
+    }
6cec19
+    cr_UpdateCollectionModule *module = UpdateCollectionModule_FromPyObject(value);
6cec19
+    cr_UpdateCollection *collection = self->collection;
6cec19
+    *((cr_UpdateCollectionModule **) ((size_t) collection + (size_t) member_offset)) = module;
6cec19
+
6cec19
+    return 0;
6cec19
+}
6cec19
+
6cec19
 static PyGetSetDef updatecollection_getsetters[] = {
6cec19
     {"shortname",     (getter)get_str, (setter)set_str,
6cec19
         "Short name", OFFSET(shortname)},
6cec19
     {"name",          (getter)get_str, (setter)set_str,
6cec19
         "Name of the collection", OFFSET(name)},
6cec19
+    {"module",        (getter)get_module, (setter)set_module,
6cec19
+        "Module information", OFFSET(module)},
6cec19
     {"packages",       (getter)get_list, (setter)NULL,
6cec19
         "List of packages", &(list_convertors[0])},
6cec19
     {NULL, NULL, NULL, NULL, NULL} /* sentinel */
6cec19
diff --git a/src/python/updatecollectionmodule-py.c b/src/python/updatecollectionmodule-py.c
6cec19
new file mode 100644
6cec19
index 0000000..20b2d99
6cec19
--- /dev/null
6cec19
+++ b/src/python/updatecollectionmodule-py.c
6cec19
@@ -0,0 +1,274 @@
6cec19
+/* createrepo_c - Library of routines for manipulation with repodata
6cec19
+ * Copyright (C) 2013  Tomas Mlcoch
6cec19
+ *
6cec19
+ * This program is free software; you can redistribute it and/or
6cec19
+ * modify it under the terms of the GNU General Public License
6cec19
+ * as published by the Free Software Foundation; either version 2
6cec19
+ * of the License, or (at your option) any later version.
6cec19
+ *
6cec19
+ * This program is distributed in the hope that it will be useful,
6cec19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
6cec19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6cec19
+ * GNU General Public License for more details.
6cec19
+ *
6cec19
+ * You should have received a copy of the GNU General Public License
6cec19
+ * along with this program; if not, write to the Free Software
6cec19
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
6cec19
+ * USA.
6cec19
+ */
6cec19
+
6cec19
+#include <Python.h>
6cec19
+#include <assert.h>
6cec19
+#include <stddef.h>
6cec19
+
6cec19
+#include "updatecollectionmodule-py.h"
6cec19
+#include "exception-py.h"
6cec19
+#include "typeconversion.h"
6cec19
+#include "contentstat-py.h"
6cec19
+
6cec19
+typedef struct {
6cec19
+    PyObject_HEAD
6cec19
+    cr_UpdateCollectionModule *module;
6cec19
+} _UpdateCollectionModuleObject;
6cec19
+
6cec19
+PyObject *
6cec19
+Object_FromUpdateCollectionModule(cr_UpdateCollectionModule *mod)
6cec19
+{
6cec19
+    PyObject *py_rec;
6cec19
+
6cec19
+    if (!mod) {
6cec19
+        PyErr_SetString(PyExc_ValueError, "Expected a cr_UpdateCollectionModule pointer not NULL.");
6cec19
+        return NULL;
6cec19
+    }
6cec19
+
6cec19
+    py_rec = PyObject_CallObject((PyObject *) &UpdateCollectionModule_Type, NULL);
6cec19
+    cr_updatecollectionmodule_free(((_UpdateCollectionModuleObject *)py_rec)->module);
6cec19
+    ((_UpdateCollectionModuleObject *)py_rec)->module = mod;
6cec19
+
6cec19
+    return py_rec;
6cec19
+}
6cec19
+
6cec19
+cr_UpdateCollectionModule *
6cec19
+UpdateCollectionModule_FromPyObject(PyObject *o)
6cec19
+{
6cec19
+    if (!UpdateCollectionModuleObject_Check(o)) {
6cec19
+        PyErr_SetString(PyExc_TypeError, "Expected a UpdateCollectionModule object.");
6cec19
+        return NULL;
6cec19
+    }
6cec19
+    return ((_UpdateCollectionModuleObject *)o)->module;
6cec19
+}
6cec19
+
6cec19
+static int
6cec19
+check_UpdateCollectionModuleStatus(const _UpdateCollectionModuleObject *self)
6cec19
+{
6cec19
+    assert(self != NULL);
6cec19
+    assert(UpdateCollectionModuleObject_Check(self));
6cec19
+    if (self->module == NULL) {
6cec19
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c UpdateCollectionModule object.");
6cec19
+        return -1;
6cec19
+    }
6cec19
+    return 0;
6cec19
+}
6cec19
+
6cec19
+/* Function on the type */
6cec19
+
6cec19
+static PyObject *
6cec19
+updatecollectionmodule_new(PyTypeObject *type,
6cec19
+                    G_GNUC_UNUSED PyObject *args,
6cec19
+                    G_GNUC_UNUSED PyObject *kwds)
6cec19
+{
6cec19
+    _UpdateCollectionModuleObject *self = (_UpdateCollectionModuleObject *)type->tp_alloc(type, 0);
6cec19
+    if (self) {
6cec19
+        self->module = NULL;
6cec19
+    }
6cec19
+    return (PyObject *)self;
6cec19
+}
6cec19
+
6cec19
+PyDoc_STRVAR(updatecollectionmodule_init__doc__,
6cec19
+".. method:: __init__()\n\n");
6cec19
+
6cec19
+static int
6cec19
+updatecollectionmodule_init(_UpdateCollectionModuleObject *self,
6cec19
+                     G_GNUC_UNUSED PyObject *args,
6cec19
+                     G_GNUC_UNUSED PyObject *kwds)
6cec19
+{
6cec19
+    /* Free all previous resources when reinitialization */
6cec19
+    if (self->module)
6cec19
+        cr_updatecollectionmodule_free(self->module);
6cec19
+
6cec19
+    /* Init */
6cec19
+    self->module = cr_updatecollectionmodule_new();
6cec19
+    if (self->module == NULL) {
6cec19
+        PyErr_SetString(CrErr_Exception, "UpdateCollectionModule initialization failed");
6cec19
+        return -1;
6cec19
+    }
6cec19
+
6cec19
+    return 0;
6cec19
+}
6cec19
+
6cec19
+static void
6cec19
+updatecollectionmodule_dealloc(_UpdateCollectionModuleObject *self)
6cec19
+{
6cec19
+    if (self->module)
6cec19
+        cr_updatecollectionmodule_free(self->module);
6cec19
+    Py_TYPE(self)->tp_free(self);
6cec19
+}
6cec19
+
6cec19
+static PyObject *
6cec19
+updatecollectionmodule_repr(G_GNUC_UNUSED _UpdateCollectionModuleObject *self)
6cec19
+{
6cec19
+    return PyUnicode_FromFormat("<createrepo_c.UpdateCollectionModule object>");
6cec19
+}
6cec19
+
6cec19
+/* UpdateCollectionModule methods */
6cec19
+
6cec19
+PyDoc_STRVAR(copy__doc__,
6cec19
+"copy() -> UpdateCollectionModule\n\n"
6cec19
+"Return copy of the UpdateCollectionModule object");
6cec19
+
6cec19
+static PyObject *
6cec19
+copy_updatecollectionmodule(_UpdateCollectionModuleObject *self,
6cec19
+                             G_GNUC_UNUSED void *nothing)
6cec19
+{
6cec19
+    if (check_UpdateCollectionModuleStatus(self))
6cec19
+        return NULL;
6cec19
+    return Object_FromUpdateCollectionModule(cr_updatecollectionmodule_copy(self->module));
6cec19
+}
6cec19
+
6cec19
+static struct PyMethodDef updatecollectionmodule_methods[] = {
6cec19
+    {"copy", (PyCFunction)copy_updatecollectionmodule, METH_NOARGS,
6cec19
+        copy__doc__},
6cec19
+    {NULL} /* sentinel */
6cec19
+};
6cec19
+
6cec19
+/* getsetters */
6cec19
+
6cec19
+#define OFFSET(member) (void *) offsetof(cr_UpdateCollectionModule, member)
6cec19
+
6cec19
+static PyObject *
6cec19
+get_str(_UpdateCollectionModuleObject *self, void *member_offset)
6cec19
+{
6cec19
+    if (check_UpdateCollectionModuleStatus(self))
6cec19
+        return NULL;
6cec19
+    cr_UpdateCollectionModule *module = self->module;
6cec19
+    char *str = *((char **) ((size_t) module + (size_t) member_offset));
6cec19
+    if (str == NULL)
6cec19
+        Py_RETURN_NONE;
6cec19
+    return PyUnicode_FromString(str);
6cec19
+}
6cec19
+
6cec19
+static PyObject *
6cec19
+get_uint(_UpdateCollectionModuleObject *self, void *member_offset)
6cec19
+{
6cec19
+    if (check_UpdateCollectionModuleStatus(self))
6cec19
+        return NULL;
6cec19
+    cr_UpdateCollectionModule *module = self->module;
6cec19
+    guint64 val = *((guint64 *) ((size_t) module + (size_t) member_offset));
6cec19
+    return PyLong_FromUnsignedLongLong((guint64) val);
6cec19
+}
6cec19
+
6cec19
+static int
6cec19
+set_str(_UpdateCollectionModuleObject *self, PyObject *value, void *member_offset)
6cec19
+{
6cec19
+    if (check_UpdateCollectionModuleStatus(self))
6cec19
+        return -1;
6cec19
+    if (!PyUnicode_Check(value) && !PyBytes_Check(value) && value != Py_None) {
6cec19
+        PyErr_SetString(PyExc_TypeError, "Unicode, bytes, or None expected!");
6cec19
+        return -1;
6cec19
+    }
6cec19
+
6cec19
+    if (PyUnicode_Check(value)) {
6cec19
+        value = PyUnicode_AsUTF8String(value);
6cec19
+    }
6cec19
+
6cec19
+    cr_UpdateCollectionModule *module = self->module;
6cec19
+    char *str = cr_safe_string_chunk_insert(module->chunk,
6cec19
+                                            PyObject_ToStrOrNull(value));
6cec19
+
6cec19
+    *((char **) ((size_t) module + (size_t) member_offset)) = str;
6cec19
+    return 0;
6cec19
+}
6cec19
+
6cec19
+static int
6cec19
+set_uint(_UpdateCollectionModuleObject *self, PyObject *value, void *member_offset)
6cec19
+{
6cec19
+    if (check_UpdateCollectionModuleStatus(self))
6cec19
+        return -1;
6cec19
+    guint64 val;
6cec19
+
6cec19
+    if (PyLong_Check(value)) {
6cec19
+        val = PyLong_AsUnsignedLongLong(value);
6cec19
+    } else if (PyFloat_Check(value)) {
6cec19
+        val = (guint64) PyFloat_AS_DOUBLE(value);
6cec19
+#if PY_MAJOR_VERSION < 3
6cec19
+    } else if (PyInt_Check(value)) {
6cec19
+        val = PyInt_AS_LONG(value);
6cec19
+#endif
6cec19
+    } else {
6cec19
+        PyErr_SetString(PyExc_TypeError, "Number expected!");
6cec19
+        return -1;
6cec19
+    }
6cec19
+
6cec19
+    cr_UpdateCollectionModule *module = self->module;
6cec19
+    *((guint64 *) ((size_t) module + (size_t) member_offset)) = (guint64) val;
6cec19
+    return 0;
6cec19
+}
6cec19
+
6cec19
+static PyGetSetDef updatecollectionmodule_getsetters[] = {
6cec19
+    {"name",               (getter)get_str, (setter)set_str,
6cec19
+        "Name",            OFFSET(name)},
6cec19
+    {"stream",             (getter)get_str, (setter)set_str,
6cec19
+        "Stream",          OFFSET(stream)},
6cec19
+    {"version",            (getter)get_uint, (setter)set_uint,
6cec19
+        "Version",         OFFSET(version)},
6cec19
+    {"context",            (getter)get_str, (setter)set_str,
6cec19
+        "Context",         OFFSET(context)},
6cec19
+    {"arch",               (getter)get_str, (setter)set_str,
6cec19
+        "Arch",            OFFSET(arch)},
6cec19
+    {NULL, NULL, NULL, NULL, NULL} /* sentinel */
6cec19
+};
6cec19
+
6cec19
+/* Object */
6cec19
+
6cec19
+PyTypeObject UpdateCollectionModule_Type = {
6cec19
+    PyVarObject_HEAD_INIT(NULL, 0)
6cec19
+    "createrepo_c.UpdateCollectionModule", /* tp_name */
6cec19
+    sizeof(_UpdateCollectionModuleObject), /* tp_basicsize */
6cec19
+    0,                              /* tp_itemsize */
6cec19
+    (destructor) updatecollectionmodule_dealloc, /* tp_dealloc */
6cec19
+    0,                              /* tp_print */
6cec19
+    0,                              /* tp_getattr */
6cec19
+    0,                              /* tp_setattr */
6cec19
+    0,                              /* tp_compare */
6cec19
+    (reprfunc) updatecollectionmodule_repr,/* tp_repr */
6cec19
+    0,                              /* tp_as_number */
6cec19
+    0,                              /* tp_as_sequence */
6cec19
+    0,                              /* tp_as_mapping */
6cec19
+    0,                              /* tp_hash */
6cec19
+    0,                              /* tp_call */
6cec19
+    0,                              /* tp_str */
6cec19
+    0,                              /* tp_getattro */
6cec19
+    0,                              /* tp_setattro */
6cec19
+    0,                              /* tp_as_buffer */
6cec19
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
6cec19
+    updatecollectionmodule_init__doc__,    /* tp_doc */
6cec19
+    0,                              /* tp_traverse */
6cec19
+    0,                              /* tp_clear */
6cec19
+    0,                              /* tp_richcompare */
6cec19
+    0,                              /* tp_weaklistoffset */
6cec19
+    PyObject_SelfIter,              /* tp_iter */
6cec19
+    0,                              /* tp_iternext */
6cec19
+    updatecollectionmodule_methods,        /* tp_methods */
6cec19
+    0,                              /* tp_members */
6cec19
+    updatecollectionmodule_getsetters,     /* tp_getset */
6cec19
+    0,                              /* tp_base */
6cec19
+    0,                              /* tp_dict */
6cec19
+    0,                              /* tp_descr_get */
6cec19
+    0,                              /* tp_descr_set */
6cec19
+    0,                              /* tp_dictoffset */
6cec19
+    (initproc) updatecollectionmodule_init,/* tp_init */
6cec19
+    0,                              /* tp_alloc */
6cec19
+    updatecollectionmodule_new,            /* tp_new */
6cec19
+    0,                              /* tp_free */
6cec19
+    0,                              /* tp_is_gc */
6cec19
+};
6cec19
diff --git a/src/python/updatecollectionmodule-py.h b/src/python/updatecollectionmodule-py.h
6cec19
new file mode 100644
6cec19
index 0000000..5847259
6cec19
--- /dev/null
6cec19
+++ b/src/python/updatecollectionmodule-py.h
6cec19
@@ -0,0 +1,33 @@
6cec19
+/* createrepo_c - Library of routines for manipulation with repodata
6cec19
+ * Copyright (C) 2013  Tomas Mlcoch
6cec19
+ *
6cec19
+ * This program is free software; you can redistribute it and/or
6cec19
+ * modify it under the terms of the GNU General Public License
6cec19
+ * as published by the Free Software Foundation; either version 2
6cec19
+ * of the License, or (at your option) any later version.
6cec19
+ *
6cec19
+ * This program is distributed in the hope that it will be useful,
6cec19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
6cec19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6cec19
+ * GNU General Public License for more details.
6cec19
+ *
6cec19
+ * You should have received a copy of the GNU General Public License
6cec19
+ * along with this program; if not, write to the Free Software
6cec19
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
6cec19
+ * USA.
6cec19
+ */
6cec19
+
6cec19
+#ifndef CR_UPDATECOLLECTIONMODULE_PY_H
6cec19
+#define CR_UPDATECOLLECTIONMODULE_PY_H
6cec19
+
6cec19
+#include "src/createrepo_c.h"
6cec19
+
6cec19
+extern PyTypeObject UpdateCollectionModule_Type;
6cec19
+
6cec19
+#define UpdateCollectionModuleObject_Check(o) \
6cec19
+            PyObject_TypeCheck(o, &UpdateCollectionModule_Type)
6cec19
+
6cec19
+PyObject *Object_FromUpdateCollectionModule(cr_UpdateCollectionModule *rec);
6cec19
+cr_UpdateCollectionModule *UpdateCollectionModule_FromPyObject(PyObject *o);
6cec19
+
6cec19
+#endif
6cec19
diff --git a/src/updateinfo.c b/src/updateinfo.c
6cec19
index 6e45229..cdc4747 100644
6cec19
--- a/src/updateinfo.c
6cec19
+++ b/src/updateinfo.c
6cec19
@@ -74,6 +74,46 @@ cr_updatecollectionpackage_free(cr_UpdateCollectionPackage *pkg)
6cec19
 
6cec19
 
6cec19
 /*
6cec19
+ * cr_UpdateCollectionModule
6cec19
+ */
6cec19
+
6cec19
+cr_UpdateCollectionModule *
6cec19
+cr_updatecollectionmodule_new(void)
6cec19
+{
6cec19
+    cr_UpdateCollectionModule *module = g_malloc0(sizeof(*module));
6cec19
+    module->chunk = g_string_chunk_new(0);
6cec19
+    return module;
6cec19
+}
6cec19
+
6cec19
+cr_UpdateCollectionModule *
6cec19
+cr_updatecollectionmodule_copy(const cr_UpdateCollectionModule *orig)
6cec19
+{
6cec19
+    cr_UpdateCollectionModule *module;
6cec19
+
6cec19
+    if (!orig) return NULL;
6cec19
+
6cec19
+    module = cr_updatecollectionmodule_new();
6cec19
+
6cec19
+    module->name    = cr_safe_string_chunk_insert(module->chunk, orig->name);
6cec19
+    module->stream  = cr_safe_string_chunk_insert(module->chunk, orig->stream);
6cec19
+    module->version = orig->version;
6cec19
+    module->context = cr_safe_string_chunk_insert(module->chunk, orig->context);
6cec19
+    module->arch    = cr_safe_string_chunk_insert(module->chunk, orig->arch);
6cec19
+
6cec19
+    return module;
6cec19
+}
6cec19
+
6cec19
+void
6cec19
+cr_updatecollectionmodule_free(cr_UpdateCollectionModule *module)
6cec19
+{
6cec19
+    if (!module)
6cec19
+        return;
6cec19
+    g_string_chunk_free(module->chunk);
6cec19
+    g_free(module);
6cec19
+}
6cec19
+
6cec19
+
6cec19
+/*
6cec19
  * cr_UpdateCollection
6cec19
  */
6cec19
 
6cec19
@@ -97,6 +137,10 @@ cr_updatecollection_copy(const cr_UpdateCollection *orig)
6cec19
     col->shortname = cr_safe_string_chunk_insert(col->chunk, orig->shortname);
6cec19
     col->name      = cr_safe_string_chunk_insert(col->chunk, orig->name);
6cec19
 
6cec19
+    if (orig->module) {
6cec19
+      col->module = cr_updatecollectionmodule_copy(orig->module);
6cec19
+    }
6cec19
+
6cec19
     if (orig->packages) {
6cec19
         GSList *newlist = NULL;
6cec19
         for (GSList *elem = orig->packages; elem; elem = g_slist_next(elem)) {
6cec19
diff --git a/src/updateinfo.h b/src/updateinfo.h
6cec19
index dbf7807..38883e0 100644
6cec19
--- a/src/updateinfo.h
6cec19
+++ b/src/updateinfo.h
6cec19
@@ -51,8 +51,19 @@ typedef struct {
6cec19
 } cr_UpdateCollectionPackage;
6cec19
 
6cec19
 typedef struct {
6cec19
+    gchar *name;
6cec19
+    gchar *stream;
6cec19
+    guint64 version;
6cec19
+    gchar *context;
6cec19
+    gchar *arch;
6cec19
+
6cec19
+    GStringChunk *chunk;
6cec19
+} cr_UpdateCollectionModule;
6cec19
+
6cec19
+typedef struct {
6cec19
     gchar *shortname;   /*!< e.g. rhn-tools-rhel-x86_64-server-6.5.aus */
6cec19
     gchar *name;        /*!< e.g. RHN Tools for RHEL AUS (v. 6.5 for 64-bit x86_64) */
6cec19
+    cr_UpdateCollectionModule *module;
6cec19
     GSList *packages;   /*!< List of cr_UpdateCollectionPackage */
6cec19
     GStringChunk *chunk;
6cec19
 } cr_UpdateCollection;
6cec19
@@ -106,6 +117,19 @@ void
6cec19
 cr_updatecollectionpackage_free(cr_UpdateCollectionPackage *pkg);
6cec19
 
6cec19
 /*
6cec19
+ * cr_UpdateCollectionModule
6cec19
+ */
6cec19
+
6cec19
+cr_UpdateCollectionModule *
6cec19
+cr_updatecollectionmodule_new(void);
6cec19
+
6cec19
+cr_UpdateCollectionModule *
6cec19
+cr_updatecollectionmodule_copy(const cr_UpdateCollectionModule *orig);
6cec19
+
6cec19
+void
6cec19
+cr_updatecollectionmodule_free(cr_UpdateCollectionModule *pkg);
6cec19
+
6cec19
+/*
6cec19
  * cr_UpdateCollection
6cec19
  */
6cec19
 
6cec19
diff --git a/src/xml_dump_updateinfo.c b/src/xml_dump_updateinfo.c
6cec19
index 4fb5720..fafe686 100644
6cec19
--- a/src/xml_dump_updateinfo.c
6cec19
+++ b/src/xml_dump_updateinfo.c
6cec19
@@ -66,6 +66,24 @@ cr_xml_dump_updatecollectionpackages(xmlNodePtr collection, GSList *packages)
6cec19
 }
6cec19
 
6cec19
 void
6cec19
+cr_xml_dump_updatecollectionmodule(xmlNodePtr collection, cr_UpdateCollectionModule *module)
6cec19
+{
6cec19
+    if (!module)
6cec19
+        return;
6cec19
+
6cec19
+    xmlNodePtr xml_module;
6cec19
+    xml_module = xmlNewChild(collection, NULL, BAD_CAST "module", NULL);
6cec19
+
6cec19
+    cr_xmlNewProp_c(xml_module, BAD_CAST "name", BAD_CAST module->name);
6cec19
+    cr_xmlNewProp_c(xml_module, BAD_CAST "stream", BAD_CAST module->stream);
6cec19
+    gchar buf[21]; //20 + '\0' is max number of chars of guint64: G_MAXUINT64 (= 18,446,744,073,709,551,615)
6cec19
+    snprintf(buf, 21, "%" G_GUINT64_FORMAT, module->version);
6cec19
+    cr_xmlNewProp_c(xml_module, BAD_CAST "version", BAD_CAST buf);
6cec19
+    cr_xmlNewProp_c(xml_module, BAD_CAST "context", BAD_CAST module->context);
6cec19
+    cr_xmlNewProp_c(xml_module, BAD_CAST "arch", BAD_CAST module->arch);
6cec19
+}
6cec19
+
6cec19
+void
6cec19
 cr_xml_dump_updateinforecord_pkglist(xmlNodePtr update, GSList *collections)
6cec19
 {
6cec19
     xmlNodePtr pkglist;
6cec19
@@ -83,6 +101,8 @@ cr_xml_dump_updateinforecord_pkglist(xmlNodePtr update, GSList *collections)
6cec19
                              BAD_CAST "name",
6cec19
                              BAD_CAST col->name);
6cec19
 
6cec19
+        cr_xml_dump_updatecollectionmodule(collection, col->module);
6cec19
+
6cec19
         cr_xml_dump_updatecollectionpackages(collection, col->packages);
6cec19
     }
6cec19
 }
6cec19
diff --git a/src/xml_parser_internal.h b/src/xml_parser_internal.h
6cec19
index 6b400eb..e079ece 100644
6cec19
--- a/src/xml_parser_internal.h
6cec19
+++ b/src/xml_parser_internal.h
6cec19
@@ -151,6 +151,8 @@ typedef struct _cr_ParserData {
6cec19
         Update record object */
6cec19
     cr_UpdateCollection *updatecollection; /*!<
6cec19
         Update collection object */
6cec19
+    cr_UpdateCollectionModule *updatecollectionmodule; /*!<
6cec19
+        Update collection module object */
6cec19
     cr_UpdateCollectionPackage *updatecollectionpackage; /*!<
6cec19
         Update collection package object */
6cec19
 
6cec19
diff --git a/src/xml_parser_updateinfo.c b/src/xml_parser_updateinfo.c
6cec19
index 18e5277..c6c6503 100644
6cec19
--- a/src/xml_parser_updateinfo.c
6cec19
+++ b/src/xml_parser_updateinfo.c
6cec19
@@ -54,6 +54,7 @@ typedef enum {
6cec19
     STATE_PKGLIST,          // <pkglist> ----------------------------
6cec19
     STATE_COLLECTION,
6cec19
     STATE_NAME,
6cec19
+    STATE_MODULE,
6cec19
     STATE_PACKAGE,
6cec19
     STATE_FILENAME,
6cec19
     STATE_SUM,
6cec19
@@ -89,6 +90,7 @@ static cr_StatesSwitch stateswitches[] = {
6cec19
     { STATE_PKGLIST,    "collection",        STATE_COLLECTION,        0 },
6cec19
     { STATE_COLLECTION, "package",           STATE_PACKAGE,           0 },
6cec19
     { STATE_COLLECTION, "name",              STATE_NAME,              1 },
6cec19
+    { STATE_COLLECTION, "module",            STATE_MODULE,            0 },
6cec19
     { STATE_PACKAGE,    "filename",          STATE_FILENAME,          1 },
6cec19
     { STATE_PACKAGE,    "sum",               STATE_SUM,               1 },
6cec19
     { STATE_PACKAGE,    "reboot_suggested",  STATE_REBOOTSUGGESTED,   0 },
6cec19
@@ -141,6 +143,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
     // Shortcuts
6cec19
     cr_UpdateRecord *rec = pd->updaterecord;
6cec19
     cr_UpdateCollection *collection = pd->updatecollection;
6cec19
+    cr_UpdateCollectionModule *module = pd->updatecollectionmodule;
6cec19
     cr_UpdateCollectionPackage *package = pd->updatecollectionpackage;
6cec19
 
6cec19
     switch(pd->state) {
6cec19
@@ -173,6 +176,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(!pd->updaterecord);
6cec19
         assert(!pd->updatecollection);
6cec19
+        assert(!pd->updatecollectionmodule);
6cec19
         assert(!pd->updatecollectionpackage);
6cec19
 
6cec19
         rec = cr_updaterecord_new();
6cec19
@@ -201,6 +205,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
         assert(!pd->updatecollection);
6cec19
+        assert(!pd->updatecollectionmodule);
6cec19
         assert(!pd->updatecollectionpackage);
6cec19
         val = cr_find_attr("date", attr);
6cec19
         if (val)
6cec19
@@ -211,6 +216,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
         assert(!pd->updatecollection);
6cec19
+        assert(!pd->updatecollectionmodule);
6cec19
         assert(!pd->updatecollectionpackage);
6cec19
         val = cr_find_attr("date", attr);
6cec19
         if (val)
6cec19
@@ -223,6 +229,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
         assert(!pd->updatecollection);
6cec19
+        assert(!pd->updatecollectionmodule);
6cec19
         assert(!pd->updatecollectionpackage);
6cec19
 
6cec19
         ref = cr_updatereference_new();
6cec19
@@ -251,6 +258,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
         assert(!pd->updatecollection);
6cec19
+        assert(!pd->updatecollectionmodule);
6cec19
         assert(!pd->updatecollectionpackage);
6cec19
 
6cec19
         collection = cr_updatecollection_new();
6cec19
@@ -263,6 +271,49 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
 
6cec19
         break;
6cec19
 
6cec19
+    case STATE_MODULE:
6cec19
+        assert(pd->updateinfo);
6cec19
+        assert(pd->updaterecord);
6cec19
+        assert(pd->updatecollection);
6cec19
+        assert(!pd->updatecollectionmodule);
6cec19
+        assert(!pd->updatecollectionpackage);
6cec19
+
6cec19
+        module = cr_updatecollectionmodule_new();
6cec19
+        if (module)
6cec19
+            collection->module = module;
6cec19
+
6cec19
+        val = cr_find_attr("name", attr);
6cec19
+        if (val)
6cec19
+            module->name = g_string_chunk_insert(module->chunk, val);
6cec19
+
6cec19
+        val = cr_find_attr("stream", attr);
6cec19
+        if (val)
6cec19
+            module->stream = g_string_chunk_insert(module->chunk, val);
6cec19
+
6cec19
+        val = cr_find_attr("version", attr);
6cec19
+        if (val){
6cec19
+            gchar *endptr;
6cec19
+            errno = 0;
6cec19
+            module->version = strtoull(val, &endptr, 10);
6cec19
+            if ((errno == ERANGE && (module->version == ULLONG_MAX))
6cec19
+                 || (errno != 0 && module->version == 0)) {
6cec19
+                perror("strtoull error when parsing module version");
6cec19
+                module->version = 0;
6cec19
+            }
6cec19
+            if (endptr == val)
6cec19
+                module->version = 0;
6cec19
+        }
6cec19
+
6cec19
+        val = cr_find_attr("context", attr);
6cec19
+        if (val)
6cec19
+            module->context = g_string_chunk_insert(module->chunk, val);
6cec19
+
6cec19
+        val = cr_find_attr("arch", attr);
6cec19
+        if (val)
6cec19
+            module->arch = g_string_chunk_insert(module->chunk, val);
6cec19
+
6cec19
+        break;
6cec19
+
6cec19
     case STATE_PACKAGE:
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
@@ -303,6 +354,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
         assert(pd->updatecollection);
6cec19
+        assert(pd->updatecollectionmodule);
6cec19
         assert(pd->updatecollectionpackage);
6cec19
         val = cr_find_attr("type", attr);
6cec19
         if (val)
6cec19
@@ -313,6 +365,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
6cec19
         assert(pd->updateinfo);
6cec19
         assert(pd->updaterecord);
6cec19
         assert(pd->updatecollection);
6cec19
+        assert(pd->updatecollectionmodule);
6cec19
         assert(pd->updatecollectionpackage);
6cec19
         package->reboot_suggested = TRUE;
6cec19
         break;
6cec19
@@ -352,6 +405,7 @@ cr_end_handler(void *pdata, G_GNUC_UNUSED const char *element)
6cec19
     case STATE_UPDATED:
6cec19
     case STATE_REFERENCES:
6cec19
     case STATE_REFERENCE:
6cec19
+    case STATE_MODULE:
6cec19
     case STATE_PKGLIST:
6cec19
     case STATE_REBOOTSUGGESTED:
6cec19
         // All elements with no text data and without need of any
6cec19
diff --git a/tests/fixtures.h b/tests/fixtures.h
6cec19
index ee374f5..8567714 100644
6cec19
--- a/tests/fixtures.h
6cec19
+++ b/tests/fixtures.h
6cec19
@@ -83,5 +83,6 @@
6cec19
 #define TEST_UPDATEINFO_00      TEST_UPDATEINFO_FILES_PATH"updateinfo_00.xml"
6cec19
 #define TEST_UPDATEINFO_01      TEST_UPDATEINFO_FILES_PATH"updateinfo_01.xml"
6cec19
 #define TEST_UPDATEINFO_02      TEST_UPDATEINFO_FILES_PATH"updateinfo_02.xml.xz"
6cec19
+#define TEST_UPDATEINFO_03      TEST_UPDATEINFO_FILES_PATH"updateinfo_03.xml"
6cec19
 
6cec19
 #endif
6cec19
diff --git a/tests/python/tests/test_updatecollection.py b/tests/python/tests/test_updatecollection.py
6cec19
index f3433c0..71ac7dd 100644
6cec19
--- a/tests/python/tests/test_updatecollection.py
6cec19
+++ b/tests/python/tests/test_updatecollection.py
6cec19
@@ -16,6 +16,13 @@ class TestCaseUpdateCollection(unittest.TestCase):
6cec19
         self.assertEqual(col.name, None)
6cec19
         self.assertEqual(col.packages, [])
6cec19
 
6cec19
+        module = cr.UpdateCollectionModule()
6cec19
+        module.name = "kangaroo"
6cec19
+        module.stream = "0"
6cec19
+        module.version = 20180730223407
6cec19
+        module.context = "deadbeef"
6cec19
+        module.arch = "noarch"
6cec19
+
6cec19
         pkg = cr.UpdateCollectionPackage()
6cec19
         pkg.name = "foo"
6cec19
         pkg.version = "1.2"
6cec19
@@ -30,12 +37,21 @@ class TestCaseUpdateCollection(unittest.TestCase):
6cec19
 
6cec19
         col.shortname = "short name"
6cec19
         col.name = "long name"
6cec19
+        col.module = module
6cec19
         col.append(pkg)
6cec19
 
6cec19
         self.assertEqual(col.shortname, "short name")
6cec19
         self.assertEqual(col.name, "long name")
6cec19
         self.assertEqual(len(col.packages), 1)
6cec19
 
6cec19
+        # Check if the appended module was appended properly
6cec19
+        module = col.module
6cec19
+        self.assertEqual(module.name, "kangaroo")
6cec19
+        self.assertEqual(module.stream, "0")
6cec19
+        self.assertEqual(module.version, 20180730223407)
6cec19
+        self.assertEqual(module.context, "deadbeef")
6cec19
+        self.assertEqual(module.arch, "noarch")
6cec19
+
6cec19
         # Also check if the appended package was appended properly
6cec19
         pkg = col.packages[0]
6cec19
         self.assertEqual(pkg.name, "foo")
6cec19
diff --git a/tests/python/tests/test_updatecollectionmodule.py b/tests/python/tests/test_updatecollectionmodule.py
6cec19
new file mode 100644
6cec19
index 0000000..1e92b12
6cec19
--- /dev/null
6cec19
+++ b/tests/python/tests/test_updatecollectionmodule.py
6cec19
@@ -0,0 +1,31 @@
6cec19
+import unittest
6cec19
+import shutil
6cec19
+import tempfile
6cec19
+import os.path
6cec19
+import createrepo_c as cr
6cec19
+
6cec19
+from .fixtures import *
6cec19
+
6cec19
+class TestCaseUpdateCollectionModule(unittest.TestCase):
6cec19
+
6cec19
+    def test_updatecollectionmodule_setters(self):
6cec19
+        module = cr.UpdateCollectionModule()
6cec19
+        self.assertTrue(module)
6cec19
+
6cec19
+        self.assertEqual(module.name, None)
6cec19
+        self.assertEqual(module.stream, None)
6cec19
+        self.assertEqual(module.version, 0)
6cec19
+        self.assertEqual(module.context, None)
6cec19
+        self.assertEqual(module.arch, None)
6cec19
+
6cec19
+        module.name = "foo"
6cec19
+        module.stream = "0"
6cec19
+        module.version = 20180730223407
6cec19
+        module.context = "deadbeef"
6cec19
+        module.arch = "noarch"
6cec19
+
6cec19
+        self.assertEqual(module.name, "foo")
6cec19
+        self.assertEqual(module.stream, "0")
6cec19
+        self.assertEqual(module.version, 20180730223407)
6cec19
+        self.assertEqual(module.context, "deadbeef")
6cec19
+        self.assertEqual(module.arch, "noarch")
6cec19
diff --git a/tests/python/tests/test_updateinfo.py b/tests/python/tests/test_updateinfo.py
6cec19
index f3b88e1..727b707 100644
6cec19
--- a/tests/python/tests/test_updateinfo.py
6cec19
+++ b/tests/python/tests/test_updateinfo.py
6cec19
@@ -123,6 +123,100 @@ class TestCaseUpdateInfo(unittest.TestCase):
6cec19
         now = datetime(now.year, now.month, now.day, now.hour, now.minute,
6cec19
                        now.second, 0)
6cec19
 
6cec19
+        mod = cr.UpdateCollectionModule()
6cec19
+        mod.name = "kangaroo"
6cec19
+        mod.stream = "0"
6cec19
+        mod.version = 18446744073709551615
6cec19
+        mod.context = "deadbeef"
6cec19
+        mod.arch = "x86"
6cec19
+
6cec19
+        pkg = cr.UpdateCollectionPackage()
6cec19
+        pkg.name = "foo"
6cec19
+        pkg.version = "1.2"
6cec19
+        pkg.release = "3"
6cec19
+        pkg.epoch = "0"
6cec19
+        pkg.arch = "x86"
6cec19
+        pkg.src = "foo.src.rpm"
6cec19
+        pkg.filename = "foo.rpm"
6cec19
+        pkg.sum = "abcdef"
6cec19
+        pkg.sum_type = cr.SHA1
6cec19
+        pkg.reboot_suggested = True
6cec19
+
6cec19
+        col = cr.UpdateCollection()
6cec19
+        col.shortname = "short name"
6cec19
+        col.name = "long name"
6cec19
+        col.module = mod
6cec19
+        col.append(pkg)
6cec19
+
6cec19
+        ref = cr.UpdateReference()
6cec19
+        ref.href = "href"
6cec19
+        ref.id = "id"
6cec19
+        ref.type = "type"
6cec19
+        ref.title = "title"
6cec19
+
6cec19
+        rec = cr.UpdateRecord()
6cec19
+        rec.fromstr = "from"
6cec19
+        rec.status = "status"
6cec19
+        rec.type = "type"
6cec19
+        rec.version = "version"
6cec19
+        rec.id = "id"
6cec19
+        rec.title = "title"
6cec19
+        rec.issued_date = now
6cec19
+        rec.updated_date = now
6cec19
+        rec.rights = "rights"
6cec19
+        rec.release = "release"
6cec19
+        rec.pushcount = "pushcount"
6cec19
+        rec.severity = "severity"
6cec19
+        rec.summary = "summary"
6cec19
+        rec.description = "description"
6cec19
+        rec.solution = "solution"
6cec19
+        rec.append_collection(col)
6cec19
+        rec.append_reference(ref)
6cec19
+
6cec19
+        ui = cr.UpdateInfo()
6cec19
+        ui.append(rec)
6cec19
+
6cec19
+        xml = ui.xml_dump()
6cec19
+
6cec19
+        self.assertEqual(xml,
6cec19
+"""
6cec19
+<updates>
6cec19
+  <update from="from" status="status" type="type" version="version">
6cec19
+    <id>id</id>
6cec19
+    <title>title</title>
6cec19
+    <issued date="%(now)s"/>
6cec19
+    <updated date="%(now)s"/>
6cec19
+    <rights>rights</rights>
6cec19
+    <release>release</release>
6cec19
+    <pushcount>pushcount</pushcount>
6cec19
+    <severity>severity</severity>
6cec19
+    <summary>summary</summary>
6cec19
+    <description>description</description>
6cec19
+    <solution>solution</solution>
6cec19
+    <references>
6cec19
+      <reference href="href" id="id" type="type" title="title"/>
6cec19
+    </references>
6cec19
+    <pkglist>
6cec19
+      <collection short="short name">
6cec19
+        <name>long name</name>
6cec19
+        <module name="kangaroo" stream="0" version="18446744073709551615" context="deadbeef" arch="x86"/>
6cec19
+        <package name="foo" version="1.2" release="3" epoch="0" arch="x86" src="foo.src.rpm">
6cec19
+          <filename>foo.rpm</filename>
6cec19
+          <sum type="sha1">abcdef</sum>
6cec19
+          <reboot_suggested/>
6cec19
+        </package>
6cec19
+      </collection>
6cec19
+    </pkglist>
6cec19
+  </update>
6cec19
+</updates>
6cec19
+""" % {"now": now.strftime("%Y-%m-%d %H:%M:%S")})
6cec19
+
6cec19
+    def test_updateinfo_xml_dump_04(self):
6cec19
+        now = datetime.now()
6cec19
+        # Microseconds are always 0 in updateinfo
6cec19
+        now = datetime(now.year, now.month, now.day, now.hour, now.minute,
6cec19
+                       now.second, 0)
6cec19
+
6cec19
         pkg = cr.UpdateCollectionPackage()
6cec19
         pkg.name = "foo"
6cec19
         pkg.version = "1.2"
6cec19
@@ -135,6 +229,7 @@ class TestCaseUpdateInfo(unittest.TestCase):
6cec19
         pkg.sum_type = cr.SHA1
6cec19
         pkg.reboot_suggested = True
6cec19
 
6cec19
+        # Collection without module
6cec19
         col = cr.UpdateCollection()
6cec19
         col.shortname = "short name"
6cec19
         col.name = "long name"
6cec19
@@ -167,6 +262,99 @@ class TestCaseUpdateInfo(unittest.TestCase):
6cec19
 
6cec19
         ui = cr.UpdateInfo()
6cec19
         ui.append(rec)
6cec19
+
6cec19
+        xml = ui.xml_dump()
6cec19
+
6cec19
+        self.assertEqual(xml,
6cec19
+"""
6cec19
+<updates>
6cec19
+  <update from="from" status="status" type="type" version="version">
6cec19
+    <id>id</id>
6cec19
+    <title>title</title>
6cec19
+    <issued date="%(now)s"/>
6cec19
+    <updated date="%(now)s"/>
6cec19
+    <rights>rights</rights>
6cec19
+    <release>release</release>
6cec19
+    <pushcount>pushcount</pushcount>
6cec19
+    <severity>severity</severity>
6cec19
+    <summary>summary</summary>
6cec19
+    <description>description</description>
6cec19
+    <solution>solution</solution>
6cec19
+    <references>
6cec19
+      <reference href="href" id="id" type="type" title="title"/>
6cec19
+    </references>
6cec19
+    <pkglist>
6cec19
+      <collection short="short name">
6cec19
+        <name>long name</name>
6cec19
+        <package name="foo" version="1.2" release="3" epoch="0" arch="x86" src="foo.src.rpm">
6cec19
+          <filename>foo.rpm</filename>
6cec19
+          <sum type="sha1">abcdef</sum>
6cec19
+          <reboot_suggested/>
6cec19
+        </package>
6cec19
+      </collection>
6cec19
+    </pkglist>
6cec19
+  </update>
6cec19
+</updates>
6cec19
+""" % {"now": now.strftime("%Y-%m-%d %H:%M:%S")})
6cec19
+
6cec19
+    def test_updateinfo_xml_dump_05(self):
6cec19
+        now = datetime.now()
6cec19
+        # Microseconds are always 0 in updateinfo
6cec19
+        now = datetime(now.year, now.month, now.day, now.hour, now.minute,
6cec19
+                       now.second, 0)
6cec19
+
6cec19
+        # Collection module with unset fields
6cec19
+        mod = cr.UpdateCollectionModule()
6cec19
+        mod.version = 18446744073709551615
6cec19
+        mod.context = "deadbeef"
6cec19
+        mod.arch = "x86"
6cec19
+
6cec19
+        pkg = cr.UpdateCollectionPackage()
6cec19
+        pkg.name = "foo"
6cec19
+        pkg.version = "1.2"
6cec19
+        pkg.release = "3"
6cec19
+        pkg.epoch = "0"
6cec19
+        pkg.arch = "x86"
6cec19
+        pkg.src = "foo.src.rpm"
6cec19
+        pkg.filename = "foo.rpm"
6cec19
+        pkg.sum = "abcdef"
6cec19
+        pkg.sum_type = cr.SHA1
6cec19
+        pkg.reboot_suggested = True
6cec19
+
6cec19
+        col = cr.UpdateCollection()
6cec19
+        col.shortname = "short name"
6cec19
+        col.name = "long name"
6cec19
+        col.module = mod
6cec19
+        col.append(pkg)
6cec19
+
6cec19
+        ref = cr.UpdateReference()
6cec19
+        ref.href = "href"
6cec19
+        ref.id = "id"
6cec19
+        ref.type = "type"
6cec19
+        ref.title = "title"
6cec19
+
6cec19
+        rec = cr.UpdateRecord()
6cec19
+        rec.fromstr = "from"
6cec19
+        rec.status = "status"
6cec19
+        rec.type = "type"
6cec19
+        rec.version = "version"
6cec19
+        rec.id = "id"
6cec19
+        rec.title = "title"
6cec19
+        rec.issued_date = now
6cec19
+        rec.updated_date = now
6cec19
+        rec.rights = "rights"
6cec19
+        rec.release = "release"
6cec19
+        rec.pushcount = "pushcount"
6cec19
+        rec.severity = "severity"
6cec19
+        rec.summary = "summary"
6cec19
+        rec.description = "description"
6cec19
+        rec.solution = "solution"
6cec19
+        rec.append_collection(col)
6cec19
+        rec.append_reference(ref)
6cec19
+
6cec19
+        ui = cr.UpdateInfo()
6cec19
+        ui.append(rec)
6cec19
+
6cec19
         xml = ui.xml_dump()
6cec19
 
6cec19
         self.assertEqual(xml,
6cec19
@@ -190,6 +378,7 @@ class TestCaseUpdateInfo(unittest.TestCase):
6cec19
     <pkglist>
6cec19
       <collection short="short name">
6cec19
         <name>long name</name>
6cec19
+        <module version="18446744073709551615" context="deadbeef" arch="x86"/>
6cec19
         <package name="foo" version="1.2" release="3" epoch="0" arch="x86" src="foo.src.rpm">
6cec19
           <filename>foo.rpm</filename>
6cec19
           <sum type="sha1">abcdef</sum>
6cec19
diff --git a/tests/test_xml_parser_updateinfo.c b/tests/test_xml_parser_updateinfo.c
6cec19
index 94768ce..3f0cfee 100644
6cec19
--- a/tests/test_xml_parser_updateinfo.c
6cec19
+++ b/tests/test_xml_parser_updateinfo.c
6cec19
@@ -168,6 +168,90 @@ test_cr_xml_parse_updateinfo_02(void)
6cec19
     cr_updateinfo_free(ui);
6cec19
 }
6cec19
 
6cec19
+//Test for module support
6cec19
+static void
6cec19
+test_cr_xml_parse_updateinfo_03(void)
6cec19
+{
6cec19
+    GError *tmp_err = NULL;
6cec19
+    cr_UpdateInfo *ui = cr_updateinfo_new();
6cec19
+    cr_UpdateRecord *update;
6cec19
+    cr_UpdateReference *ref;
6cec19
+    cr_UpdateCollection *col;
6cec19
+    cr_UpdateCollectionModule *module;
6cec19
+    cr_UpdateCollectionPackage *pkg;
6cec19
+
6cec19
+    int ret = cr_xml_parse_updateinfo(TEST_UPDATEINFO_03, ui,
6cec19
+                                      NULL, NULL, &tmp_err);
6cec19
+
6cec19
+    g_assert(tmp_err == NULL);
6cec19
+    g_assert_cmpint(ret, ==, CRE_OK);
6cec19
+
6cec19
+    g_assert_cmpint(g_slist_length(ui->updates), ==, 6);
6cec19
+    update = g_slist_nth_data(ui->updates, 3);
6cec19
+
6cec19
+    g_assert_cmpstr(update->from, ==, "errata@redhat.com");
6cec19
+    g_assert_cmpstr(update->status, ==, "stable");
6cec19
+    g_assert_cmpstr(update->type, ==, "enhancement");
6cec19
+    g_assert_cmpstr(update->version, ==, "1");
6cec19
+    g_assert_cmpstr(update->id, ==, "RHEA-2012:0058");
6cec19
+    g_assert_cmpstr(update->title, ==, "Gorilla_Erratum");
6cec19
+    g_assert_cmpstr(update->description, ==, "Gorilla_Erratum");
6cec19
+
6cec19
+    update = g_slist_nth_data(ui->updates, 4);
6cec19
+
6cec19
+    g_assert_cmpstr(update->id, ==, "RHEA-2012:0059");
6cec19
+    g_assert_cmpstr(update->title, ==, "Duck_Kangaroo_Erratum");
6cec19
+    g_assert_cmpstr(update->description, ==, "Duck_Kangaro_Erratum description");
6cec19
+    g_assert_cmpstr(update->issued_date, ==, "2018-01-27 16:08:09");
6cec19
+    g_assert_cmpstr(update->updated_date, ==, "2018-07-20 06:00:01 UTC");
6cec19
+    g_assert_cmpstr(update->release, ==, "1");
6cec19
+
6cec19
+    g_assert_cmpint(g_slist_length(update->references), ==, 0);
6cec19
+
6cec19
+    g_assert_cmpint(g_slist_length(update->collections), ==, 2);
6cec19
+    col = g_slist_nth_data(update->collections, 0);
6cec19
+    g_assert_cmpstr(col->shortname, ==, "");
6cec19
+    g_assert_cmpstr(col->name, ==, "coll_name1");
6cec19
+
6cec19
+    module = col->module;
6cec19
+    g_assert_cmpstr(module->name, ==, "kangaroo");
6cec19
+    g_assert_cmpstr(module->stream, ==, "0");
6cec19
+    g_assert_cmpuint(module->version, ==, 20180730223407);
6cec19
+    g_assert_cmpstr(module->context, ==, "deadbeef");
6cec19
+    g_assert_cmpstr(module->arch, ==, "noarch");
6cec19
+
6cec19
+    g_assert_cmpint(g_slist_length(col->packages), ==, 1);
6cec19
+    pkg = col->packages->data;
6cec19
+    g_assert_cmpstr(pkg->name, ==, "kangaroo");
6cec19
+    g_assert_cmpstr(pkg->version, ==, "0.3");
6cec19
+    g_assert_cmpstr(pkg->release, ==, "1");
6cec19
+    g_assert(!pkg->epoch);
6cec19
+    g_assert_cmpstr(pkg->arch, ==, "noarch");
6cec19
+    g_assert_cmpstr(pkg->src, ==, "http://www.fedoraproject.org");
6cec19
+    g_assert_cmpstr(pkg->filename, ==, "kangaroo-0.3-1.noarch.rpm");
6cec19
+    g_assert(!pkg->sum);
6cec19
+    g_assert(!pkg->sum_type);
6cec19
+
6cec19
+    col = g_slist_nth_data(update->collections, 1);
6cec19
+    g_assert_cmpstr(col->shortname, ==, "");
6cec19
+    g_assert_cmpstr(col->name, ==, "coll_name2");
6cec19
+
6cec19
+    module = col->module;
6cec19
+    g_assert_cmpstr(module->name, ==, "duck");
6cec19
+    g_assert_cmpstr(module->stream, ==, "0");
6cec19
+    g_assert_cmpuint(module->version, ==, 20180730233102);
6cec19
+    g_assert_cmpstr(module->context, ==, "deadbeef");
6cec19
+    g_assert_cmpstr(module->arch, ==, "noarch");
6cec19
+
6cec19
+    g_assert_cmpint(g_slist_length(col->packages), ==, 1);
6cec19
+    pkg = col->packages->data;
6cec19
+    g_assert_cmpstr(pkg->name, ==, "duck");
6cec19
+    g_assert_cmpstr(pkg->version, ==, "0.7");
6cec19
+    g_assert_cmpstr(pkg->filename, ==, "duck-0.7-1.noarch.rpm");
6cec19
+
6cec19
+    cr_updateinfo_free(ui);
6cec19
+}
6cec19
+
6cec19
 int
6cec19
 main(int argc, char *argv[])
6cec19
 {
6cec19
@@ -179,6 +263,8 @@ main(int argc, char *argv[])
6cec19
                     test_cr_xml_parse_updateinfo_01);
6cec19
     g_test_add_func("/xml_parser_updateinfo/test_cr_xml_parse_updateinfo_02",
6cec19
                     test_cr_xml_parse_updateinfo_02);
6cec19
+    g_test_add_func("/xml_parser_updateinfo/test_cr_xml_parse_updateinfo_03",
6cec19
+                    test_cr_xml_parse_updateinfo_03);
6cec19
 
6cec19
     return g_test_run();
6cec19
 }
6cec19
diff --git a/tests/testdata/updateinfo_files/updateinfo_03.xml b/tests/testdata/updateinfo_files/updateinfo_03.xml
6cec19
new file mode 100644
6cec19
index 0000000..ddbd99b
6cec19
--- /dev/null
6cec19
+++ b/tests/testdata/updateinfo_files/updateinfo_03.xml
6cec19
@@ -0,0 +1,128 @@
6cec19
+
6cec19
+<updates>
6cec19
+<update from="errata@redhat.com" status="stable" type="security" version="1">
6cec19
+  <id>RHEA-2012:0055</id>
6cec19
+  <title>Sea_Erratum</title>
6cec19
+  <release>1</release>
6cec19
+  <issued date="2012-01-27 16:08:06"/>
6cec19
+  <updated date="2012-01-27 16:08:06"/>
6cec19
+  <description>Sea_Erratum</description>
6cec19
+  <pkglist>
6cec19
+    <collection short="">
6cec19
+      <name>1</name>
6cec19
+      <package arch="noarch" name="walrus" release="1" src="http://www.fedoraproject.org" version="5.21">
6cec19
+        <filename>walrus-5.21-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+      <package arch="noarch" name="penguin" release="1" src="http://www.fedoraproject.org" version="0.9.1">
6cec19
+        <filename>penguin-0.9.1-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+      <package arch="noarch" name="shark" release="1" src="http://www.fedoraproject.org" version="0.1">
6cec19
+        <filename>shark-0.1-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+  </pkglist>
6cec19
+</update>
6cec19
+
6cec19
+<update from="errata@redhat.com" status="stable" type="security" version="1">
6cec19
+  <id>RHEA-2012:0056</id>
6cec19
+  <title>Bird_Erratum</title>
6cec19
+  <release>1</release>
6cec19
+  <issued date="2013-01-27 16:08:08"/>
6cec19
+  <updated date="2013-02-27 17:00:00"/>
6cec19
+  <description>ParthaBird_Erratum</description>
6cec19
+  <pkglist>
6cec19
+    <collection short="">
6cec19
+      <name>1</name>
6cec19
+      <package arch="noarch" name="crow" release="1" src="http://www.fedoraproject.org" version="0.8">
6cec19
+        <filename>crow-0.8-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+      <package arch="noarch" name="stork" release="2" src="http://www.fedoraproject.org" version="0.12">
6cec19
+        <filename>stork-0.12-2.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+      <package arch="noarch" name="duck" release="1" src="http://www.fedoraproject.org" version="0.6">
6cec19
+        <filename>duck-0.6-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+  </pkglist>
6cec19
+</update>
6cec19
+
6cec19
+<update from="errata@redhat.com" status="stable" type="security" version="1">
6cec19
+  <id>RHEA-2012:0057</id>
6cec19
+  <title>Bear_ErratumPARTHA</title>
6cec19
+  <release>1</release>
6cec19
+  <issued date="2013-01-27 16:08:05"/>
6cec19
+  <updated date="2013-01-27 16:08:05 UTC"/>
6cec19
+  <description>Bear_Erratum</description>
6cec19
+  <pkglist>
6cec19
+    <collection short="">
6cec19
+      <name>1</name>
6cec19
+      <package arch="noarch" name="bear" release="1" src="http://www.fedoraproject.org" version="4.1">
6cec19
+        <filename>bear-4.1-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+  </pkglist>
6cec19
+</update>
6cec19
+
6cec19
+<update from="errata@redhat.com" status="stable" type="enhancement" version="1">
6cec19
+  <id>RHEA-2012:0058</id>
6cec19
+  <title>Gorilla_Erratum</title>
6cec19
+  <release>1</release>
6cec19
+  <issued date="2013-01-27 16:08:09"/>
6cec19
+  <updated date="2014-07-20 06:00:01 UTC"/>
6cec19
+  <description>Gorilla_Erratum</description>
6cec19
+  <pkglist>
6cec19
+    <collection short="">
6cec19
+      <name>1</name>
6cec19
+      <package arch="noarch" name="gorilla" release="1" src="http://www.fedoraproject.org" version="0.62">
6cec19
+        <filename>gorilla-0.62-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+  </pkglist>
6cec19
+</update>
6cec19
+
6cec19
+<update from="errata@redhat.com" status="stable" type="enhancement" version="1">
6cec19
+  <id>RHEA-2012:0059</id>
6cec19
+  <title>Duck_Kangaroo_Erratum</title>
6cec19
+  <release>1</release>
6cec19
+  <issued date="2018-01-27 16:08:09"/>
6cec19
+  <updated date="2018-07-20 06:00:01 UTC"/>
6cec19
+  <description>Duck_Kangaro_Erratum description</description>
6cec19
+  <pkglist>
6cec19
+    <collection short="">
6cec19
+      <name>coll_name1</name>
6cec19
+      <module name="kangaroo" stream="0" version="20180730223407" context="deadbeef" arch="noarch"/>
6cec19
+      
6cec19
+               version="0.3">
6cec19
+        <filename>kangaroo-0.3-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+    <collection short="">
6cec19
+      <name>coll_name2</name>
6cec19
+      <module name="duck" stream="0" version="20180730233102" context="deadbeef" arch="noarch"/>
6cec19
+      
6cec19
+               version="0.7">
6cec19
+        <filename>duck-0.7-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+  </pkglist>
6cec19
+</update>
6cec19
+
6cec19
+<update from="errata@redhat.com" status="stable" type="enhancement" version="1">
6cec19
+  <id>RHEA-2012:0060</id>
6cec19
+  <title>Duck_0.8_Erratum</title>
6cec19
+  <release>1</release>
6cec19
+  <issued date="2018-01-29 16:08:09"/>
6cec19
+  <updated date="2018-07-29 06:00:01 UTC"/>
6cec19
+  <description>Duck_0.8_Erratum description</description>
6cec19
+  <pkglist>
6cec19
+    <collection short="">
6cec19
+      <name>coll_name</name>
6cec19
+      <module name="duck" stream="0" version="201809302113907" context="deadbeef" arch="noarch"/>
6cec19
+      
6cec19
+               version="0.8">
6cec19
+        <filename>duck-0.8-1.noarch.rpm</filename>
6cec19
+      </package>
6cec19
+    </collection>
6cec19
+  </pkglist>
6cec19
+</update>
6cec19
+</updates>
6cec19
--
6cec19
libgit2 0.27.8
6cec19