From 5a794c16d98a12f75b728ff902c410f10f93770f Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Fri, 8 Aug 2014 13:32:16 -0500 Subject: Issue #22060: Clean up/simplify test_ctypes, use test discovery --- Lib/ctypes/test/__init__.py | 216 ++----------------------------------- Lib/ctypes/test/__main__.py | 4 + Lib/ctypes/test/runtests.py | 19 ---- Lib/ctypes/test/test_find.py | 72 ++++++------- Lib/ctypes/test/test_loading.py | 43 ++++---- Lib/ctypes/test/test_python_api.py | 5 - Lib/ctypes/test/test_win32.py | 2 - Lib/test/test_ctypes.py | 12 +-- Misc/NEWS | 3 + 9 files changed, 74 insertions(+), 302 deletions(-) create mode 100644 Lib/ctypes/test/__main__.py delete mode 100644 Lib/ctypes/test/runtests.py diff --git a/Lib/ctypes/test/__init__.py b/Lib/ctypes/test/__init__.py index aef54c3..26a70b7 100644 --- a/Lib/ctypes/test/__init__.py +++ b/Lib/ctypes/test/__init__.py @@ -1,216 +1,14 @@ -import os, sys, unittest, getopt, time +import os +import unittest +from test import support -use_resources = [] - -import ctypes +# skip tests if _ctypes was not built +ctypes = support.import_module('ctypes') ctypes_symbols = dir(ctypes) def need_symbol(name): return unittest.skipUnless(name in ctypes_symbols, '{!r} is required'.format(name)) - -class ResourceDenied(unittest.SkipTest): - """Test skipped because it requested a disallowed resource. - - This is raised when a test calls requires() for a resource that - has not be enabled. Resources are defined by test modules. - """ - -def is_resource_enabled(resource): - """Test whether a resource is enabled. - - If the caller's module is __main__ then automatically return True.""" - if sys._getframe().f_back.f_globals.get("__name__") == "__main__": - return True - result = use_resources is not None and \ - (resource in use_resources or "*" in use_resources) - if not result: - _unavail[resource] = None - return result - -_unavail = {} -def requires(resource, msg=None): - """Raise ResourceDenied if the specified resource is not available. - - If the caller's module is __main__ then automatically return True.""" - # see if the caller's module is __main__ - if so, treat as if - # the resource was set - if sys._getframe().f_back.f_globals.get("__name__") == "__main__": - return - if not is_resource_enabled(resource): - if msg is None: - msg = "Use of the `%s' resource not enabled" % resource - raise ResourceDenied(msg) - -def find_package_modules(package, mask): - import fnmatch - if (package.__loader__ is not None and - hasattr(package.__loader__, '_files')): - path = package.__name__.replace(".", os.path.sep) - mask = os.path.join(path, mask) - for fnm in package.__loader__._files.keys(): - if fnmatch.fnmatchcase(fnm, mask): - yield os.path.splitext(fnm)[0].replace(os.path.sep, ".") - else: - path = package.__path__[0] - for fnm in os.listdir(path): - if fnmatch.fnmatchcase(fnm, mask): - yield "%s.%s" % (package.__name__, os.path.splitext(fnm)[0]) - -def get_tests(package, mask, verbosity, exclude=()): - """Return a list of skipped test modules, and a list of test cases.""" - tests = [] - skipped = [] - for modname in find_package_modules(package, mask): - if modname.split(".")[-1] in exclude: - skipped.append(modname) - if verbosity > 1: - print("Skipped %s: excluded" % modname, file=sys.stderr) - continue - try: - mod = __import__(modname, globals(), locals(), ['*']) - except (ResourceDenied, unittest.SkipTest) as detail: - skipped.append(modname) - if verbosity > 1: - print("Skipped %s: %s" % (modname, detail), file=sys.stderr) - continue - for name in dir(mod): - if name.startswith("_"): - continue - o = getattr(mod, name) - if type(o) is type(unittest.TestCase) and issubclass(o, unittest.TestCase): - tests.append(o) - return skipped, tests - -def usage(): - print(__doc__) - return 1 - -def test_with_refcounts(runner, verbosity, testcase): - """Run testcase several times, tracking reference counts.""" - import gc - import ctypes - ptc = ctypes._pointer_type_cache.copy() - cfc = ctypes._c_functype_cache.copy() - wfc = ctypes._win_functype_cache.copy() - - # when searching for refcount leaks, we have to manually reset any - # caches that ctypes has. - def cleanup(): - ctypes._pointer_type_cache = ptc.copy() - ctypes._c_functype_cache = cfc.copy() - ctypes._win_functype_cache = wfc.copy() - gc.collect() - - test = unittest.makeSuite(testcase) - for i in range(5): - rc = sys.gettotalrefcount() - runner.run(test) - cleanup() - COUNT = 5 - refcounts = [None] * COUNT - for i in range(COUNT): - rc = sys.gettotalrefcount() - runner.run(test) - cleanup() - refcounts[i] = sys.gettotalrefcount() - rc - if filter(None, refcounts): - print("%s leaks:\n\t" % testcase, refcounts) - elif verbosity: - print("%s: ok." % testcase) - -class TestRunner(unittest.TextTestRunner): - def run(self, test, skipped): - "Run the given test case or test suite." - # Same as unittest.TextTestRunner.run, except that it reports - # skipped tests. - result = self._makeResult() - startTime = time.time() - test(result) - stopTime = time.time() - timeTaken = stopTime - startTime - result.printErrors() - self.stream.writeln(result.separator2) - run = result.testsRun - if _unavail: #skipped: - requested = list(_unavail.keys()) - requested.sort() - self.stream.writeln("Ran %d test%s in %.3fs (%s module%s skipped)" % - (run, run != 1 and "s" or "", timeTaken, - len(skipped), - len(skipped) != 1 and "s" or "")) - self.stream.writeln("Unavailable resources: %s" % ", ".join(requested)) - else: - self.stream.writeln("Ran %d test%s in %.3fs" % - (run, run != 1 and "s" or "", timeTaken)) - self.stream.writeln() - if not result.wasSuccessful(): - self.stream.write("FAILED (") - failed, errored = map(len, (result.failures, result.errors)) - if failed: - self.stream.write("failures=%d" % failed) - if errored: - if failed: self.stream.write(", ") - self.stream.write("errors=%d" % errored) - self.stream.writeln(")") - else: - self.stream.writeln("OK") - return result - - -def main(*packages): - try: - opts, args = getopt.getopt(sys.argv[1:], "rqvu:x:") - except getopt.error: - return usage() - - verbosity = 1 - search_leaks = False - exclude = [] - for flag, value in opts: - if flag == "-q": - verbosity -= 1 - elif flag == "-v": - verbosity += 1 - elif flag == "-r": - try: - sys.gettotalrefcount - except AttributeError: - print("-r flag requires Python debug build", file=sys.stderr) - return -1 - search_leaks = True - elif flag == "-u": - use_resources.extend(value.split(",")) - elif flag == "-x": - exclude.extend(value.split(",")) - - mask = "test_*.py" - if args: - mask = args[0] - - for package in packages: - run_tests(package, mask, verbosity, search_leaks, exclude) - - -def run_tests(package, mask, verbosity, search_leaks, exclude): - skipped, testcases = get_tests(package, mask, verbosity, exclude) - runner = TestRunner(verbosity=verbosity) - - suites = [unittest.makeSuite(o) for o in testcases] - suite = unittest.TestSuite(suites) - result = runner.run(suite, skipped) - - if search_leaks: - # hunt for refcount leaks - runner = BasicTestRunner() - for t in testcases: - test_with_refcounts(runner, verbosity, t) - - return bool(result.errors) - -class BasicTestRunner: - def run(self, test): - result = unittest.TestResult() - test(result) - return result +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/ctypes/test/__main__.py b/Lib/ctypes/test/__main__.py new file mode 100644 index 0000000..362a9ec --- /dev/null +++ b/Lib/ctypes/test/__main__.py @@ -0,0 +1,4 @@ +from ctypes.test import load_tests +import unittest + +unittest.main() diff --git a/Lib/ctypes/test/runtests.py b/Lib/ctypes/test/runtests.py deleted file mode 100644 index b7a2b26..0000000 --- a/Lib/ctypes/test/runtests.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Usage: runtests.py [-q] [-r] [-v] [-u resources] [mask] - -Run all tests found in this directory, and print a summary of the results. -Command line flags: - -q quiet mode: don't print anything while the tests are running - -r run tests repeatedly, look for refcount leaks - -u - Add resources to the lits of allowed resources. '*' allows all - resources. - -v verbose mode: print the test currently executed - -x - Exclude specified tests. - mask mask to select filenames containing testcases, wildcards allowed -""" -import sys -import ctypes.test - -if __name__ == "__main__": - sys.exit(ctypes.test.main(ctypes.test)) diff --git a/Lib/ctypes/test/test_find.py b/Lib/ctypes/test/test_find.py index d838099..d8cd8ad 100644 --- a/Lib/ctypes/test/test_find.py +++ b/Lib/ctypes/test/test_find.py @@ -1,60 +1,58 @@ import unittest import os import sys +import test.support from ctypes import * from ctypes.util import find_library -from ctypes.test import is_resource_enabled - -if sys.platform == "win32": - lib_gl = find_library("OpenGL32") - lib_glu = find_library("Glu32") - lib_gle = None -elif sys.platform == "darwin": - lib_gl = lib_glu = find_library("OpenGL") - lib_gle = None -else: - lib_gl = find_library("GL") - lib_glu = find_library("GLU") - lib_gle = find_library("gle") - -## print, for debugging -if is_resource_enabled("printing"): - if lib_gl or lib_glu or lib_gle: - print("OpenGL libraries:") - for item in (("GL", lib_gl), - ("GLU", lib_glu), - ("gle", lib_gle)): - print("\t", item) - # On some systems, loading the OpenGL libraries needs the RTLD_GLOBAL mode. class Test_OpenGL_libs(unittest.TestCase): - def setUp(self): - self.gl = self.glu = self.gle = None + @classmethod + def setUpClass(cls): + lib_gl = lib_glu = lib_gle = None + if sys.platform == "win32": + lib_gl = find_library("OpenGL32") + lib_glu = find_library("Glu32") + elif sys.platform == "darwin": + lib_gl = lib_glu = find_library("OpenGL") + else: + lib_gl = find_library("GL") + lib_glu = find_library("GLU") + lib_gle = find_library("gle") + + ## print, for debugging + if test.support.verbose: + print("OpenGL libraries:") + for item in (("GL", lib_gl), + ("GLU", lib_glu), + ("gle", lib_gle)): + print("\t", item) + + cls.gl = cls.glu = cls.gle = None if lib_gl: - self.gl = CDLL(lib_gl, mode=RTLD_GLOBAL) + cls.gl = CDLL(lib_gl, mode=RTLD_GLOBAL) if lib_glu: - self.glu = CDLL(lib_glu, RTLD_GLOBAL) + cls.glu = CDLL(lib_glu, RTLD_GLOBAL) if lib_gle: try: - self.gle = CDLL(lib_gle) + cls.gle = CDLL(lib_gle) except OSError: pass - @unittest.skipUnless(lib_gl, 'lib_gl not available') def test_gl(self): - if self.gl: - self.gl.glClearIndex + if self.gl is None: + self.skipTest('lib_gl not available') + self.gl.glClearIndex - @unittest.skipUnless(lib_glu, 'lib_glu not available') def test_glu(self): - if self.glu: - self.glu.gluBeginCurve + if self.glu is None: + self.skipTest('lib_glu not available') + self.glu.gluBeginCurve - @unittest.skipUnless(lib_gle, 'lib_gle not available') def test_gle(self): - if self.gle: - self.gle.gleGetJoinStyle + if self.gle is None: + self.skipTest('lib_gle not available') + self.gle.gleGetJoinStyle # On platforms where the default shared library suffix is '.so', # at least some libraries can be loaded as attributes of the cdll diff --git a/Lib/ctypes/test/test_loading.py b/Lib/ctypes/test/test_loading.py index 3ab07c1..4fb8964 100644 --- a/Lib/ctypes/test/test_loading.py +++ b/Lib/ctypes/test/test_loading.py @@ -1,37 +1,42 @@ from ctypes import * -import sys, unittest import os +import sys +import unittest +import test.support from ctypes.util import find_library -from ctypes.test import is_resource_enabled libc_name = None -if os.name == "nt": - libc_name = find_library("c") -elif os.name == "ce": - libc_name = "coredll" -elif sys.platform == "cygwin": - libc_name = "cygwin1.dll" -else: - libc_name = find_library("c") - -if is_resource_enabled("printing"): - print("libc_name is", libc_name) + +def setUpModule(): + global libc_name + if os.name == "nt": + libc_name = find_library("c") + elif os.name == "ce": + libc_name = "coredll" + elif sys.platform == "cygwin": + libc_name = "cygwin1.dll" + else: + libc_name = find_library("c") + + if test.support.verbose: + print("libc_name is", libc_name) class LoaderTest(unittest.TestCase): unknowndll = "xxrandomnamexx" - @unittest.skipUnless(libc_name is not None, 'could not find libc') def test_load(self): + if libc_name is None: + self.skipTest('could not find libc') CDLL(libc_name) CDLL(os.path.basename(libc_name)) self.assertRaises(OSError, CDLL, self.unknowndll) - @unittest.skipUnless(libc_name is not None, 'could not find libc') - @unittest.skipUnless(libc_name is not None and - os.path.basename(libc_name) == "libc.so.6", - 'wrong libc path for test') def test_load_version(self): + if libc_name is None: + self.skipTest('could not find libc') + if os.path.basename(libc_name) != 'libc.so.6': + self.skipTest('wrong libc path for test') cdll.LoadLibrary("libc.so.6") # linux uses version, libc 9 should not exist self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") @@ -48,7 +53,7 @@ class LoaderTest(unittest.TestCase): 'test specific to Windows (NT/CE)') def test_load_library(self): self.assertIsNotNone(libc_name) - if is_resource_enabled("printing"): + if test.support.verbose: print(find_library("kernel32")) print(find_library("user32")) diff --git a/Lib/ctypes/test/test_python_api.py b/Lib/ctypes/test/test_python_api.py index 0bd2f4c..9c13746 100644 --- a/Lib/ctypes/test/test_python_api.py +++ b/Lib/ctypes/test/test_python_api.py @@ -1,7 +1,6 @@ from ctypes import * import unittest, sys from test import support -from ctypes.test import requires ################################################################ # This section should be moved into ctypes\__init__.py, when it's ready. @@ -39,12 +38,8 @@ class PythonAPITestCase(unittest.TestCase): del pyob self.assertEqual(grc(s), refcnt) - # This test is unreliable, because it is possible that code in - # unittest changes the refcount of the '42' integer. So, it - # is disabled by default. @support.refcount_test def test_PyLong_Long(self): - requires("refcount") ref42 = grc(42) pythonapi.PyLong_FromLong.restype = py_object self.assertEqual(pythonapi.PyLong_FromLong(42), 42) diff --git a/Lib/ctypes/test/test_win32.py b/Lib/ctypes/test/test_win32.py index fcd2163..93573dd 100644 --- a/Lib/ctypes/test/test_win32.py +++ b/Lib/ctypes/test/test_win32.py @@ -1,7 +1,6 @@ # Windows specific tests from ctypes import * -from ctypes.test import requires import unittest, sys from test import support @@ -42,7 +41,6 @@ class FunctionCallTestCase(unittest.TestCase): @unittest.skipIf(sys.executable.endswith('_d.exe'), "SEH not enabled in debug builds") def test_SEH(self): - requires("SEH") # Call functions with invalid arguments, and make sure # that access violations are trapped and raise an # exception. diff --git a/Lib/test/test_ctypes.py b/Lib/test/test_ctypes.py index 496355e..53964ca 100644 --- a/Lib/test/test_ctypes.py +++ b/Lib/test/test_ctypes.py @@ -1,16 +1,6 @@ import unittest -from test.support import import_module - -# Skip tests if _ctypes module was not built. -import_module('_ctypes') - -import ctypes.test - -def load_tests(*args): - skipped, testcases = ctypes.test.get_tests(ctypes.test, "test_*.py", verbosity=0) - suites = [unittest.makeSuite(t) for t in testcases] - return unittest.TestSuite(suites) +from ctypes.test import load_tests if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS b/Misc/NEWS index 149f2a7..2b21ba5 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -243,6 +243,9 @@ IDLE Tests ----- +- Issue #22060: test_ctypes has been somewhat cleaned up and simplified; it + now uses unittest test discovery to find its tests. + - Issue #22104: regrtest.py no longer holds a reference to the suite of tests loaded from test modules that don't define test_main(). -- cgit v0.12