Merge pull request #110 from RobinD42/use-pytest

Use pytest for running unittests
This commit is contained in:
Robin Dunn
2016-07-07 21:38:56 -07:00
committed by GitHub
17 changed files with 115 additions and 319 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ wingdbstub.py*
mydbstub.py*
.idea
.cache
/build
/tmp

View File

@@ -209,5 +209,9 @@ Other Dev Stuff
etc. work with properties? See:
https://groups.google.com/d/msg/wxpython-dev/dMrpaKs_d0U/nVMY7lMvAwAJ
* In test_lib_agw_persist_persistencemanager.py change the tests to be
self-contained instead of some relying on files generated by others. This
won't work if we want to run tests in parallel.

View File

@@ -167,6 +167,10 @@ def main(args):
msg('cfg.VERSION: %s' % cfg.VERSION)
msg('')
# For collecting test names or files, to be run after all have been pulled
# off the command line
test_names = []
while commands:
# ensure that each command starts with the CWD being the phoenix dir.
os.chdir(phoenixDir())
@@ -174,21 +178,21 @@ def main(args):
if not cmd:
continue # ignore empty command-line args (possible with the buildbot)
elif cmd.startswith('test_'):
testOne(cmd, options, args)
test_names.append('unittests/%s.py' % cmd)
elif cmd.startswith('unittests/test_'):
testOne(os.path.basename(cmd), options, args)
test_names.append(cmd)
elif 'cmd_'+cmd in globals():
function = globals()['cmd_'+cmd]
function(options, args)
if cmd == 'test':
# Any additional cmd-line args are assumed to be tests, which
# will have already been taken care of, so bail out of the
# loop.
break
else:
print('*** Unknown command: ' + cmd)
usage()
sys.exit(1)
# Now run the collected tests names, if any
if test_names:
cmd_test(options, args, test_names)
msg("Done!")
@@ -402,14 +406,15 @@ def makeOptionParser():
("prefix", ("", "Prefix value to pass to the wx build.")),
("destdir", ("", "Installation root for wxWidgets, files will go to {destdir}/{prefix}")),
("extra_setup", ("", "Extra args to pass on setup.py's command line.")),
("extra_make", ("", "Extra args to pass on [n]make's command line.")),
("extra_waf", ("", "Extra args to pass on waf's command line.")),
("extra_setup", ("", "Extra args to pass on setup.py's command line.")),
("extra_make", ("", "Extra args to pass on [n]make's command line.")),
("extra_waf", ("", "Extra args to pass on waf's command line.")),
("extra_pytest", ("", "Extra args to pass on py.test's command line.")),
("jobs", ("", "Number of parallel compile jobs to do, if supported.")),
(("j","jobs"), ("", "Number of parallel compile jobs to do, if supported.")),
("both", (False, "Build both a debug and release version. (Only used on Windows)")),
("unicode", (True, "Build wxPython with unicode support (always on for wx2.9+)")),
("verbose", (False, "Print out more information.")),
(("v", "verbose"), (False, "Print out more information during the build.")),
("nodoc", (False, "Do not run the default docs generator")),
("upload", (False, "Upload bdist and/or sdist packages to snapshot server.")),
("cairo", (False, "Allow Cairo use with wxGraphicsContext (Windows only)")),
@@ -417,6 +422,8 @@ def makeOptionParser():
("jom", (False, "Use jom instead of nmake for the wxMSW build")),
("relwithdebug", (False, "Turn on the generation of debug info for release builds on MSW.")),
("release_build", (False, "Turn off some development options for a release build.")),
("pytest_timeout", ("0", "Timeout, in seconds, for stopping stuck test cases. (Currently not working as expected, so disabled by default.)")),
("pytest_jobs", ("", "Number of parallel processes py.test should run")),
]
parser = optparse.OptionParser("build options:")
@@ -425,8 +432,14 @@ def makeOptionParser():
action = 'store'
if type(default) == bool:
action = 'store_true'
parser.add_option('--'+opt, default=default, action=action,
dest=opt, help=txt)
if isinstance(opt, str):
opts = ('--'+opt, )
dest = opt
else:
opts = ('-'+opt[0], '--'+opt[1])
dest = opt[1]
parser.add_option(*opts, default=default, action=action,
dest=dest, help=txt)
return parser
@@ -1043,28 +1056,37 @@ def cmd_touch(options, args):
def cmd_test(options, args):
def cmd_test(options, args, tests=None):
# Run all tests
cmdTimer = CommandTimer('test')
pwd = pushDir(phoenixDir())
cmd = '"%s" unittests/runtests.py %s ' % (PYTHON, '-v' if options.verbose else '')
if len(args) > 1:
if options.verbose:
args.remove('--verbose')
cmd += ' '.join(args[1:])
# --boxed runs each test in a new process (only for posix *&##$#@$^!!)
# -n is the number of processes to run in parallel
# --timeout will kill the test process if it gets stuck
jobs = '-n{}'.format(options.pytest_jobs) if options.pytest_jobs else ''
boxed = '--boxed' if not isWindows else ''
sec = options.pytest_timeout
timeout = '--timeout={}'.format(sec) if sec and sec != "0" else ''
cmd = '"{}" -m pytest {} {} {} {} {} '.format(
PYTHON,
'-v' if options.verbose else '',
boxed,
jobs,
timeout,
options.extra_pytest)
if not tests:
# let pytest find all tests in the unittest folder
cmd += 'unittests'
else:
# otherwise, run only the test modules given
cmd += ' '.join(tests)
runcmd(cmd, fatal=False)
def testOne(name, options, args):
cmdTimer = CommandTimer('test %s:' % name)
pwd = pushDir(phoenixDir())
if name.endswith('.py') or name.endswith('.pyc'):
i = name.rfind('.')
name = name[:i]
runcmd('"%s" unittests/%s.py %s' % (PYTHON, name, '-v' if options.verbose else ''), fatal=True)
def cmd_build(options, args):
cmdTimer = CommandTimer('build')
cmd_build_wx(options, args)

View File

@@ -1,16 +1,10 @@
# Phoenix build requirements
setuptools == 20.2.2
six == 1.10.0
wheel == 0.29.0
twine==1.6.5
Pygments == 2.1.3
alabaster == 0.7.8
babel == 2.3.4
imagesize == 0.7.1
pytz == 2016.4
sphinx == 1.4.1
docutils == 0.12
Jinja2 == 2.8
MarkupSafe == 0.23
requests == 2.10.0
# Phoenix build and test requirements
setuptools
six
wheel
twine
sphinx
requests
pytest
pytest-xdist
pytest-timeout

View File

@@ -1,70 +0,0 @@
#---------------------------------------------------------------------------
# Name: unittests/do-runtests.py
# Author: Robin Dunn
#
# Created: 6-Aug-2012
# Copyright: (c) 2013 by Total Control Software
# License: wxWindows License
#---------------------------------------------------------------------------
"""
Run the unittests given on the command line while using a custom TestResults
class, and output the results in a format that can be integrated into another
TestResults (in the calling process.) See runtests.py for more information
and also for the code that calls this script via subprocess.
"""
import sys
import os
import unittest
import six
import pickle
g_testResult = None
# make sure the phoenix dir is on the path
if os.path.dirname(__file__):
phoenixDir = os.path.abspath(os.path.dirname(__file__)+'/..')
else: # run as main?
d = os.path.dirname(sys.argv[0])
if not d: d = '.'
phoenixDir = os.path.abspath(d+'/..')
sys.path.insert(0, phoenixDir)
class MyTestResult(unittest.TextTestResult):
def stopTestRun(self):
self.output = self.stream.getvalue()
def getResultsMsg(self):
def fixList(src):
return [(self.getDescription(test), err) for test, err in src]
msg = dict()
msg['output'] = self.output
msg['testsRun'] = self.testsRun
msg['failures'] = fixList(self.failures)
msg['errors'] = fixList(self.errors)
msg['skipped'] = fixList(self.skipped)
msg['expectedFailures'] = fixList(self.expectedFailures)
msg['unexpectedSuccesses'] = fixList(self.unexpectedSuccesses)
return msg
class MyTestRunner(unittest.TextTestRunner):
def _makeResult(self):
global g_testResult
if g_testResult is None:
self.stream = unittest.runner._WritelnDecorator(six.StringIO())
g_testResult = MyTestResult(self.stream, self.descriptions, self.verbosity)
return g_testResult
if __name__ == '__main__':
unittest.main(module=None, exit=False, testRunner=MyTestRunner)
msg = g_testResult.getResultsMsg()
text = pickle.dumps(msg)
if six.PY3:
sys.stdout.buffer.write(text)
else:
sys.stdout.write(text)

View File

@@ -1,164 +0,0 @@
#---------------------------------------------------------------------------
# Name: unittests/runtests.py
# Author: Robin Dunn
#
# Created: 3-Dec-2010
# Copyright: (c) 2010 by Total Control Software
# License: wxWindows License
#---------------------------------------------------------------------------
"""
This script will find and run all of the Phoenix test cases. We use a custom
TestSuite and other customized unittest classes so we can run each test
module in a separate process. This helps to isolate the test cases from each
other so they don't stomp on each other too much.
Currently the process granularity is the TestSuite created when the tests in
a single module are loaded, which makes it essentially the same as when
running that module standalone. More granularity is possible by using
separate processes for each TestCase.testMethod, but I haven't seen the need
for that yet.
See do-runtests.py for the script that is run in the child processes.
"""
import sys
import os
import glob
import subprocess
import unittest
# make sure the phoenix dir is on the path
if os.path.dirname(__file__):
phoenixDir = os.path.abspath(os.path.dirname(__file__)+'/..')
else: # run as main?
d = os.path.dirname(sys.argv[0])
if not d: d = '.'
phoenixDir = os.path.abspath(d+'/..')
sys.path.insert(0, phoenixDir)
import wx
import six
print("wx.version: " + wx.version())
print("pid: " + str(os.getpid()))
#print("executable: " + sys.executable); raw_input("Press Enter...")
from unittests import wtc
#---------------------------------------------------------------------------
def getTestName(test):
cls = test.__class__
return "%s.%s.%s" % (cls.__module__, cls.__name__, test._testMethodName)
class MyTestSuite(unittest.TestSuite):
"""
Override run() to run the TestCases in a new process.
"""
def run(self, result, debug=False):
if self._tests and isinstance(self._tests[0], unittest.TestSuite):
# self is a suite of suites, recurse down another level
return unittest.TestSuite.run(self, result, debug)
elif self._tests and not isinstance(self._tests[0], wtc.WidgetTestCase):
# we can run normal test cases in this process
return unittest.TestSuite.run(self, result, debug)
else:
# Otherwise we want to run these tests in a new process,
# get the names of all the test cases in this test suite
testNames = list()
for test in self:
name = getTestName(test)
testNames.append(name)
# build the command to be run
PYTHON = os.environ.get('PYTHON', sys.executable)
runner = os.path.join(phoenixDir, 'unittests', 'do-runtests.py')
cmd = [PYTHON, '-u', runner]
if result.verbosity > 1:
cmd.append('--verbose')
elif result.verbosity < 1:
cmd.append('--quiet')
cmd += testNames
# run it
sp = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE)#, stderr=subprocess.STDOUT)
output = sp.stdout.read()
#if sys.version_info > (3,):
# output = output.decode('ascii')
output = output.rstrip()
rval = sp.wait()
sp.stdout.close()
if rval:
print("Command '%s' failed with exit code %d." % (cmd, rval))
sys.exit(rval)
# Unpickle the output and copy it to result. It should be a
# dictionary with info from the TestResult class used in the
# child process.
import pickle
msg = pickle.loads(output)
result.stream.write(msg['output'])
result.stream.flush()
result.testsRun += msg['testsRun']
result.failures += msg['failures']
result.errors += msg['errors']
result.skipped += msg['skipped']
result.expectedFailures += msg['expectedFailures']
result.unexpectedSuccesses += msg['unexpectedSuccesses']
return result
class MyTestResult(unittest.TextTestResult):
def __init__(self, stream, descriptions, verbosity):
super(MyTestResult, self).__init__(stream, descriptions, verbosity)
self.verbosity = verbosity
def getDescription(self, test):
"""
Override getDescription() to be able to deal with the test already
being converted to a string.
"""
if isinstance(test, six.string_types):
return test
return super(MyTestResult, self).getDescription(test)
class MyTestLoader(unittest.TestLoader):
suiteClass = MyTestSuite
class MyTestRunner(unittest.TextTestRunner):
resultclass = MyTestResult
#---------------------------------------------------------------------------
if __name__ == '__main__':
if '--single-process' in sys.argv:
sys.argv.remove('--single-process')
args = sys.argv[:1] + 'discover -p test_*.py -s unittests -t .'.split() + sys.argv[1:]
unittest.main(argv=args)
else:
# The discover option doesn't use my my custom loader or suite
# classes, so we'll do the finding of the test files in this case. If
# there aren't already some in argv then get all of them in unittests
# folder.
for arg in sys.argv[1:]:
if arg.startswith('unittests'):
names = sys.argv[1:]
sys.argv = sys.argv[:1]
break
else:
names = glob.glob(os.path.join('unittests', 'test_*.py'))
names = [os.path.splitext(os.path.basename(n))[0] for n in names]
args = sys.argv + names
unittest.main(argv=args, module=None,
testRunner=MyTestRunner, testLoader=MyTestLoader())

View File

@@ -1,7 +1,5 @@
import unittest
import wx
from unittests import wtc
import sys
import six
#---------------------------------------------------------------------------

View File

@@ -1,7 +1,7 @@
import unittest
from unittests import wtc
import wx
from wx.lib import six
import six
import wx.dataview as dv
import os

View File

@@ -1,4 +1,5 @@
import unittest, wtc
import unittest
from unittests import wtc
import wx

View File

@@ -10,14 +10,17 @@ import wx.lib.agw.persist as PM
class lib_agw_persist_persistencemanager_Tests(wtc.WidgetTestCase):
def test_lib_agw_persist_persistencemanagerCtor(self):
def setUp(self):
super(lib_agw_persist_persistencemanager_Tests, self).setUp()
dirName = os.path.dirname(os.path.abspath(__file__))
self._configFile1 = os.path.join(dirName, "PersistTest1")
def test_persistencemanagerCtor(self):
self._persistMgr = PM.PersistenceManager.Get()
self._persistMgr.SetManagerStyle(PM.PM_SAVE_RESTORE_AUI_PERSPECTIVES)
dirName, fileName = os.path.split(os.path.abspath(__file__))
_configFile1 = os.path.join(dirName, "PersistTest1")
self._persistMgr.SetPersistenceFile(_configFile1)
self._persistMgr.SetPersistenceFile(self._configFile1)
# give the frame a Name for below
self.frame.SetName('PersistTestFrame')
@@ -30,13 +33,10 @@ class lib_agw_persist_persistencemanager_Tests(wtc.WidgetTestCase):
self._persistMgr.SaveAndUnregister()
def test_lib_agw_persist_persistencemanagerRestore(self):
def test_persistencemanagerRestore(self):
self._persistMgr = PM.PersistenceManager.Get()
dirName, fileName = os.path.split(os.path.abspath(__file__))
_configFile1 = os.path.join(dirName, "PersistTest1")
self._persistMgr.SetPersistenceFile(_configFile1)
self._persistMgr.SetPersistenceFile(self._configFile1)
# give the frame a Name for below
self.frame.SetName('PersistTestFrame')
@@ -45,14 +45,12 @@ class lib_agw_persist_persistencemanager_Tests(wtc.WidgetTestCase):
self.assertEqual(self._persistMgr.HasRestored(), True, "Persistence should be there, as it was created in CTOR test.")
def test_lib_agw_persist_persistencemanagerPersistValue(self):
def test_persistencemanagerPersistValue(self):
self._persistMgr = PM.PersistenceManager.Get()
self._persistMgr.SetManagerStyle(PM.PM_SAVE_RESTORE_AUI_PERSPECTIVES)
dirName, fileName = os.path.split(os.path.abspath(__file__))
_configFile1 = os.path.join(dirName, "PersistTest1")
self._persistMgr.SetPersistenceFile(_configFile1)
self._persistMgr.SetPersistenceFile(self._configFile1)
# give the frame a Name for below
self.frame.SetName('PersistTestFrame')
@@ -65,7 +63,14 @@ class lib_agw_persist_persistencemanager_Tests(wtc.WidgetTestCase):
self.assertEqual(cb.GetValue(), False, "Should be False as set in CTOR test")
def test_lib_agw_persist_persistencemanagerConstantsExist(self):
def test_persistencemanagerZZZZCleanup(self):
# Just clean up the test file used by the other tests...
# TODO: Fix these tests to be self-contained and to clean up after themselves
if os.path.exists(self._configFile1):
os.unlink(self._configFile1)
def test_persistencemanagerConstantsExist(self):
# PersistenceManager styles
PM.PM_SAVE_RESTORE_AUI_PERSPECTIVES
PM.PM_SAVE_RESTORE_TREE_LIST_SELECTIONS

View File

@@ -1,5 +1,6 @@
import sys
import unittest, wtc
import unittest
from unittests import wtc
import wx
##import os; print 'PID:', os.getpid(); raw_input('Ready to start, press enter...')

View File

@@ -19,7 +19,7 @@ class MouseEventsPanel(wx.Panel):
def onMouseEvent(self, evt):
self.events.append( (evt.EventType, evt.Position) )
#print(self.events)
print( (evt.EventType, evt.Position) )
evt.Skip()
@@ -56,7 +56,7 @@ class uiaction_MouseTests(wtc.WidgetTestCase):
uia.MouseMove(p.ClientToScreen((10,10)).x, p.ClientToScreen((10,10)).y)
self.waitFor(WAIT)
self.waitFor(WAIT)
self.assertEqual(len(p.events), 3)
self.assertTrue(self.cmp(p.events[0], wx.wxEVT_MOTION, (1,1)))
self.assertTrue(self.cmp(p.events[1], wx.wxEVT_MOTION, (5,5)))
@@ -105,7 +105,6 @@ class uiaction_MouseTests(wtc.WidgetTestCase):
self.assertTrue(len(p.events) == 2)
self.assertTrue(self.cmp(p.events[0], wx.wxEVT_LEFT_DOWN, (10,10)))
self.assertTrue(self.cmp(p.events[1], wx.wxEVT_LEFT_UP, (10,10)))
def test_uiactionMouseLeftDClick(self):
@@ -123,8 +122,7 @@ class uiaction_MouseTests(wtc.WidgetTestCase):
self.assertTrue(self.cmp(p.events[2], (wx.wxEVT_LEFT_DOWN, wx.wxEVT_LEFT_DCLICK), (10,10)))
self.assertTrue(self.cmp(p.events[3], wx.wxEVT_LEFT_UP, (10,10)))
def test_uiactionMouseDD(self):
p = MouseEventsPanel(self.frame, [wx.EVT_MOTION, wx.EVT_LEFT_DOWN, wx.EVT_LEFT_UP])
@@ -150,10 +148,12 @@ class uiaction_KeyboardTests(wtc.WidgetTestCase):
def setUp(self):
super(uiaction_KeyboardTests, self).setUp()
self.tc = wx.TextCtrl(self.frame)
pnl = wx.Panel(self.frame)
pnl.SetBackgroundColour('pink')
self.tc = wx.TextCtrl(pnl)
self.tc.SetFocus()
self.waitFor(WAIT)
def test_uiactionKeyboardKeyDownUp(self):
uia = wx.UIActionSimulator()
@@ -169,7 +169,7 @@ class uiaction_KeyboardTests(wtc.WidgetTestCase):
self.assertEqual(self.tc.GetValue(), "This is a test")
@unittest.skipIf(sys.platform == 'darwin', 'wx.UIActionSimulator.Char needs work...')
@unittest.skipIf(sys.platform == 'darwin', 'wx.UIActionSimulator.Char needs work...')
def test_uiactionKeyboardChar(self):
uia = wx.UIActionSimulator()
for c in "This is a test":

View File

@@ -27,8 +27,6 @@ class xrc_Tests(wtc.WidgetTestCase):
self.assertTrue(isinstance(ctrl, wx.StaticText))
def test_xrc1(self):
xmlres = xrc.XmlResource(xrcFile)
self.checkXmlRes(xmlres)
@@ -236,7 +234,7 @@ class xrc_Tests(wtc.WidgetTestCase):
resource = b'''<?xml version="1.0"?>
<resource>
<!-- Notice that the class IS a standard wx class and that a subclass is specified -->
<object class="wxPanel" name="MyPanel" subclass="xrcfactorytest.MyCustomPanel">
<object class="wxPanel" name="MyPanel" subclass="unittests.xrcfactorytest.MyCustomPanel">
<size>200,100</size>
<object class="wxStaticText" name="label1">
<label>This panel is a custom class derived from wx.Panel,\nand is loaded by the Python SubclassFactory.</label>
@@ -255,7 +253,7 @@ class xrc_Tests(wtc.WidgetTestCase):
self.myYield()
self.assertNotEqual(panel, None)
import xrcfactorytest
from unittests import xrcfactorytest
self.assertTrue(isinstance(panel, xrcfactorytest.MyCustomPanel))

View File

@@ -17,15 +17,21 @@ class WidgetTestCase(unittest.TestCase):
wx.Log.SetActiveTarget(wx.LogStderr())
self.frame = wx.Frame(None, title='WTC: '+self.__class__.__name__)
self.frame.Show()
self.frame.PostSizeEvent()
def tearDown(self):
def _cleanup():
for tlw in wx.GetTopLevelWindows():
if tlw:
tlw.Destroy()
if isinstance(tlw, wx.Dialog) and tlw.IsModal():
tlw.EndModal(0)
wx.CallAfter(tlw.Destroy)
else:
tlw.Close(force=True)
wx.WakeUpIdle()
#self.app.ExitMainLoop()
wx.CallLater(50, _cleanup)
timer = wx.PyTimer(_cleanup)
timer.Start(100)
self.app.MainLoop()
del self.app

View File

@@ -17,7 +17,7 @@ This is where FloatCanvas defines its drawings objects.
import sys
import wx
from wx.lib import six
import six
import numpy as N

View File

@@ -50,7 +50,7 @@ mac = sys.platform.startswith("darwin")
import numpy as N
from time import clock
import wx
from wx.lib import six
import six
from .FCObjects import *

View File

@@ -10,7 +10,7 @@ import zlib
def getScreenShotData():
return zlib.decompress(
'x\xdatzeP\\M\xd0.N\xf0\xe0\x16\xdc\xdd\x12\xdc\xdd\t/\xee\xee\xee\x0e!\x10\
b'x\xdatzeP\\M\xd0.N\xf0\xe0\x16\xdc\xdd\x12\xdc\xdd\t/\xee\xee\xee\x0e!\x10\
\xdc\xdd\t\xee\xee\x8b\xbbC`\x91`\x8b;\xcb\xe2.\x97|_\xdd\x7f\xf7v\xd5\xd4\
\xa9\x9a\xd3\xa7\xa7g\xba{\xe6y\xa6N\xe4we\x194d"d(((49Y\xc9\xff\xa0\xa0\xa0\
/>\x9a\xe8\'\x84\x8f\x1e\tK\xd6\xe4\x8f\x07\x9c\x9b\xb8\x9c\xfa\xa7\x0f\xf1\