test: Output results for cockpit's log.html
Cockpit's log.html has a peculiar format, but parsing logs in html is much easier, because it shows expandable sections for each test.
This commit is contained in:
parent
e947e01331
commit
f810924042
@ -4,6 +4,7 @@ import argparse
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# import Cockpit's machinery for test VMs and its browser test API
|
# import Cockpit's machinery for test VMs and its browser test API
|
||||||
@ -12,8 +13,6 @@ import testvm # pylint: disable=import-error
|
|||||||
|
|
||||||
|
|
||||||
def print_exception(etype, value, tb):
|
def print_exception(etype, value, tb):
|
||||||
import traceback
|
|
||||||
|
|
||||||
# only include relevant lines
|
# only include relevant lines
|
||||||
limit = 0
|
limit = 0
|
||||||
while tb and '__unittest' in tb.tb_frame.f_globals:
|
while tb and '__unittest' in tb.tb_frame.f_globals:
|
||||||
@ -31,7 +30,7 @@ class ComposerTestCase(unittest.TestCase):
|
|||||||
self.network = testvm.VirtNetwork(0)
|
self.network = testvm.VirtNetwork(0)
|
||||||
self.machine = testvm.VirtMachine(self.image, networking=self.network.host(), memory_mb=2048)
|
self.machine = testvm.VirtMachine(self.image, networking=self.network.host(), memory_mb=2048)
|
||||||
|
|
||||||
print(f"Starting virtual machine '{self.image}'")
|
print("Starting virtual machine '{}'".format(self.image))
|
||||||
self.machine.start()
|
self.machine.start()
|
||||||
self.machine.wait_boot()
|
self.machine.wait_boot()
|
||||||
|
|
||||||
@ -89,13 +88,78 @@ class ComposerTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(r.returncode, 0)
|
self.assertEqual(r.returncode, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class ComposerTestResult(unittest.TestResult):
|
||||||
|
def name(self, test):
|
||||||
|
name = test.id().replace("__main__.", "")
|
||||||
|
if test.shortDescription():
|
||||||
|
name += ": " + test.shortDescription()
|
||||||
|
return name
|
||||||
|
|
||||||
|
def startTest(self, test):
|
||||||
|
super().startTest(test)
|
||||||
|
|
||||||
|
print("# ----------------------------------------------------------------------")
|
||||||
|
print("# ", self.name(test))
|
||||||
|
print("", flush=True)
|
||||||
|
|
||||||
|
def stopTest(self, test):
|
||||||
|
print(flush=True)
|
||||||
|
|
||||||
|
def addSuccess(self, test):
|
||||||
|
super().addSuccess(test)
|
||||||
|
print("ok {} {}".format(self.testsRun, self.name(test)))
|
||||||
|
|
||||||
|
def addError(self, test, err):
|
||||||
|
super().addError(test, err)
|
||||||
|
traceback.print_exception(*err, file=sys.stdout)
|
||||||
|
print("not ok {} {}".format(self.testsRun, self.name(test)))
|
||||||
|
|
||||||
|
def addFailure(self, test, err):
|
||||||
|
super().addError(test, err)
|
||||||
|
traceback.print_exception(*err, file=sys.stdout)
|
||||||
|
print("not ok {} {}".format(self.testsRun, self.name(test)))
|
||||||
|
|
||||||
|
def addSkip(self, test, reason):
|
||||||
|
super().addSkip(test, reason)
|
||||||
|
print("ok {} {} # SKIP {}".format(self.testsRun, self.name(test), reason))
|
||||||
|
|
||||||
|
def addExpectedFailure(self, test, err):
|
||||||
|
super().addExpectedFailure(test, err)
|
||||||
|
print("ok {} {}".format(self.testsRun, self.name(test)))
|
||||||
|
|
||||||
|
def addUnexpectedSuccess(self, test):
|
||||||
|
super().addUnexpectedSuccess(test)
|
||||||
|
print("not ok {} {}".format(self.testsRun, self.name(test)))
|
||||||
|
|
||||||
|
|
||||||
|
class ComposerTestRunner(object):
|
||||||
|
"""A test runner that (in combination with ComposerTestResult) outputs
|
||||||
|
results in a way that cockpit's log.html can read and format them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, failfast=False):
|
||||||
|
self.failfast = failfast
|
||||||
|
|
||||||
|
def run(self, testable):
|
||||||
|
result = ComposerTestResult()
|
||||||
|
result.failfast = self.failfast
|
||||||
|
result.startTestRun()
|
||||||
|
count = testable.countTestCases()
|
||||||
|
print("1.." + str(count))
|
||||||
|
try:
|
||||||
|
testable(result)
|
||||||
|
finally:
|
||||||
|
result.stopTestRun()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def print_tests(tests):
|
def print_tests(tests):
|
||||||
for test in tests:
|
for test in tests:
|
||||||
if isinstance(test, unittest.TestSuite):
|
if isinstance(test, unittest.TestSuite):
|
||||||
print_tests(test)
|
print_tests(test)
|
||||||
elif isinstance(test, unittest.loader._FailedTest):
|
elif isinstance(test, unittest.loader._FailedTest):
|
||||||
name = test.id().replace("unittest.loader._FailedTest.", "")
|
name = test.id().replace("unittest.loader._FailedTest.", "")
|
||||||
print(f"Error: '{name}' does not match a test", file=sys.stderr)
|
print("Error: '{}' does not match a test".format(name), file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
print(test.id().replace("__main__.", ""))
|
print(test.id().replace("__main__.", ""))
|
||||||
|
|
||||||
@ -119,7 +183,7 @@ def main():
|
|||||||
print_tests(tests)
|
print_tests(tests)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
runner = unittest.TextTestRunner(verbosity=2, failfast=args.sit)
|
runner = ComposerTestRunner(failfast=args.sit)
|
||||||
result = runner.run(tests)
|
result = runner.run(tests)
|
||||||
|
|
||||||
sys.exit(not result.wasSuccessful())
|
sys.exit(not result.wasSuccessful())
|
||||||
|
Loading…
Reference in New Issue
Block a user