Implemented BuildData API
authorEd Bartosh <eduard.bartosh@intel.com>
Mon, 13 May 2013 11:30:21 +0000 (14:30 +0300)
committerEd Bartosh <eduard.bartosh@intel.com>
Mon, 13 May 2013 12:31:38 +0000 (15:31 +0300)
This API is responcible for generating and parsing build.xml

Change-Id: If580443977f20fd2266ebbd64e027c59d1aa63fd
Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
.coveragerc [new file with mode: 0644]
common/builddata.py [new file with mode: 0644]
tests/test_builddata.py [new file with mode: 0644]

diff --git a/.coveragerc b/.coveragerc
new file mode 100644 (file)
index 0000000..3779e08
--- /dev/null
@@ -0,0 +1,2 @@
+[run]
+include = common/*
diff --git a/common/builddata.py b/common/builddata.py
new file mode 100644 (file)
index 0000000..c6aa613
--- /dev/null
@@ -0,0 +1,145 @@
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2012 Intel, 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; version 2 of the License
+#
+# 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., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""
+This module takes care of handling/generating/parsing of build.xml
+"""
+
+import re
+
+from collections import OrderedDict
+from xml.dom import minidom
+
+class BuildDataError(Exception):
+    """Custom BuildData exception."""
+    pass
+
+class BuildData(object):
+    """Class for handling build data.
+
+    NOTE! For now it contains only APIs used by pre-release job
+          (see usage example below).
+    NOTE! Feel free to add APIs, used by gbs here
+
+    """
+
+    # fixing of buggy xml.dom.minidom.toprettyxml
+    XMLTEXT_RE = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
+
+    def __init__(self, build_id=None):
+        self.build_id = build_id
+        self.targets = OrderedDict()
+
+    def add_target(self, target):
+        """Add (or update) target to the list of targets."""
+        self.targets[target["name"]] = target
+
+    def load(self, xml):
+        """Load build data from string."""
+
+        def get_elem(node, name):
+            """Helper: get first element by tag name."""
+            try:
+                return node.getElementsByTagName(name)[0]
+            except IndexError:
+                raise BuildDataError("Error: <%s><%s> not found" % \
+                                     (node.nodeName, name))
+        dom = minidom.parseString(xml)
+        btargets = get_elem(dom, "buildtargets")
+
+        for btarget in btargets.getElementsByTagName("buildtarget"):
+            print btarget.childNodes
+            bconf = get_elem(btarget, "buildconf")
+
+            target = {
+                "name":  btarget.getAttribute("name"),
+                "archs": [],
+                "buildconf": {
+                    "location":
+                        get_elem(bconf, "location").getAttribute("href"),
+                    "checksum": {
+                       "type": get_elem(bconf, "checksum").getAttribute("type"),
+                       "value": get_elem(bconf, "checksum").firstChild.data
+                    }
+                }
+            }
+
+            # Get archs
+            for barch in btarget.getElementsByTagName("arch"):
+                target["archs"].append(barch.getAttribute("name"))
+
+            self.targets[target["name"]] = target
+
+    def to_xml(self, hreadable=True):
+        """Format build data as xml."""
+        content = '<?xml version="1.0"?><build version="1.0">'\
+                  '<id>%s</id>' % self.build_id
+        # list of repos
+        content += '<repos>'
+        archs = []
+        for name, target in self.targets.iteritems():
+            content += '<repo>%s</repo>' % name
+            archs.extend(target['archs'])
+        content += '</repos>'
+
+        # list of architectures
+        content += '<archs>'
+        for arch in set(archs):
+            content += '<arch>%s</arch>' % arch
+        content += '</archs>'
+
+        # build config
+        #for name in self.targets:
+        #    content += '<buildconf>%s</buildconf>' % \
+        #               self.targets[name]['buildconf']
+
+        # build targets
+        content += '<buildtargets>'
+        for name in self.targets:
+            target = self.targets[name]
+            content += '<buildtarget name="%s">' % name
+
+            # buildconf
+            content += '<buildconf>'
+            buildconf = target['buildconf']
+            content += '<location href="%s"/>' % buildconf['location']
+            content += '<checksum type="%s">%s</checksum>' % \
+                       (buildconf['checksum']['type'],
+                        buildconf['checksum']['value'])
+            content += '</buildconf>'
+
+            # archs
+            content += '<arch default="yes">%s</arch>' % target['archs'][0]
+            for arch in target["archs"][1:]:
+                content += '<arch>%s</arch>' % arch
+
+            content += '</buildtarget>'
+        content += '</buildtargets></build>'
+
+        # make it human readable
+        if hreadable:
+            dom = minidom.parseString(content)
+            content = self.XMLTEXT_RE.sub('>\g<1></',
+                                          dom.toprettyxml(indent='  '))
+
+        return content
+
+    def save(self, fname, hreadable=True):
+        """Save builddata to file."""
+        # Write content down
+        with open(fname, 'w') as out:
+            out.write(self.to_xml(hreadable))
diff --git a/tests/test_builddata.py b/tests/test_builddata.py
new file mode 100644 (file)
index 0000000..d4ecdf4
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/python -tt
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2012 Intel, 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; version 2 of the License
+#
+# 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., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Unit tests for class BuildData"""
+
+import os
+import unittest
+from collections import OrderedDict
+
+from common.builddata import BuildData, BuildDataError
+
+
+TEST_XML = """<?xml version="1.0"?>
+<build version="1.0">
+  <archs>
+    <arch>i586</arch>
+    <arch>armv7l</arch>
+  </archs>
+  <repos>
+    <repo>exynos</repo>
+    <repo>atom</repo>
+  </repos>
+  <buildtargets>
+    <buildtarget name="atom">
+      <buildconf>
+        <location href="3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8-build.conf"/>
+        <checksum type="sh256">3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8</checksum>
+      </buildconf>
+      <arch default="yes" name="i586">
+        <repo type="binary">repos/atom/i586/packages</repo>
+        <repo type="debug">repos/atom/i586/debug</repo>
+        <repo type="source">repos/atom/i586/sources</repo>
+      </arch>
+      <arch name="x86_64">
+        <repo type="binary">repos/atom/x86_64/packages</repo>
+        <repo type="debug">repos/atom/x86_64/debug</repo>
+        <repo type="source">repos/atom/x86_64/sources</repo>
+      </arch>
+    </buildtarget>
+    <buildtarget name="exynos">
+      <buildconf>
+        <location href="3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8-build.conf"/>
+        <checksum type="sh256">3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8</checksum>
+      </buildconf>
+      <arch default="yes" name="armv7l">
+        <repo type="binary">repos/exynos/armv7l/packages</repo>
+        <repo type="debug">repos/exynos/armv7l/debug</repo>
+        <repo type="source">repos/exynos/armv7l/sources</repo>
+      </arch>
+    </buildtarget>
+  </buildtargets>
+  <id>tizen-2.1_20130326.13</id>
+</build>
+"""
+
+class BuildDataTest(unittest.TestCase):
+    '''Tests for BuildData functionality.'''
+
+    def test_load(self):
+        bdata = BuildData(build_id='test.id')
+        bdata.load(TEST_XML)
+        self.assertEqual(bdata.targets, OrderedDict(
+            [(u'atom', {'buildconf':
+               {'checksum': {'type': u'sh256',
+                             'value': u'3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8'},
+                'location':  u'3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8-build.conf'},
+               'name': u'atom', 'archs': [u'i586', u'x86_64']}),
+             (u'exynos', {'buildconf':
+               {'checksum': {'type': u'sh256',
+                             'value': u'3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8'},
+                'location': u'3bd64bd5fa862d99dbc363ccb1557d137b5685bc3bfe9a86bcbf50767da5e2e8-build.conf'},
+               'name': u'exynos', 'archs': [u'armv7l']})]))
+
+    def test_load_error(self):
+        """Test rasing BuildDataError."""
+        bdata = BuildData(1)
+        self.assertRaises(BuildDataError, bdata.load, '<test/>')
+
+
+    def test_add_target(self):
+        """Test adding new target."""
+        bdata = BuildData(1)
+        target = {"name": "test_target",
+                  "archs": ['i586'],
+                  "buildconf": {
+                      "checksum": {
+                          "type": "md5",
+                          "value": "45c5fb8bd2b9065bd7eb961cf3663b8c"
+                       },
+                      "location": 'build.conf'
+                   }
+                 }
+
+        bdata.add_target(target)
+        self.assertTrue(target["name"] in bdata.targets)
+
+
+    def test_to_xml(self):
+        """Test xml output."""
+        bdata = BuildData(build_id='test.id')
+        bdata.load(TEST_XML)
+        self.assertEqual(len(bdata.to_xml()), 964)
+        self.assertEqual(len(bdata.to_xml(hreadable=False)), 809)
+
+
+    def test_save(self):
+        bdata = BuildData(build_id='test.id')
+        bdata.load(TEST_XML)
+        fname = 'test_save.tmp'
+        bdata.save(fname)
+        self.assertEqual(len(open(fname).read()), 964)
+        os.unlink(fname)