diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2012-11-27 16:19:38 +1000 |
---|---|---|
committer | Peter Hutterer <peter.hutterer@who-t.net> | 2012-11-27 16:19:38 +1000 |
commit | 86db7b3750c4f9adeb310dca39b7623ce6f3b964 (patch) | |
tree | 41a37b3627c3809964c162fc1649656422b808ee | |
parent | 1aa9715df511d0d7b4b67240f114df59393c9be1 (diff) | |
parent | 4f40e03ac3b01ac6e096113304216df571c24d8a (diff) |
Merge branch 'bug-registry'
-rw-r--r-- | HACKING | 27 | ||||
-rw-r--r-- | registry/example.xml | 35 | ||||
-rw-r--r-- | registry/server-registry.xml | 210 | ||||
-rwxr-xr-x | registry/xit-bug-registry | 814 |
4 files changed, 1086 insertions, 0 deletions
@@ -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) |