xref: /aosp_15_r20/external/antlr/runtime/Python/xmlrunner.py (revision 16467b971bd3e2009fad32dd79016f2c7e421deb)
1 *16467b97STreehugger Robot"""
2 *16467b97STreehugger RobotXML Test Runner for PyUnit
3 *16467b97STreehugger Robot"""
4 *16467b97STreehugger Robot
5 *16467b97STreehugger Robot# Written by Sebastian Rittau <srittau@jroger.in-berlin.de> and placed in
6 *16467b97STreehugger Robot# the Public Domain. With contributions by Paolo Borelli.
7 *16467b97STreehugger Robot
8 *16467b97STreehugger Robot__revision__ = "$Id: /private/python/stdlib/xmlrunner.py 16654 2007-11-12T12:46:35.368945Z srittau  $"
9 *16467b97STreehugger Robot
10 *16467b97STreehugger Robotimport os.path
11 *16467b97STreehugger Robotimport re
12 *16467b97STreehugger Robotimport sys
13 *16467b97STreehugger Robotimport time
14 *16467b97STreehugger Robotimport traceback
15 *16467b97STreehugger Robotimport unittest
16 *16467b97STreehugger Robotfrom StringIO import StringIO
17 *16467b97STreehugger Robotfrom xml.sax.saxutils import escape
18 *16467b97STreehugger Robot
19 *16467b97STreehugger Robotfrom StringIO import StringIO
20 *16467b97STreehugger Robot
21 *16467b97STreehugger Robot
22 *16467b97STreehugger Robotclass _TestInfo(object):
23 *16467b97STreehugger Robot
24 *16467b97STreehugger Robot    """Information about a particular test.
25 *16467b97STreehugger Robot
26 *16467b97STreehugger Robot    Used by _XMLTestResult.
27 *16467b97STreehugger Robot
28 *16467b97STreehugger Robot    """
29 *16467b97STreehugger Robot
30 *16467b97STreehugger Robot    def __init__(self, test, time):
31 *16467b97STreehugger Robot        (self._class, self._method) = test.id().rsplit(".", 1)
32 *16467b97STreehugger Robot        self._time = time
33 *16467b97STreehugger Robot        self._error = None
34 *16467b97STreehugger Robot        self._failure = None
35 *16467b97STreehugger Robot
36 *16467b97STreehugger Robot    @staticmethod
37 *16467b97STreehugger Robot    def create_success(test, time):
38 *16467b97STreehugger Robot        """Create a _TestInfo instance for a successful test."""
39 *16467b97STreehugger Robot        return _TestInfo(test, time)
40 *16467b97STreehugger Robot
41 *16467b97STreehugger Robot    @staticmethod
42 *16467b97STreehugger Robot    def create_failure(test, time, failure):
43 *16467b97STreehugger Robot        """Create a _TestInfo instance for a failed test."""
44 *16467b97STreehugger Robot        info = _TestInfo(test, time)
45 *16467b97STreehugger Robot        info._failure = failure
46 *16467b97STreehugger Robot        return info
47 *16467b97STreehugger Robot
48 *16467b97STreehugger Robot    @staticmethod
49 *16467b97STreehugger Robot    def create_error(test, time, error):
50 *16467b97STreehugger Robot        """Create a _TestInfo instance for an erroneous test."""
51 *16467b97STreehugger Robot        info = _TestInfo(test, time)
52 *16467b97STreehugger Robot        info._error = error
53 *16467b97STreehugger Robot        return info
54 *16467b97STreehugger Robot
55 *16467b97STreehugger Robot    def print_report(self, stream):
56 *16467b97STreehugger Robot        """Print information about this test case in XML format to the
57 *16467b97STreehugger Robot        supplied stream.
58 *16467b97STreehugger Robot
59 *16467b97STreehugger Robot        """
60 *16467b97STreehugger Robot        stream.write('  <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \
61 *16467b97STreehugger Robot            {
62 *16467b97STreehugger Robot                "class": self._class,
63 *16467b97STreehugger Robot                "method": self._method,
64 *16467b97STreehugger Robot                "time": self._time,
65 *16467b97STreehugger Robot            })
66 *16467b97STreehugger Robot        if self._failure != None:
67 *16467b97STreehugger Robot            self._print_error(stream, 'failure', self._failure)
68 *16467b97STreehugger Robot        if self._error != None:
69 *16467b97STreehugger Robot            self._print_error(stream, 'error', self._error)
70 *16467b97STreehugger Robot        stream.write('</testcase>\n')
71 *16467b97STreehugger Robot
72 *16467b97STreehugger Robot    def _print_error(self, stream, tagname, error):
73 *16467b97STreehugger Robot        """Print information from a failure or error to the supplied stream."""
74 *16467b97STreehugger Robot        text = escape(str(error[1]))
75 *16467b97STreehugger Robot        stream.write('\n')
76 *16467b97STreehugger Robot        stream.write('    <%s type="%s">%s\n' \
77 *16467b97STreehugger Robot            % (tagname, str(error[0]), text))
78 *16467b97STreehugger Robot        tb_stream = StringIO()
79 *16467b97STreehugger Robot        traceback.print_tb(error[2], None, tb_stream)
80 *16467b97STreehugger Robot        stream.write(escape(tb_stream.getvalue()))
81 *16467b97STreehugger Robot        stream.write('    </%s>\n' % tagname)
82 *16467b97STreehugger Robot        stream.write('  ')
83 *16467b97STreehugger Robot
84 *16467b97STreehugger Robot
85 *16467b97STreehugger Robotclass _XMLTestResult(unittest.TestResult):
86 *16467b97STreehugger Robot
87 *16467b97STreehugger Robot    """A test result class that stores result as XML.
88 *16467b97STreehugger Robot
89 *16467b97STreehugger Robot    Used by XMLTestRunner.
90 *16467b97STreehugger Robot
91 *16467b97STreehugger Robot    """
92 *16467b97STreehugger Robot
93 *16467b97STreehugger Robot    def __init__(self, classname):
94 *16467b97STreehugger Robot        unittest.TestResult.__init__(self)
95 *16467b97STreehugger Robot        self._test_name = classname
96 *16467b97STreehugger Robot        self._start_time = None
97 *16467b97STreehugger Robot        self._tests = []
98 *16467b97STreehugger Robot        self._error = None
99 *16467b97STreehugger Robot        self._failure = None
100 *16467b97STreehugger Robot
101 *16467b97STreehugger Robot    def startTest(self, test):
102 *16467b97STreehugger Robot        unittest.TestResult.startTest(self, test)
103 *16467b97STreehugger Robot        self._error = None
104 *16467b97STreehugger Robot        self._failure = None
105 *16467b97STreehugger Robot        self._start_time = time.time()
106 *16467b97STreehugger Robot
107 *16467b97STreehugger Robot    def stopTest(self, test):
108 *16467b97STreehugger Robot        time_taken = time.time() - self._start_time
109 *16467b97STreehugger Robot        unittest.TestResult.stopTest(self, test)
110 *16467b97STreehugger Robot        if self._error:
111 *16467b97STreehugger Robot            info = _TestInfo.create_error(test, time_taken, self._error)
112 *16467b97STreehugger Robot        elif self._failure:
113 *16467b97STreehugger Robot            info = _TestInfo.create_failure(test, time_taken, self._failure)
114 *16467b97STreehugger Robot        else:
115 *16467b97STreehugger Robot            info = _TestInfo.create_success(test, time_taken)
116 *16467b97STreehugger Robot        self._tests.append(info)
117 *16467b97STreehugger Robot
118 *16467b97STreehugger Robot    def addError(self, test, err):
119 *16467b97STreehugger Robot        unittest.TestResult.addError(self, test, err)
120 *16467b97STreehugger Robot        self._error = err
121 *16467b97STreehugger Robot
122 *16467b97STreehugger Robot    def addFailure(self, test, err):
123 *16467b97STreehugger Robot        unittest.TestResult.addFailure(self, test, err)
124 *16467b97STreehugger Robot        self._failure = err
125 *16467b97STreehugger Robot
126 *16467b97STreehugger Robot    def print_report(self, stream, time_taken, out, err):
127 *16467b97STreehugger Robot        """Prints the XML report to the supplied stream.
128 *16467b97STreehugger Robot
129 *16467b97STreehugger Robot        The time the tests took to perform as well as the captured standard
130 *16467b97STreehugger Robot        output and standard error streams must be passed in.a
131 *16467b97STreehugger Robot
132 *16467b97STreehugger Robot        """
133 *16467b97STreehugger Robot        stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \
134 *16467b97STreehugger Robot            { "e": len(self.errors), "f": len(self.failures) })
135 *16467b97STreehugger Robot        stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \
136 *16467b97STreehugger Robot            {
137 *16467b97STreehugger Robot                "n": self._test_name,
138 *16467b97STreehugger Robot                "t": self.testsRun,
139 *16467b97STreehugger Robot                "time": time_taken,
140 *16467b97STreehugger Robot            })
141 *16467b97STreehugger Robot        for info in self._tests:
142 *16467b97STreehugger Robot            info.print_report(stream)
143 *16467b97STreehugger Robot        stream.write('  <system-out><![CDATA[%s]]></system-out>\n' % out)
144 *16467b97STreehugger Robot        stream.write('  <system-err><![CDATA[%s]]></system-err>\n' % err)
145 *16467b97STreehugger Robot        stream.write('</testsuite>\n')
146 *16467b97STreehugger Robot
147 *16467b97STreehugger Robot
148 *16467b97STreehugger Robotclass XMLTestRunner(object):
149 *16467b97STreehugger Robot
150 *16467b97STreehugger Robot    """A test runner that stores results in XML format compatible with JUnit.
151 *16467b97STreehugger Robot
152 *16467b97STreehugger Robot    XMLTestRunner(stream=None) -> XML test runner
153 *16467b97STreehugger Robot
154 *16467b97STreehugger Robot    The XML file is written to the supplied stream. If stream is None, the
155 *16467b97STreehugger Robot    results are stored in a file called TEST-<module>.<class>.xml in the
156 *16467b97STreehugger Robot    current working directory (if not overridden with the path property),
157 *16467b97STreehugger Robot    where <module> and <class> are the module and class name of the test class.
158 *16467b97STreehugger Robot
159 *16467b97STreehugger Robot    """
160 *16467b97STreehugger Robot
161 *16467b97STreehugger Robot    def __init__(self, stream=None):
162 *16467b97STreehugger Robot        self._stream = stream
163 *16467b97STreehugger Robot        self._path = "."
164 *16467b97STreehugger Robot
165 *16467b97STreehugger Robot    def run(self, test):
166 *16467b97STreehugger Robot        """Run the given test case or test suite."""
167 *16467b97STreehugger Robot        class_ = test.__class__
168 *16467b97STreehugger Robot        classname = class_.__module__ + "." + class_.__name__
169 *16467b97STreehugger Robot        if self._stream == None:
170 *16467b97STreehugger Robot            filename = "TEST-%s.xml" % classname
171 *16467b97STreehugger Robot            stream = file(os.path.join(self._path, filename), "w")
172 *16467b97STreehugger Robot            stream.write('<?xml version="1.0" encoding="utf-8"?>\n')
173 *16467b97STreehugger Robot        else:
174 *16467b97STreehugger Robot            stream = self._stream
175 *16467b97STreehugger Robot
176 *16467b97STreehugger Robot        result = _XMLTestResult(classname)
177 *16467b97STreehugger Robot        start_time = time.time()
178 *16467b97STreehugger Robot
179 *16467b97STreehugger Robot        # TODO: Python 2.5: Use the with statement
180 *16467b97STreehugger Robot        old_stdout = sys.stdout
181 *16467b97STreehugger Robot        old_stderr = sys.stderr
182 *16467b97STreehugger Robot        sys.stdout = StringIO()
183 *16467b97STreehugger Robot        sys.stderr = StringIO()
184 *16467b97STreehugger Robot
185 *16467b97STreehugger Robot        try:
186 *16467b97STreehugger Robot            test(result)
187 *16467b97STreehugger Robot            try:
188 *16467b97STreehugger Robot                out_s = sys.stdout.getvalue()
189 *16467b97STreehugger Robot            except AttributeError:
190 *16467b97STreehugger Robot                out_s = ""
191 *16467b97STreehugger Robot            try:
192 *16467b97STreehugger Robot                err_s = sys.stderr.getvalue()
193 *16467b97STreehugger Robot            except AttributeError:
194 *16467b97STreehugger Robot                err_s = ""
195 *16467b97STreehugger Robot        finally:
196 *16467b97STreehugger Robot            sys.stdout = old_stdout
197 *16467b97STreehugger Robot            sys.stderr = old_stderr
198 *16467b97STreehugger Robot
199 *16467b97STreehugger Robot        time_taken = time.time() - start_time
200 *16467b97STreehugger Robot        result.print_report(stream, time_taken, out_s, err_s)
201 *16467b97STreehugger Robot        if self._stream == None:
202 *16467b97STreehugger Robot            stream.close()
203 *16467b97STreehugger Robot
204 *16467b97STreehugger Robot        return result
205 *16467b97STreehugger Robot
206 *16467b97STreehugger Robot    def _set_path(self, path):
207 *16467b97STreehugger Robot        self._path = path
208 *16467b97STreehugger Robot
209 *16467b97STreehugger Robot    path = property(lambda self: self._path, _set_path, None,
210 *16467b97STreehugger Robot            """The path where the XML files are stored.
211 *16467b97STreehugger Robot
212 *16467b97STreehugger Robot            This property is ignored when the XML file is written to a file
213 *16467b97STreehugger Robot            stream.""")
214 *16467b97STreehugger Robot
215 *16467b97STreehugger Robot
216 *16467b97STreehugger Robotclass XMLTestRunnerTest(unittest.TestCase):
217 *16467b97STreehugger Robot    def setUp(self):
218 *16467b97STreehugger Robot        self._stream = StringIO()
219 *16467b97STreehugger Robot
220 *16467b97STreehugger Robot    def _try_test_run(self, test_class, expected):
221 *16467b97STreehugger Robot
222 *16467b97STreehugger Robot        """Run the test suite against the supplied test class and compare the
223 *16467b97STreehugger Robot        XML result against the expected XML string. Fail if the expected
224 *16467b97STreehugger Robot        string doesn't match the actual string. All time attribute in the
225 *16467b97STreehugger Robot        expected string should have the value "0.000". All error and failure
226 *16467b97STreehugger Robot        messages are reduced to "Foobar".
227 *16467b97STreehugger Robot
228 *16467b97STreehugger Robot        """
229 *16467b97STreehugger Robot
230 *16467b97STreehugger Robot        runner = XMLTestRunner(self._stream)
231 *16467b97STreehugger Robot        runner.run(unittest.makeSuite(test_class))
232 *16467b97STreehugger Robot
233 *16467b97STreehugger Robot        got = self._stream.getvalue()
234 *16467b97STreehugger Robot        # Replace all time="X.YYY" attributes by time="0.000" to enable a
235 *16467b97STreehugger Robot        # simple string comparison.
236 *16467b97STreehugger Robot        got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got)
237 *16467b97STreehugger Robot        # Likewise, replace all failure and error messages by a simple "Foobar"
238 *16467b97STreehugger Robot        # string.
239 *16467b97STreehugger Robot        got = re.sub(r'(?s)<failure (.*?)>.*?</failure>', r'<failure \1>Foobar</failure>', got)
240 *16467b97STreehugger Robot        got = re.sub(r'(?s)<error (.*?)>.*?</error>', r'<error \1>Foobar</error>', got)
241 *16467b97STreehugger Robot
242 *16467b97STreehugger Robot        self.assertEqual(expected, got)
243 *16467b97STreehugger Robot
244 *16467b97STreehugger Robot    def test_no_tests(self):
245 *16467b97STreehugger Robot        """Regression test: Check whether a test run without any tests
246 *16467b97STreehugger Robot        matches a previous run.
247 *16467b97STreehugger Robot
248 *16467b97STreehugger Robot        """
249 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
250 *16467b97STreehugger Robot            pass
251 *16467b97STreehugger Robot        self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000">
252 *16467b97STreehugger Robot  <system-out><![CDATA[]]></system-out>
253 *16467b97STreehugger Robot  <system-err><![CDATA[]]></system-err>
254 *16467b97STreehugger Robot</testsuite>
255 *16467b97STreehugger Robot""")
256 *16467b97STreehugger Robot
257 *16467b97STreehugger Robot    def test_success(self):
258 *16467b97STreehugger Robot        """Regression test: Check whether a test run with a successful test
259 *16467b97STreehugger Robot        matches a previous run.
260 *16467b97STreehugger Robot
261 *16467b97STreehugger Robot        """
262 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
263 *16467b97STreehugger Robot            def test_foo(self):
264 *16467b97STreehugger Robot                pass
265 *16467b97STreehugger Robot        self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
266 *16467b97STreehugger Robot  <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
267 *16467b97STreehugger Robot  <system-out><![CDATA[]]></system-out>
268 *16467b97STreehugger Robot  <system-err><![CDATA[]]></system-err>
269 *16467b97STreehugger Robot</testsuite>
270 *16467b97STreehugger Robot""")
271 *16467b97STreehugger Robot
272 *16467b97STreehugger Robot    def test_failure(self):
273 *16467b97STreehugger Robot        """Regression test: Check whether a test run with a failing test
274 *16467b97STreehugger Robot        matches a previous run.
275 *16467b97STreehugger Robot
276 *16467b97STreehugger Robot        """
277 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
278 *16467b97STreehugger Robot            def test_foo(self):
279 *16467b97STreehugger Robot                self.assert_(False)
280 *16467b97STreehugger Robot        self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000">
281 *16467b97STreehugger Robot  <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
282 *16467b97STreehugger Robot    <failure type="exceptions.AssertionError">Foobar</failure>
283 *16467b97STreehugger Robot  </testcase>
284 *16467b97STreehugger Robot  <system-out><![CDATA[]]></system-out>
285 *16467b97STreehugger Robot  <system-err><![CDATA[]]></system-err>
286 *16467b97STreehugger Robot</testsuite>
287 *16467b97STreehugger Robot""")
288 *16467b97STreehugger Robot
289 *16467b97STreehugger Robot    def test_error(self):
290 *16467b97STreehugger Robot        """Regression test: Check whether a test run with a erroneous test
291 *16467b97STreehugger Robot        matches a previous run.
292 *16467b97STreehugger Robot
293 *16467b97STreehugger Robot        """
294 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
295 *16467b97STreehugger Robot            def test_foo(self):
296 *16467b97STreehugger Robot                raise IndexError()
297 *16467b97STreehugger Robot        self._try_test_run(TestTest, """<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
298 *16467b97STreehugger Robot  <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
299 *16467b97STreehugger Robot    <error type="exceptions.IndexError">Foobar</error>
300 *16467b97STreehugger Robot  </testcase>
301 *16467b97STreehugger Robot  <system-out><![CDATA[]]></system-out>
302 *16467b97STreehugger Robot  <system-err><![CDATA[]]></system-err>
303 *16467b97STreehugger Robot</testsuite>
304 *16467b97STreehugger Robot""")
305 *16467b97STreehugger Robot
306 *16467b97STreehugger Robot    def test_stdout_capture(self):
307 *16467b97STreehugger Robot        """Regression test: Check whether a test run with output to stdout
308 *16467b97STreehugger Robot        matches a previous run.
309 *16467b97STreehugger Robot
310 *16467b97STreehugger Robot        """
311 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
312 *16467b97STreehugger Robot            def test_foo(self):
313 *16467b97STreehugger Robot                print "Test"
314 *16467b97STreehugger Robot        self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
315 *16467b97STreehugger Robot  <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
316 *16467b97STreehugger Robot  <system-out><![CDATA[Test
317 *16467b97STreehugger Robot]]></system-out>
318 *16467b97STreehugger Robot  <system-err><![CDATA[]]></system-err>
319 *16467b97STreehugger Robot</testsuite>
320 *16467b97STreehugger Robot""")
321 *16467b97STreehugger Robot
322 *16467b97STreehugger Robot    def test_stderr_capture(self):
323 *16467b97STreehugger Robot        """Regression test: Check whether a test run with output to stderr
324 *16467b97STreehugger Robot        matches a previous run.
325 *16467b97STreehugger Robot
326 *16467b97STreehugger Robot        """
327 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
328 *16467b97STreehugger Robot            def test_foo(self):
329 *16467b97STreehugger Robot                print >>sys.stderr, "Test"
330 *16467b97STreehugger Robot        self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
331 *16467b97STreehugger Robot  <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
332 *16467b97STreehugger Robot  <system-out><![CDATA[]]></system-out>
333 *16467b97STreehugger Robot  <system-err><![CDATA[Test
334 *16467b97STreehugger Robot]]></system-err>
335 *16467b97STreehugger Robot</testsuite>
336 *16467b97STreehugger Robot""")
337 *16467b97STreehugger Robot
338 *16467b97STreehugger Robot    class NullStream(object):
339 *16467b97STreehugger Robot        """A file-like object that discards everything written to it."""
340 *16467b97STreehugger Robot        def write(self, buffer):
341 *16467b97STreehugger Robot            pass
342 *16467b97STreehugger Robot
343 *16467b97STreehugger Robot    def test_unittests_changing_stdout(self):
344 *16467b97STreehugger Robot        """Check whether the XMLTestRunner recovers gracefully from unit tests
345 *16467b97STreehugger Robot        that change stdout, but don't change it back properly.
346 *16467b97STreehugger Robot
347 *16467b97STreehugger Robot        """
348 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
349 *16467b97STreehugger Robot            def test_foo(self):
350 *16467b97STreehugger Robot                sys.stdout = XMLTestRunnerTest.NullStream()
351 *16467b97STreehugger Robot
352 *16467b97STreehugger Robot        runner = XMLTestRunner(self._stream)
353 *16467b97STreehugger Robot        runner.run(unittest.makeSuite(TestTest))
354 *16467b97STreehugger Robot
355 *16467b97STreehugger Robot    def test_unittests_changing_stderr(self):
356 *16467b97STreehugger Robot        """Check whether the XMLTestRunner recovers gracefully from unit tests
357 *16467b97STreehugger Robot        that change stderr, but don't change it back properly.
358 *16467b97STreehugger Robot
359 *16467b97STreehugger Robot        """
360 *16467b97STreehugger Robot        class TestTest(unittest.TestCase):
361 *16467b97STreehugger Robot            def test_foo(self):
362 *16467b97STreehugger Robot                sys.stderr = XMLTestRunnerTest.NullStream()
363 *16467b97STreehugger Robot
364 *16467b97STreehugger Robot        runner = XMLTestRunner(self._stream)
365 *16467b97STreehugger Robot        runner.run(unittest.makeSuite(TestTest))
366 *16467b97STreehugger Robot
367 *16467b97STreehugger Robot
368 *16467b97STreehugger Robotclass XMLTestProgram(unittest.TestProgram):
369 *16467b97STreehugger Robot    def runTests(self):
370 *16467b97STreehugger Robot        if self.testRunner is None:
371 *16467b97STreehugger Robot            self.testRunner = XMLTestRunner()
372 *16467b97STreehugger Robot        unittest.TestProgram.runTests(self)
373 *16467b97STreehugger Robot
374 *16467b97STreehugger Robotmain = XMLTestProgram
375 *16467b97STreehugger Robot
376 *16467b97STreehugger Robot
377 *16467b97STreehugger Robotif __name__ == "__main__":
378 *16467b97STreehugger Robot    main(module=None)
379