summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2012-11-27 16:19:38 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2012-11-27 16:19:38 +1000
commit86db7b3750c4f9adeb310dca39b7623ce6f3b964 (patch)
tree41a37b3627c3809964c162fc1649656422b808ee
parent1aa9715df511d0d7b4b67240f114df59393c9be1 (diff)
parent4f40e03ac3b01ac6e096113304216df571c24d8a (diff)
Merge branch 'bug-registry'
-rw-r--r--HACKING27
-rw-r--r--registry/example.xml35
-rw-r--r--registry/server-registry.xml210
-rwxr-xr-xregistry/xit-bug-registry814
4 files changed, 1086 insertions, 0 deletions
diff --git a/HACKING b/HACKING
index 3ce771e..7f1e55a 100644
--- a/HACKING
+++ b/HACKING
@@ -213,3 +213,30 @@ If you need to gdb the server to set breakpoints before a test starts, set
The server will be sent a SIGSTOP signal after starting, waiting for you to
attach gdb. It can then be foregrounded and the test continues.
+
+== Using the bug registry ==
+Most server versions will fail at least some tests, tests may have been
+committed before a fix for a given failure was upstream. It's hard to keep
+track of which tests fail, which is what the bug registry addresses.
+
+Often, what really matters is if there are any tests that changed after a
+fix in the server. To use the bug registry for this task run the following
+commands. On the __original__ server, run
+ # Run the grab tests, printing to a JUnit test xml file
+ ./test/server/grab --gtest_output="xml:grab.xml"
+ # Create a registry based on the test results
+ xit-bug-registry create grab.xml > grab-results.xml
+ # fix server bug
+ # Re-run grab tests on new server
+ ./test/server/grab --gtest_output="xml:grab.xml"
+ # Compare previous results with new results
+ xit-bug-registry verify grab.xml < grab-results.xml
+
+
+The output will print the test names and the expected vs real outcome plus a
+status code to grep for the unexpected.
+
+Note that especially test failures need to be treated with caution. An
+unrelated fix may alter the outcome of a already failing tests (e.g. the
+server now crashes as opposed to returning incorrect values). It is not
+enough to simply check that all tests have the same outcome.
diff --git a/registry/example.xml b/registry/example.xml
new file mode 100644
index 0000000..846b2a1
--- /dev/null
+++ b/registry/example.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- typically, a registry file will contain one registry but for the
+ purpose of merging multiple registries we allow for more than one -->
+<xit:registries xmlns:xit="http://www.x.org/xorg-integration-testing">
+ <!-- registry name is for humans -->
+ <xit:registry name="x.org">
+ <xit:meta>
+ <!-- date this registry was compiled -->
+ <xit:date>2012-11-19</xit:date>
+ <!-- Registers module versions used when compiling this registry -->
+ <xit:moduleversion name="xserver" type="git">123550deadbeef</xit:moduleversion>
+ <xit:moduleversion name="evdev" type="git">1281211babab</xit:moduleversion>
+ </xit:meta>
+ <xit:testsuite name="TestSuite">
+ <!-- mark a given test as known to fail in this registry -->
+ <xit:testcase name="TestCase" success="false">
+ <!-- URL pointing to the bugreport. optional type, default is
+ bugzilla. This URL may be used to retrieve more information about
+ a bug -->
+ <xit:bug type="bugzilla">http://bugs.freedesktop.org/123456</xit:bug>
+ <!-- Something pointing to any archive containing additional
+ information about this bug. Default type is "text" -->
+ <xit:testinfo type="text">This test case is currently supposed to fail because blah</xit:bug-info>
+ <xit:testinfo type="url">http://lists.x.org/archive/message00001.html</xit:bug-info>
+ <!-- commit or package known to fix this test case -->
+ <xit:fix type="git">abcde1234deadbeef</xit:fix>
+ <xit:fix type="rpm">xorg-x11-server-1.13.0-1.fc18</xit:fix>
+ </xit:testcase>
+
+ <!-- mark a given test as known to succeed in this registry. This
+ element may contain all the elements for failed bugs -->
+ <xit:test name="TestName2" success="true" />
+ </xit:testsuite>
+ </xit:registry>
+</xit:registries>
diff --git a/registry/server-registry.xml b/registry/server-registry.xml
new file mode 100644
index 0000000..0d2e988
--- /dev/null
+++ b/registry/server-registry.xml
@@ -0,0 +1,210 @@
+<xit:registries xmlns:xit="http://www.x.org/xorg-integration-testing">
+ <xit:registry name="xorg-upstream">
+ <xit:meta>
+ <xit:date>2012-11-16</xit:date>
+ <xit:moduleversion name="xserver" type="git">011f8458805e443ac9130865d2840a929a00cabf</xit:moduleversion>
+ </xit:meta>
+ <xit:testsuite name="BarrierConflictingDirections">
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/0" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/1" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/2" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/3" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/4" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/5" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/6" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/7" success="true"/>
+ <xit:testcase name="InvalidConflictingDirectionsBarrier/8" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="BarrierConstrained">
+ <xit:testcase name="HoritzontalBarrierNegativeYBlocksMotion" success="true"/>
+ <xit:testcase name="HoritzontalBarrierPositiveYBlocksMotion" success="true"/>
+ <xit:testcase name="HorizontalBarrierBothDirectionsYBlocksNoMotion" success="true"/>
+ <xit:testcase name="HorizontalBarrierNoDirectionBlocksMotion" success="true"/>
+ <xit:testcase name="VerticalBarrierBothDirectionsXBlocksNoMotion" success="true"/>
+ <xit:testcase name="VerticalBarrierNegativeXBlocksMotion" success="true"/>
+ <xit:testcase name="VerticalBarrierNoDirectionBlocksMotion" success="true"/>
+ <xit:testcase name="VerticalBarrierPositiveXBlocksMotion" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="BarrierNonZeroArea">
+ <xit:testcase name="InvalidNonZeroAreaBarrier/0" success="true"/>
+ <xit:testcase name="InvalidNonZeroAreaBarrier/1" success="true"/>
+ <xit:testcase name="InvalidNonZeroAreaBarrier/2" success="true"/>
+ <xit:testcase name="InvalidNonZeroAreaBarrier/3" success="true"/>
+ <xit:testcase name="InvalidNonZeroAreaBarrier/4" success="true"/>
+ <xit:testcase name="InvalidNonZeroAreaBarrier/5" success="true"/>
+ <xit:testcase name="InvalidNonZeroAreaBarrier/6" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="BarrierSimpleTest">
+ <xit:testcase name="CreateAndDestroyBarrier" success="true"/>
+ <xit:testcase name="DestroyInvalidBarrier" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="BarrierZeroLength">
+ <xit:testcase name="InvalidZeroLengthBarrier/0" success="true"/>
+ <xit:testcase name="InvalidZeroLengthBarrier/1" success="true"/>
+ <xit:testcase name="InvalidZeroLengthBarrier/2" success="true"/>
+ <xit:testcase name="InvalidZeroLengthBarrier/3" success="true"/>
+ <xit:testcase name="InvalidZeroLengthBarrier/4" success="true"/>
+ <xit:testcase name="InvalidZeroLengthBarrier/5" success="true"/>
+ <xit:testcase name="InvalidZeroLengthBarrier/6" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="EventQueueTest">
+ <xit:testcase name="mieqOverflow" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="MiscServerTest">
+ <xit:testcase name="DoubleSegfault" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="PointerGrabTest">
+ <xit:testcase name="GrabDisabledDevices" success="true"/>
+ <xit:testcase name="ImplicitGrabRawEvents" success="false"/>
+ </xit:testsuite>
+ <xit:testsuite name="ScreenSaverTest">
+ <xit:testcase name="ScreenSaverActivateDeactivate" success="false">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=56649</xit:bug>
+ </xit:testcase>
+ </xit:testsuite>
+ <xit:testsuite name="TouchDeviceChangeTest">
+ <xit:testcase name="DeviceChangedEventPointerToTouchSwitch" success="true"/>
+ <xit:testcase name="DeviceChangedEventTouchToPointerSwitch" success="true"/>
+ <xit:testcase name="NoCursorJumpsOnTouchToPointerSwitch" success="false"/>
+ </xit:testsuite>
+ <xit:testsuite name="TouchDeviceTest">
+ <xit:testcase name="DisableDeviceEndTouches" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="TouchEventHistoryTest">
+ <xit:testcase name="EventHistoryReplay/0" success="false"/>
+ <xit:testcase name="EventHistoryReplay/1" success="false"/>
+ <xit:testcase name="EventHistoryReplay/10" success="false"/>
+ <xit:testcase name="EventHistoryReplay/11" success="false"/>
+ <xit:testcase name="EventHistoryReplay/12" success="false"/>
+ <xit:testcase name="EventHistoryReplay/13" success="false"/>
+ <xit:testcase name="EventHistoryReplay/14" success="false"/>
+ <xit:testcase name="EventHistoryReplay/15" success="false"/>
+ <xit:testcase name="EventHistoryReplay/16" success="false"/>
+ <xit:testcase name="EventHistoryReplay/17" success="false"/>
+ <xit:testcase name="EventHistoryReplay/18" success="false"/>
+ <xit:testcase name="EventHistoryReplay/19" success="false"/>
+ <xit:testcase name="EventHistoryReplay/2" success="false"/>
+ <xit:testcase name="EventHistoryReplay/3" success="false"/>
+ <xit:testcase name="EventHistoryReplay/4" success="false"/>
+ <xit:testcase name="EventHistoryReplay/5" success="false"/>
+ <xit:testcase name="EventHistoryReplay/6" success="false"/>
+ <xit:testcase name="EventHistoryReplay/7" success="false"/>
+ <xit:testcase name="EventHistoryReplay/8" success="false"/>
+ <xit:testcase name="EventHistoryReplay/9" success="false"/>
+ </xit:testsuite>
+ <xit:testsuite name="TouchGrabTestMultipleModes">
+ <xit:testcase name="ActiveAndPassiveGrab/0" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=55738</xit:bug>
+ <xit:fix type="git">676447190190d8546165e21be242cf16dd69f5ae</xit:fix>
+ </xit:testcase>
+ <xit:testcase name="ActiveAndPassiveGrab/1" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=55738</xit:bug>
+ <xit:fix type="git">676447190190d8546165e21be242cf16dd69f5ae</xit:fix>
+ </xit:testcase>
+ </xit:testsuite>
+ <xit:testsuite name="TouchGrabTestMultipleTaps">
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/0" success="true"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/1" success="true"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/2" success="true"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/3" success="true"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/4" success="true"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/5" success="false"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/6" success="false"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/7" success="false"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/8" success="false"/>
+ <xit:testcase name="PassiveGrabPointerEmulationMultipleTouchesFastSuccession/9" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/0" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/1" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/2" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/3" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/4" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/5" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/6" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/7" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/8" success="false"/>
+ <xit:testcase name="PassiveGrabPointerRelease/9" success="false"/>
+ </xit:testsuite>
+ <xit:testsuite name="TouchTest">
+ <xit:testcase name="TouchEventsButtonState" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="TouchTestXI2Version">
+ <xit:testcase name="EmulatedButton1MotionMaskOnTouch/0" success="true"/>
+ <xit:testcase name="EmulatedButton1MotionMaskOnTouch/1" success="true"/>
+ <xit:testcase name="EmulatedButton1MotionMaskOnTouch/2" success="true"/>
+ <xit:testcase name="EmulatedButton1MotionMaskOnTouch/3" success="true"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndCore/0" success="false"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndCore/1" success="false"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndCore/2" success="false"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndCore/3" success="false"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndXI2/0" success="true"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndXI2/1" success="true"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndXI2/2" success="true"/>
+ <xit:testcase name="EmulatedButtonMaskOnTouchBeginEndXI2/3" success="true"/>
+ <xit:testcase name="XIQueryPointerTouchscreen/0" success="true"/>
+ <xit:testcase name="XIQueryPointerTouchscreen/1" success="true"/>
+ <xit:testcase name="XIQueryPointerTouchscreen/2" success="true"/>
+ <xit:testcase name="XIQueryPointerTouchscreen/3" success="true"/>
+ <xit:testcase name="XITouchscreenPointerEmulation/0" success="true"/>
+ <xit:testcase name="XITouchscreenPointerEmulation/1" success="true"/>
+ <xit:testcase name="XITouchscreenPointerEmulation/2" success="true"/>
+ <xit:testcase name="XITouchscreenPointerEmulation/3" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="XIGrabButtonTest">
+ <xit:testcase name="GrabWindowTest" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="XISelectEventsTouchTest">
+ <xit:testcase name="TouchSelectionConflicts/0" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/1" success="false">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/2" success="false">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/3" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/4" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/5" success="false">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/6" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/7" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ <xit:testcase name="TouchSelectionConflicts/8" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=57301</xit:bug>
+ </xit:testcase>
+ </xit:testsuite>
+ <xit:testsuite name="XTest">
+ <xit:testcase name="DisabledDevicesCtl" success="true"/>
+ <xit:testcase name="DisabledDevicesProperty" success="true">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=56380</xit:bug>
+ <xit:fix type="git">aad65415bff12c6860c19beac42e4165e598a40f</xit:fix>
+ </xit:testcase>
+ </xit:testsuite>
+ <xit:testsuite name="XineramaTest">
+ <xit:testcase name="ScreenCrossing/0" success="true"/>
+ <xit:testcase name="ScreenCrossing/1" success="true"/>
+ </xit:testsuite>
+ <xit:testsuite name="ZaphodTest">
+ <xit:testcase name="ScreenCrossing/0" success="false">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=54654</xit:bug>
+ <xit:fix type="git">e7cd5cce740e653000fb1192b600268dcf77dde2</xit:fix>
+ </xit:testcase>
+ <xit:testcase name="ScreenCrossing/1" success="false">
+ <xit:bug type="bugzilla">https://bugs.freedesktop.org/show_bug.cgi?id=54654</xit:bug>
+ <xit:fix type="git">e7cd5cce740e653000fb1192b600268dcf77dde2</xit:fix>
+ </xit:testcase>
+ </xit:testsuite>
+ <xit:testsuite name="ZaphodTouchDeviceChangeTest">
+ <xit:testcase name="NoCursorJumpsOnTouchToPointerSwitch" success="false"/>
+ </xit:testsuite>
+ </xit:registry>
+</xit:registries>
+
diff --git a/registry/xit-bug-registry b/registry/xit-bug-registry
new file mode 100755
index 0000000..2767951
--- /dev/null
+++ b/registry/xit-bug-registry
@@ -0,0 +1,814 @@
+#!/usr/bin/env python
+#
+# This program is a bug registry to compare test results between different
+# runs of a test suite. It's prime purpose is to track known failures and
+# alarm if a test does not yield the result expected.
+#
+# Input files are either xml files created by this registry, or JUnit XML
+# files obtained from the test suite if --gtest_output="xml:filename.xml" is
+# set.
+#
+# Default input/output for registry files is stdin/stdout unless -f is
+# given. If -f is given, that file is both input and output file and is
+# modified in-place.
+#
+# Usage examples:
+# Create test registry from test results:
+# xit-bug-registry create some-results.xml > registry.xml
+# or
+# xit-bug-registry -f registry.xml create some-results.xml
+#
+# Verify test results against existing registry:
+# xit-bug-registry -f registry.xml verify some-results.xml
+#
+# Merge a new test registry into an existing one, adding new test
+# cases from second-registry but leaving the existing ones untouched:
+# xit-bug-registry -f registry.xml merge first-registry.xml second-registry.xml
+#
+# Print info about a test:
+# xit-bug-registry info MyTestSuite TestName < registry.xml
+#
+# Add information about a specific test case:
+# xit-bug-registry -f registry.xml edit MyTestSuite TestName add-bug http://bugs.freedesktop.org/1234556
+# xit-bug-registry -f registry.xml edit MyTestSuite TestName rm-bug http://bugs.freedesktop.org/1234556
+# xit-bug-registry -f registry.xml edit MyTestSuite TestName add-rpm xorg-x11-server-1.23-4.fc18.rpm
+# xit-bug-registry -f registry.xml edit MyTestSuite TestName rm-rpm xorg-x11-server-1.23-4.fc18.rpm
+# xit-bug-registry -f registry.xml edit MyTestSuite TestName add-commit 12354534deadbeef
+# xit-bug-registry -f registry.xml edit MyTestSuite TestName rm-commit 12354534deadbeef
+#
+
+import sys
+import os
+import time
+import argparse
+from lxml import objectify
+import lxml.etree
+import shutil
+
+def debug(msg):
+ print >> sys.stderr, msg
+
+XMLNS = "http://www.x.org/xorg-integration-testing"
+def xmlns_tag(tag, ns=XMLNS):
+ """Helper function to create a namespaced tag from the tag. Needed for
+ iterchildren() which only takes fully qualified tags, apparently"""
+ return "{" + ns + "}" + tag
+
+def str2bool(val):
+ if val in ["True", "true", "1"]:
+ return True
+ elif val in ["False", "false", "0"]:
+ return False
+ else:
+ raise ValueError
+
+class XITTestRegistry:
+ """Central class keeping a set of test cases and their results"""
+
+ def __init__(self, name="", test_cases = []):
+ """Initialise with a registry name and a list of """
+ self.tests = self._from_list(test_cases)
+ self.name = name
+ self.date = time.localtime()
+ self.moduleversions = {}
+
+ @classmethod
+ def fromXML(self, filename):
+ """Generate and returns a list of XITTestRegistry from the xml file at filename"""
+ registries = objectify.parse(filename).getroot()
+ regs = []
+ for registry in registries.iterchildren(tag=xmlns_tag("registry")):
+ reg = XITTestRegistry(name=registry.attrib["name"])
+
+ for meta in registry.iterchildren(tag=xmlns_tag("meta")):
+ date = meta.find(xmlns_tag("date"))
+ if date != None:
+ reg.date = time.strptime(date.text, "%Y-%m-%d")
+
+ for modversion in meta.iterchildren(tag=xmlns_tag("moduleversion")):
+ reg.moduleversions[modversion.attrib["name"]] = modversion.text
+
+ for suite in registry.iterchildren(tag=xmlns_tag("testsuite")):
+ for testcase in suite.iterchildren(tag=xmlns_tag("testcase")):
+ tcase = XITTest(suite.attrib["name"],
+ testcase.attrib["name"],
+ str2bool(testcase.attrib["success"]))
+ for bug in testcase.iterchildren(tag=xmlns_tag("bug")):
+ try:
+ type = bug.attrib["type"]
+ except KeyError:
+ type = "bugzilla"
+ tcase.addBug(XITBug(type, bug.text))
+
+ for info in testcase.iterchildren(tag=xmlns_tag("testinfo")):
+ try:
+ type = info.attrib["type"]
+ except KeyError:
+ type = "text"
+ tcase.addInfo(XITInfo.createFromType(type, info.text))
+
+ for fix in testcase.iterchildren(tag=xmlns_tag("fix")):
+ type = fix.attrib["type"]
+ tcase.addFix(XITFix.createFromType(type, fix.text))
+
+ reg.addTest(tcase)
+ regs.append(reg)
+ return regs
+
+ def toXML(self):
+ """Generate XML output from this registry and return it"""
+ NSMAP = { "xit" : "http://www.x.org/xorg-integration-testing" }
+ E = objectify.ElementMaker(annotate = False,
+ namespace = NSMAP['xit'],
+ nsmap = NSMAP)
+ xit_registries = E.registries()
+ xit_registry = E.registry()
+ xit_registry.set("name", self.name)
+ xit_registries.append(xit_registry)
+
+ xit_meta = E.meta()
+ xit_registry.append(xit_meta)
+
+ xit_date = E.date(time.strftime("%Y-%m-%d", self.date))
+ xit_meta.append(xit_date)
+
+ for module, version in sorted(self.moduleversions.iteritems()):
+ xit_modversion = E.moduleversion(version)
+ xit_modversion.set("name", module)
+ xit_modversion.set("type", "git") # FIXME
+ xit_meta.append(xit_modversion)
+
+ for suite_name, suite in sorted(self.tests.iteritems()):
+ xit_suite = E.testsuite()
+ xit_suite.set("name", suite_name)
+ for name, test in sorted(suite.iteritems()):
+ xit_testcase = E.testcase()
+ xit_testcase.set("name", test.name)
+ xit_testcase.set("success", str(test.status).lower())
+
+ for bug in test.getBugs():
+ xit_bug = E.bug(bug.url)
+ xit_bug.set("type", bug.type)
+ xit_testcase.append(xit_bug)
+
+ for fix in test.getFixes():
+ xit_fix = E.fix(fix.text)
+ xit_fix.set("type", fix.type)
+ xit_testcase.append(xit_fix)
+
+ for info in test.getInfo():
+ xit_info = E.testinfo(info.text)
+ xit_info.set("type", info.type)
+ xit_testcase.append(xit_info)
+
+ xit_suite.append(xit_testcase)
+ xit_registry.append(xit_suite)
+
+ lxml.etree.cleanup_namespaces(xit_registries)
+ return lxml.etree.tostring(xit_registries, pretty_print=True)
+
+ def _from_list(self, list_in):
+ """Convert a list of test cases into the internally used dict[suite name][test case name] = XITestCase"""
+ l = {}
+ for elem in list_in:
+ if not l.has_key(elem.suite):
+ l[elem.suite] = {}
+ l[elem.suite][elem.name] = elem
+ return l
+
+ def listTestNames(self):
+ """Return a list of tuples (suite name, test name, test status) for all tests """
+ tests = []
+ for suite_name, suite in sorted(self.tests.iteritems()):
+ for test_name, test in sorted(suite.iteritems()):
+ tests.append((suite_name, test_name, test.status))
+ return tests
+
+ def getTest(self, testsuite, testcase):
+ """Return the test named by suite name and test case name, or None if none exists"""
+ try:
+ return self.tests[testsuite][testcase]
+ except KeyError:
+ return None
+
+ def addTest(self, test):
+ """Add a test to this registry"""
+ if not self.tests.has_key(test.suite):
+ self.tests[test.suite] = {}
+ self.tests[test.suite][test.name] = test
+
+class XITTestCase:
+ """Represents one single test case, comprised of test suite name and test case name.
+ A test has a status (true on success, false on failure).
+ This class is merely a common parent class for XIT and JUnit test results"""
+
+ def __init__(self, suite, name, status = True):
+ self.suite = suite
+ self.name = name
+ self.status = status
+
+ @property
+ def status(self):
+ return self.status
+
+ @status.setter
+ def status(self, val):
+ if type(val) == str:
+ self.status = str2bool(val)
+ elif type(val) == bool:
+ self.status = val
+ else:
+ raise ValueError
+
+ def __cmp__(self, other):
+ if other == None:
+ return 1
+ rc = cmp(self.suite, other.suite)
+ if rc == 0:
+ rc = cmp(self.name, other.name)
+ return rc
+
+class XITTest(XITTestCase):
+ """Represents one single XIT test case, comprised of test suite name and test case name.
+ A test has a status (true on success, false on failure) and may have zero or more
+ bugs, fix or general info tags attached"""
+ def __init__(self, suite, name, success):
+ XITTestCase.__init__(self, suite, name, success)
+ self._bugs = [] # XITBug
+ self._info = [] # XITInfo
+ self._fixes = [] # XITFix
+
+ def addBug(self, bug):
+ """Add a reference to a known bug to this test case"""
+ self._bugs.append(bug)
+
+ def removeBug(self, bug):
+ """Remove a reference to a known bug to this test case"""
+ try:
+ self._bugs.remove(bug)
+ except ValueError:
+ pass
+
+ def addInfo(self, info):
+ """Add some general information about this test case"""
+ self._info.append(info)
+
+ def addFix(self, fix):
+ """Add fix information about this test case"""
+ self._fixes.append(fix)
+
+ def removeFix(self, fix):
+ """Add fix information about this test case"""
+ try:
+ self._fixes.remove(fix)
+ except ValueError:
+ pass
+
+ def getBugs(self):
+ """Return a list of all bug registered with this test case"""
+ return sorted(self._bugs)
+
+ def getInfo(self):
+ """Return a list of all general info pieces registered with this test case"""
+ return sorted(self._info)
+
+ def getFixes(self):
+ """Return a list of all fixes registered with this test case"""
+ return sorted(self._fixes)
+
+ def __str__(self):
+ s = self.suite + " " + self.name + ":\tExpected result: " + ("Success" if self.status else "Failure") + "\n"
+ if len(self._info):
+ s += "Extra info:\n"
+ i = 0
+ for info in self._info:
+ s += str(i) + ": " + str(info) + "\n"
+ i += 1
+ if len(self._bugs):
+ s += "Known bugs: \n"
+ i = 0
+ for bug in self._bugs:
+ s += str(i) + ": " + str(bug) + "\n"
+ i += 1
+ if len(self._fixes):
+ s += "Known fixes: \n"
+ i = 0
+ for fix in self._fixes:
+ s += str(i) + ": " + str(fix) + "\n"
+ i += 1
+ return s
+
+
+class XITBug:
+ """Represents a known bug related to a specific test case. Usually, this
+ will be a link to a bugzilla database"""
+
+ def __init__(self, bug_type="bugzilla", url=""):
+ self.type = bug_type
+ self.url = url
+
+ def __str__(self):
+ return self.url
+
+ def __eq__(self, other):
+ return self.type == other.type and self.url == other.url
+
+ def __cmp__(self, other):
+ rc = cmp(self.type, other.type)
+ if rc == 0:
+ rc = cmp(self.url, other.url)
+ return rc
+
+class XITFix:
+ """Represents a fix known to alter the outcome of a testcase"""
+ def __init__(self):
+ self.type = None
+ self.text = None
+
+ def __str__(self):
+ return self.text
+
+ def __eq__(self, other):
+ return self.type == other.type and self.text == other.text
+
+ def __cmp__(self, other):
+ rc = cmp(self.type, other.type)
+ if rc == 0:
+ rc = cmp(self.text, other.text)
+ return rc
+
+ @classmethod
+ def createFromType(self, type, text):
+ if type == "git":
+ return XITFixGit(text)
+ elif type == "rpm":
+ return XITFixRPM(text)
+ raise ValueError
+
+class XITFixGit(XITFix):
+ """Represents a git commit known to affect the outcome of a testcase"""
+ def __init__(self, sha1):
+ self.type = "git"
+ self.text = sha1
+
+ @property
+ def sha1(self):
+ return self.text
+
+ def __str__(self):
+ return self.sha1
+
+class XITFixRPM(XITFix):
+ """Represents a rpm package known to affect the outcome of a testcase"""
+ def __init__(self, rpm):
+ self.type = "rpm"
+ self.text = rpm
+
+ @property
+ def rpm(self):
+ return self.text
+
+ def __str__(self):
+ return self.sha1
+
+class XITInfo:
+ """Represents a piece of generic information about a bug."""
+ def __init__(self):
+ self.type = None
+ self.text = None
+
+ @classmethod
+ def createFromType(self, type, text, params={}):
+ """Create a XITInfo of the right type and return it"""
+ if type == "text":
+ return XITInfoText(text)
+ elif type == "url":
+ return XITInfoURL(text)
+ raise ValueError
+
+ def __cmp__(self, other):
+ rc = cmp(self.type, other.type)
+ if rc == 0:
+ rc = cmp(self.text, other.text)
+ return rc
+
+class XITInfoText(XITInfo):
+ """Represents a piece of generic textual information about a bug."""
+ def __init__(self, text):
+ self.text = text
+ self.type = "text"
+
+ def __str__(self):
+ return self.text
+
+class XITInfoURL(XITInfo):
+ """Represents a piece of generic information about a bug in the form of a URL."""
+ def __init__(self, url):
+ self.text = url
+ self.type = "url"
+
+ @property
+ def url(self):
+ return self.text
+
+ def __str__(self):
+ return self.url
+
+class JUnitTestResult(XITTestCase):
+ """Represents a JUnit test result in XML format, generated by --gtest_output="xml:filename.xml" """
+ def __init__(self, suite, name):
+ XITTestCase.__init__(self, suite, name, True)
+ self.failures = []
+
+ def addFailure(self, failure):
+ self.failures.append(failure)
+ self.status = False
+
+ def __str__(self):
+ s = self.suite + "." + self.name + ":\t"
+ if len(self.failures) == 0:
+ s += "success";
+ else:
+ s += "failure"
+ s += "\nFailures:\n"
+ for failure in self.failures:
+ s += str(failure) + "\n"
+ return s
+
+ @classmethod
+ def fromXML(self, filename):
+ """Return a list of JUnitTestResults based on the xml file given"""
+ testsuites = objectify.parse(filename).getroot()
+ results = []
+ for testsuite in testsuites.iterchildren(tag="testsuite"):
+ for testcase in testsuite.iterchildren(tag="testcase"):
+ result = JUnitTestResult(testsuite.attrib["name"], testcase.attrib["name"])
+ results.append(result)
+
+ for failure in testcase.iterchildren(tag="failure"):
+ result.addFailure(JUnitTestFailure(failure.attrib["message"]))
+ return results
+
+class JUnitTestFailure:
+ def __init__(self, message):
+ self.message = message
+
+ def __str__(self):
+ return self.message
+
+
+def list_tests(args):
+ """List all tests, showing test name and expected status"""
+ registry = load_registry(args)
+ all_tests = registry.listTestNames()
+ all_tests.insert(0, ("TestSuite", "TestCase", "Success"))
+ all_tests.insert(1, ("---------", "--------", "-------"))
+ for suite, test, status in all_tests:
+ print "{:<50}{:<50}{:>10}".format(suite, test, str(status))
+
+def show_test_info(args):
+ """Show all information about a given XIT registry test case"""
+ registry = load_registry(args)
+ test = registry.getTest(args.testsuite[0], args.testcase[0])
+ if test != None:
+ print str(test)
+
+def verify_one_result(test, result, format_str):
+ """Verify the test result given against the registry. Prints a status
+ message comprising full test name, expected outcome, actual outcome
+ and a grep-able bit to indicate which way the outcome differs"""
+
+ if test != None:
+ expected_status = str(test.status).lower()
+ if str(test.status).lower() != str(result.status).lower():
+ status_match = "XX"
+ else:
+ status_match = "++" if test.status else "--"
+ else:
+ expected_status = ""
+ status_match = "??"
+ print format_str.format(status_match, result.suite, result.name, str(result.status).lower(), expected_status)
+
+def verify_results(args):
+ """Verify a JUnit test result against the XIT test registry"""
+ registry = load_registry(args)
+ results = JUnitTestResult.fromXML(args.results)
+
+ sname_len = 0
+ tname_len = 0
+
+ sname_len = max([ len(x.suite) for x in results ]) + 1
+ tname_len = max([ len(x.name) for x in results ]) + 1
+
+ format_str = "{:<4}{:<%d}{:<%d}{:>10}{:>10}" % (sname_len, tname_len)
+ print format_str.format("Code", "TestSuite", "TestCase", "Result", "Expected")
+ print format_str.format("----", "---------", "--------", "------", "--------")
+
+ for result in sorted(results):
+ verify_one_result(registry.getTest(result.suite, result.name), result, format_str)
+
+def compare_registries(args):
+ reg1 = XITTestRegistry.fromXML(args.reg1[0])[0]
+ reg2 = XITTestRegistry.fromXML(args.reg2[0])[0]
+
+ sname_len = 0
+ tname_len = 0
+
+ sname_len = max([ len(x[0]) for x in reg1.listTestNames() ]) + 1
+ tname_len = max([ len(x[1]) for x in reg1.listTestNames() ]) + 1
+
+ format_str = "{:<4}{:<%d}{:<%d}{:>10}{:>10}" % (sname_len, tname_len)
+
+ print format_str.format("Code", "TestSuite", "TestCase", "Result", "Expected")
+ print format_str.format("----", "---------", "--------", "------", "--------")
+
+ for suite, test, status in sorted(reg1.listTestNames()):
+ verify_one_result(reg1.getTest(suite, test), reg2.getTest(suite, test), format_str)
+
+
+def create_registry(args):
+ """Create a new registry XML file based on the test cases in the JUnit file"""
+ results_list = JUnitTestResult.fromXML(args.results)
+
+ results_dict = {}
+ for r in results_list:
+ if not results_dict.has_key(r.suite):
+ results_dict[r.suite] = []
+ results_dict[r.suite].append(r)
+
+ if args.name:
+ reg_name = args.name[0]
+ else:
+ reg_name = os.path.basename(args.results).split(".xml")[0]
+
+ NSMAP = { "xit" : XMLNS }
+ E = objectify.ElementMaker(annotate = False,
+ namespace = NSMAP['xit'],
+ nsmap = NSMAP)
+ xit_registries = E.registries()
+ xit_registry = E.registry()
+ xit_registry.set("name", reg_name)
+ xit_registries.append(xit_registry)
+
+ for suite in results_dict.keys():
+ xit_suite = E.testsuite()
+ xit_suite.set("name", suite)
+ for r in results_dict[suite]:
+ xit_testcase = E.testcase()
+ xit_testcase.set("name", r.name)
+ xit_testcase.set("success", str(r.status).lower())
+ xit_suite.append(xit_testcase)
+ xit_registry.append(xit_suite)
+
+ lxml.etree.cleanup_namespaces(xit_registries)
+ r = open_new_registry(args)
+ write_to_registry(r, lxml.etree.tostring(xit_registries, pretty_print=True))
+ sync_registry(args, r)
+
+def merge_registries(args):
+ """Merge two registries together"""
+ if args.add:
+ merge_add_registries(args)
+
+def merge_add_registries(args):
+ """Merge registry args.reg2 into regs.arg1, leaving all existing information in reg1 untouched"""
+ reg1 = XITTestRegistry.fromXML(args.reg1[0])[0]
+ reg2 = XITTestRegistry.fromXML(args.reg2[0])[0]
+
+ # merge 2 into 1
+ tests2 = reg2.listTestNames()
+ for suite, name, status in tests2:
+ if reg1.getTest(suite, name) == None:
+ reg1.addTest(reg2.getTest(suite, name))
+
+ registry_from_string(args, reg1.toXML());
+
+def add_bug(args):
+ registry = load_registry(args)
+ testcase = registry.getTest(args.testsuite, args.testcase)
+
+ if testcase == None:
+ print >> sys.stderr, "Invalid test name '%s %s'" % (args.testsuite, args.testcase)
+ sys.exit(1)
+
+ testcase.addBug(XITBug("bugzilla", args.url))
+ registry_from_string(args, registry.toXML())
+
+def rm_bug(args):
+ registry = load_registry(args)
+ testcase = registry.getTest(args.testsuite, args.testcase)
+
+ if testcase == None:
+ print >> sys.stderr, "Invalid test name '%s %s'" % (args.testsuite, args.testcase)
+ sys.exit(1)
+
+ testcase.removeBug(XITBug("bugzilla", args.url))
+ registry_from_string(args, registry.toXML())
+
+def add_fix(args, type, text):
+ registry = load_registry(args)
+ testcase = registry.getTest(args.testsuite, args.testcase)
+
+ if testcase == None:
+ print >> sys.stderr, "Invalid test name '%s %s'" % (args.testsuite, args.testcase)
+ sys.exit(1)
+
+ testcase.addFix(XITFix.createFromType(type, text))
+ registry_from_string(args, registry.toXML())
+
+def add_commit(args):
+ add_fix(args, "git", args.sha1)
+
+def add_rpm(args):
+ add_fix(args, "rpm", args.rpm)
+
+def rm_fix(args, type, text):
+ registry = load_registry(args)
+ testcase = registry.getTest(args.testsuite, args.testcase)
+
+ if testcase == None:
+ print >> sys.stderr, "Invalid test name '%s %s'" % (args.testsuite, args.testcase)
+ sys.exit(1)
+
+ testcase.removeFix(XITFix.createFromType(type, text))
+ registry_from_string(args, registry.toXML())
+
+def rm_commit(args):
+ rm_fix(args, "git", args.sha1)
+
+def rm_rpm(args):
+ rm_fix(args, "rpm", args.rpm)
+
+def set_status(args):
+ registry = load_registry(args)
+ testcase = registry.getTest(args.testsuite, args.testcase)
+
+ if testcase == None:
+ print >> sys.stderr, "Invalid test name '%s %s'" % (args.testsuite, args.testcase)
+ sys.exit(1)
+
+ status = { "true" : True,
+ "false" : False,
+ "success" : True,
+ "failure" : False }
+
+ try:
+ testcase.status = status[args.status]
+ except KeyError:
+ print >> sys.stderr, "Invalid status code, allowed are %s" % ",".join(status.keys())
+ sys.exit(1)
+
+ registry_from_string(args, registry.toXML())
+
+def set_date(args):
+ registry = load_registry(args)
+ date = args.date
+ if date != None:
+ date = time.strptime(date, "%Y-%m-%d")
+ else:
+ date = time.localtime()
+
+ registry.date = date
+ registry_from_string(args, registry.toXML())
+
+def set_modversion(args):
+ registry = load_registry(args)
+ name = args.name
+ version = args.version
+ type = args.type if args.type else "git"
+
+ if type != "git":
+ print >> sys.stderr, "Type %s not supported" % type
+ sys.exit(1)
+
+ if version != "none":
+ registry.moduleversions[name] = version
+ else:
+ del registry.moduleversions[name]
+
+
+ registry_from_string(args, registry.toXML())
+
+def parse_cmdline():
+ parser = argparse.ArgumentParser(description = "Parse XIT test results for "
+ "known failures.\nRun XIT tests with the --gtest_output:xml and "
+ "compare the test result with the registry of known test "
+ "successes/failures.\n")
+ parser.add_argument("-f", "--file", help="file containing XIT test registry, modified in-place (default: stdin/stdout) ", action="store", required=False)
+ subparsers = parser.add_subparsers(title="Actions", help=None)
+
+ list_subparser = subparsers.add_parser("list", help="List all test cases")
+ list_subparser.set_defaults(func = list_tests)
+
+ info_subparser = subparsers.add_parser("info", help="Print info about a specific test case")
+ info_subparser.add_argument("testsuite", nargs=1, default=None, help="Test Suite name")
+ info_subparser.add_argument("testcase", nargs=1, default=None, help="Test Case name")
+ info_subparser.set_defaults(func = show_test_info)
+
+ verify_subparser = subparsers.add_parser("verify", help="Compare JUnit test results against the registry")
+ verify_subparser.add_argument("results", metavar="results.xml", help="The XML file containing test results")
+ verify_subparser.set_defaults(func = verify_results)
+
+ compare_subparser = subparsers.add_parser("compare", help="Compare two test registries")
+ compare_subparser.add_argument("reg1", metavar="registry1.xml", nargs=1, help="Registry file no 1")
+ compare_subparser.add_argument("reg2", metavar="registry2.xml", nargs=1, help="Registry file no 2")
+ compare_subparser.set_defaults(func = compare_registries)
+
+ import_subparser = subparsers.add_parser("create", help="Create new XIT registry from JUnit test results")
+ import_subparser.add_argument("results", metavar="results.xml", help="The XML file containing test results")
+ import_subparser.add_argument("--name", nargs=1, help="Human-readable name for registry (default: the filename)")
+ import_subparser.set_defaults(func = create_registry)
+
+ merge_subparser = subparsers.add_parser("merge", help="Merge two registries together")
+ merge_subparser.add_argument("reg1", metavar="registry1.xml", nargs=1, help="Registry file no 1")
+ merge_subparser.add_argument("reg2", metavar="registry2.xml", nargs=1, help="Registry file no 2")
+ merge_subparser.add_argument("--add", default=True, action="store_true", help="Merge new test cases from registry 2 into registry 1, leaving existing test cases unmodified")
+ merge_subparser.set_defaults(func = merge_registries)
+
+ edit_subparser = subparsers.add_parser("edit", help="Modify an entry in the registry")
+ edit_subparser.add_argument("testsuite", default=None, help="Test Suite name")
+ edit_subparser.add_argument("testcase", default=None, help="Test Case name")
+ edit_subparsers = edit_subparser.add_subparsers(title="Actions", help=None)
+
+ add_bug_subparser = edit_subparsers.add_parser("add-bug", help="Add link to bugzilla entry")
+ add_bug_subparser.add_argument("url", help="URL to bugzilla entry")
+ add_bug_subparser.set_defaults(func = add_bug)
+
+ rm_bug_subparser = edit_subparsers.add_parser("rm-bug", help="Remove link to bugzilla entry")
+ rm_bug_subparser.add_argument("url", help="URL to bugzilla entry")
+ rm_bug_subparser.set_defaults(func = rm_bug)
+
+ add_commit_subparser = edit_subparsers.add_parser("add-commit", help="Add git SHA1 of a commit that alters this test outcome")
+ add_commit_subparser.add_argument("sha1", help="git commit SHA1")
+ add_commit_subparser.set_defaults(func = add_commit)
+
+ rm_commit_subparser = edit_subparsers.add_parser("rm-commit", help="Remove git SHA1 of a commit listed in this test")
+ rm_commit_subparser.add_argument("sha1", help="git commit SHA1")
+ rm_commit_subparser.set_defaults(func = rm_commit)
+
+ add_rpm_subparser = edit_subparsers.add_parser("add-rpm", help="Add rpm package NVR that alters this test outcome")
+ add_rpm_subparser.add_argument("rpm", help="rpm package NVR")
+ add_rpm_subparser.set_defaults(func = add_rpm)
+
+ rm_rpm_subparser = edit_subparsers.add_parser("rm-rpm", help="Remove rpm package NVR listed in this test")
+ rm_rpm_subparser.add_argument("rpm", help="rpm package NVR")
+ rm_rpm_subparser.set_defaults(func = rm_rpm)
+
+ set_status_subparser = edit_subparsers.add_parser("set-status", help="Change the status of a bug")
+ set_status_subparser.add_argument("status", help="expected test status (true/false)")
+ set_status_subparser.set_defaults(func = set_status)
+
+ meta_subparser = subparsers.add_parser("meta", help="Modify the meta information in the registry")
+ meta_subparsers = meta_subparser.add_subparsers(title="Actions", help=None)
+
+ set_date_subparser = meta_subparsers.add_parser("set-date", help="Update the date")
+ set_date_subparser.add_argument("date", nargs="?", help="Updated date in the form of 2012-12-31 (default to today if missing)")
+ set_date_subparser.set_defaults(func = set_date)
+
+ modversion_subparser = meta_subparsers.add_parser("set-module-version", help="Change a module version")
+ modversion_subparser.add_argument("name", help="Name of the module")
+ modversion_subparser.add_argument("version", help="Version string, use 'none' to remove an entry")
+ modversion_subparser.add_argument("--type", help="Version type, e.g. git, rpm");
+ modversion_subparser.set_defaults(func = set_modversion)
+
+ return parser
+
+def load_registry(args):
+ if args.file == None:
+ print >> sys.stderr, "Reading from stdin"
+ args.file = sys.stdin
+ registries = XITTestRegistry.fromXML(args.file)
+ if len(registries) > 1:
+ print >> sys.stderr, "More than one registry found in input file, this is not supported yet. Using first one only."
+ elif len(registries) == 0:
+ print >> sys.stderr, "Failed to parse input file."
+ sys.exit(1)
+
+ return registries[0]
+
+def write_to_registry(f, msg):
+ print >> f, msg
+
+def open_new_registry(args):
+ if args.file == None or args.file == sys.stdin:
+ return sys.stdout
+ else:
+ return os.tmpfile()
+
+def sync_registry(args, outfile):
+ if outfile == sys.stdout:
+ return
+
+ outfile.seek(0)
+ f = open(args.file, "w")
+ shutil.copyfileobj(outfile, f)
+
+def registry_from_string(args, s):
+ r = open_new_registry(args)
+ write_to_registry(r, s)
+ sync_registry(args, r)
+
+parser = parse_cmdline()
+args = parser.parse_args()
+
+args.func(args)