-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #193 from stdweird/setup_test
Support unittesting via 'python setup.py test'
- Loading branch information
Showing
3 changed files
with
246 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,30 +24,75 @@ | |
import shutil | ||
import sys | ||
import re | ||
|
||
from distutils import log # also for setuptools | ||
from distutils.command.bdist_rpm import bdist_rpm as orig_bdist_rpm | ||
from distutils.dir_util import remove_tree | ||
|
||
from setuptools.command.test import test as TestCommand | ||
from setuptools.command.test import ScanningLoader | ||
from setuptools import setup | ||
from setuptools.command.bdist_rpm import bdist_rpm | ||
from setuptools.command.build_py import build_py | ||
from setuptools.command.egg_info import egg_info | ||
from setuptools.command.install_scripts import install_scripts | ||
# egg_info uses sdist directly through manifest_maker | ||
from setuptools.command.sdist import sdist | ||
|
||
from unittest import TestSuite | ||
|
||
# private class variables to communicate | ||
# between VscScanningLoader and VscTestCommand | ||
# stored in __builtin__ because the (Vsc)TestCommand.run_tests | ||
# reloads and cleans up the modules | ||
import __builtin__ | ||
if not hasattr(__builtin__,'__test_filter'): | ||
setattr(__builtin__, '__test_filter', { | ||
'module': None, | ||
'function': None, | ||
'allowmods': [], | ||
}) | ||
|
||
# Keep this for legacy reasons, setuptools didn't used to be a requirement | ||
has_setuptools = True | ||
|
||
# 0 : WARN (default), 1 : INFO, 2 : DEBUG | ||
log.set_verbosity(2) | ||
|
||
has_setuptools = None | ||
# available authors | ||
ag = ('Andy Georges', '[email protected]') | ||
eh = ('Ewan Higgs', '[email protected]') | ||
jt = ('Jens Timmermans', '[email protected]') | ||
kh = ('Kenneth Hoste', '[email protected]') | ||
kw = ('Kenneth Waegeman', '[email protected]') | ||
lm = ('Luis Fernando Munoz Meji?as', '[email protected]') | ||
sdw = ('Stijn De Weirdt', '[email protected]') | ||
wdp = ('Wouter Depypere', '[email protected]') | ||
wp = ('Ward Poelmans', '[email protected]') | ||
|
||
# Regexp used to remove suffixes from scripts when installing(/packaging) | ||
REGEXP_REMOVE_SUFFIX = re.compile(r'(\.(?:py|sh|pl))$') | ||
|
||
# We do need all setup files to be included in the source dir if we ever want to install | ||
# the package elsewhere. | ||
# We do need all setup files to be included in the source dir | ||
# if we ever want to install the package elsewhere. | ||
EXTRA_SDIST_FILES = ['setup.py'] | ||
|
||
# Put unittests under this directory | ||
DEFAULT_TEST_SUITE = 'test' | ||
|
||
URL_GH_HPCUGENT = 'https://github.com/hpcugent/%(name)s' | ||
URL_GHUGENT_HPCUGENT = 'https://github.ugent.be/hpcugent/%(name)s' | ||
|
||
|
||
def find_extra_sdist_files(): | ||
"""Looks for files to append to the FileList that is used by the egg_info.""" | ||
print "looking for extra dist files" | ||
log.info("looking for extra dist files") | ||
filelist = [] | ||
for fn in EXTRA_SDIST_FILES: | ||
if os.path.isfile(fn): | ||
filelist.append(fn) | ||
else: | ||
print "sdist add_defaults Failed to find %s" % fn | ||
print "exiting." | ||
log.error("sdist add_defaults Failed to find %s. Exiting." % fn) | ||
sys.exit(1) | ||
return filelist | ||
|
||
|
@@ -61,80 +106,35 @@ def remove_extra_bdist_rpm_files(): | |
""" | ||
return [] | ||
|
||
# The following aims to import from setuptools, but if this is not available, we import the basic functionality from | ||
# distutils instead. Note that setuptools make copies of the scripts, it does _not_ preserve symbolic links. | ||
try: | ||
#raise ImportError("no setuptools") # to try distutils, uncomment | ||
from setuptools import setup | ||
from setuptools.command.bdist_rpm import bdist_rpm | ||
from distutils.command.bdist_rpm import bdist_rpm as orig_bdist_rpm | ||
from setuptools.command.build_py import build_py | ||
from setuptools.command.install_scripts import install_scripts | ||
from setuptools.command.sdist import sdist | ||
|
||
# egg_info uses sdist directly through manifest_maker | ||
from setuptools.command.egg_info import egg_info | ||
|
||
class vsc_egg_info(egg_info): | ||
"""Class to determine the set of files that should be included. | ||
This amounts to including the default files, as determined by setuptools, extended with the | ||
few extra files we need to add for installation purposes. | ||
""" | ||
|
||
def find_sources(self): | ||
"""Default lookup.""" | ||
egg_info.find_sources(self) | ||
self.filelist.extend(find_extra_sdist_files()) | ||
|
||
# TODO: this should be in the setup.py, here we should have a placeholder, so we need not change this for every | ||
# package we deploy | ||
class vsc_bdist_rpm_egg_info(vsc_egg_info): | ||
"""Class to determine the source files that should be present in an (S)RPM. | ||
All __init__.py files that augment namespace packages should be installed by the | ||
dependent package, so we need not install it here. | ||
""" | ||
|
||
def find_sources(self): | ||
"""Fins the sources as default and then drop the cruft.""" | ||
vsc_egg_info.find_sources(self) | ||
for f in remove_extra_bdist_rpm_files(): | ||
print "DEBUG: removing %s from source list" % (f) | ||
self.filelist.files.remove(f) | ||
|
||
has_setuptools = True | ||
except ImportError as err: | ||
log.warn("ImportError, falling back to distutils-only: %s", err) | ||
from distutils.core import setup | ||
from distutils.command.install_scripts import install_scripts | ||
from distutils.command.build_py import build_py | ||
from distutils.command.sdist import sdist | ||
from distutils.command.bdist_rpm import bdist_rpm as orig_bdist_rpm | ||
bdist_rpm = orig_bdist_rpm # mimic setuptools' bdist_rpm | ||
|
||
class vsc_egg_info(object): | ||
pass # dummy class for distutils | ||
class vsc_egg_info(egg_info): | ||
"""Class to determine the set of files that should be included. | ||
class vsc_bdist_rpm_egg_info(vsc_egg_info): | ||
pass # dummy class for distutils | ||
This amounts to including the default files, as determined by setuptools, extended with the | ||
few extra files we need to add for installation purposes. | ||
""" | ||
|
||
has_setuptools = False | ||
def find_sources(self): | ||
"""Default lookup.""" | ||
egg_info.find_sources(self) | ||
self.filelist.extend(find_extra_sdist_files()) | ||
|
||
# TODO: this should be in the setup.py, here we should have a placeholder, so we need not change this for every | ||
# package we deploy | ||
class vsc_bdist_rpm_egg_info(vsc_egg_info): | ||
"""Class to determine the source files that should be present in an (S)RPM. | ||
# available authors | ||
ag = ('Andy Georges', '[email protected]') | ||
jt = ('Jens Timmermans', '[email protected]') | ||
kh = ('Kenneth Hoste', '[email protected]') | ||
lm = ('Luis Fernando Munoz Meji?as', '[email protected]') | ||
sdw = ('Stijn De Weirdt', '[email protected]') | ||
wdp = ('Wouter Depypere', '[email protected]') | ||
kw = ('Kenneth Waegeman', '[email protected]') | ||
eh = ('Ewan Higgs', '[email protected]') | ||
All __init__.py files that augment namespace packages should be installed by the | ||
dependent package, so we need not install it here. | ||
""" | ||
|
||
def find_sources(self): | ||
"""Finds the sources as default and then drop the cruft.""" | ||
vsc_egg_info.find_sources(self) | ||
for fn in remove_extra_bdist_rpm_files(): | ||
log.debug("removing %s from source list" % (fn)) | ||
self.filelist.files.remove(fn) | ||
|
||
# FIXME: do we need this here? it won;t hurt, but still ... | ||
REGEXP_REMOVE_SUFFIX = re.compile(r'(\.(?:py|sh|pl))$') | ||
|
||
class vsc_install_scripts(install_scripts): | ||
"""Create the (fake) links for mympirun also remove .sh and .py extensions from the scripts.""" | ||
|
@@ -166,14 +166,168 @@ def find_package_modules (self, package, package_dir): | |
|
||
|
||
class vsc_bdist_rpm(bdist_rpm): | ||
""" Custom class to build the RPM, since the __inti__.py cannot be included for the packages that have namespace spread across all of the machine.""" | ||
"""Custom class to build the RPM, since the __init__.py cannot be included for the packages that have namespace spread across all of the machine.""" | ||
def run(self): | ||
log.error("vsc_bdist_rpm = %s" % (self.__dict__)) | ||
SHARED_TARGET['cmdclass']['egg_info'] = vsc_bdist_rpm_egg_info # changed to allow removal of files | ||
self.run_command('egg_info') # ensure distro name is up-to-date | ||
orig_bdist_rpm.run(self) | ||
|
||
|
||
def filter_testsuites(testsuites): | ||
"""(Recursive) filtering of (suites of) tests""" | ||
test_filter = getattr(__builtin__, '__test_filter')['function'] | ||
|
||
res = type(testsuites)() | ||
|
||
for ts in testsuites: | ||
# ts is either a test or testsuite of more tests | ||
if isinstance(ts, TestSuite): | ||
res.addTest(filter_testsuites(ts)) | ||
else: | ||
if re.search(test_filter, ts._testMethodName): | ||
res.addTest(ts) | ||
return res | ||
|
||
|
||
class VscScanningLoader(ScanningLoader): | ||
"""The class to look for tests""" | ||
|
||
TEST_LOADER_MODULE = __name__ | ||
|
||
def loadTestsFromModule(self, module): | ||
""" | ||
Support test module and function name based filtering | ||
""" | ||
testsuites = ScanningLoader.loadTestsFromModule(self, module) | ||
|
||
test_filter = getattr(__builtin__,'__test_filter') | ||
|
||
res = testsuites | ||
|
||
if test_filter['module'] is not None: | ||
name = module.__name__ | ||
if name in test_filter['allowmods']: | ||
# a parent name space | ||
pass | ||
elif re.search(test_filter['module'], name): | ||
if test_filter['function'] is not None: | ||
res = filter_testsuites(testsuites) | ||
# add parents (and module itself) | ||
pms = name.split('.') | ||
for pm_idx in range(len(pms)): | ||
pm = '.'.join(pms[:pm_idx]) | ||
if not pm in test_filter['allowmods']: | ||
test_filter['allowmods'].append(pm) | ||
else: | ||
res = type(testsuites)() | ||
|
||
return res | ||
|
||
|
||
class VscTestCommand(TestCommand): | ||
""" | ||
The cmdclass for testing | ||
""" | ||
|
||
# make 2 new 'python setup.py test' options available | ||
user_options = TestCommand.user_options + [ | ||
('test-filterf=', 'f', "Regex filter on test function names"), | ||
('test-filterm=', 'F', "Regex filter on test (sub)modules"), | ||
] | ||
|
||
TEST_LOADER_CLASS = VscScanningLoader | ||
|
||
def initialize_options(self): | ||
""" | ||
Add attributes for new commandline options and set test_loader | ||
""" | ||
TestCommand.initialize_options(self) | ||
self.test_filterm = None | ||
self.test_filterf = None | ||
self.test_loader = '%s:%s' % (self.TEST_LOADER_CLASS.TEST_LOADER_MODULE, self.TEST_LOADER_CLASS.__name__) | ||
log.info("test_loader set to %s" % self.test_loader) | ||
|
||
def setup_sys_path(self): | ||
""" | ||
Prepare sys.path to be able to | ||
use the modules provided by this package (assumeing they are in 'lib') | ||
use any scripts as modules (for unittesting) | ||
use the test modules as modules (for unittesting) | ||
Returns a list of directories to cleanup | ||
""" | ||
cleanup = [] | ||
|
||
# determine the base directory of the repository | ||
# we will assume that the tests are called from | ||
# a 'setup.py' like file in the basedirectory | ||
# (but could be called anything, as long as it is in the basedir) | ||
setup_py = os.path.abspath(sys.argv[0]) | ||
log.info('run_tests from %s' % setup_py) | ||
base_dir = os.path.dirname(setup_py) | ||
|
||
# make a lib dir to trick setup.py to package this properly | ||
# and git ignore empty dirs, so recreate it if necessary | ||
lib_dir = os.path.join(base_dir, 'lib') | ||
if not os.path.exists(lib_dir): | ||
os.mkdir(lib_dir) | ||
cleanup.append(lib_dir) | ||
|
||
test_dir = os.path.join(base_dir, DEFAULT_TEST_SUITE) | ||
if os.path.isdir(test_dir): | ||
sys.path.insert(0, test_dir) | ||
else: | ||
raise Exception("Can't find location of testsuite directory %s in %s" % (DEFAULT_TEST_SUITE, base_dir)) | ||
|
||
# make sure we can import the script as a module | ||
scripts_dir = os.path.join(base_dir, 'bin') | ||
if os.path.isdir(scripts_dir): | ||
sys.path.insert(0, scripts_dir) | ||
|
||
return cleanup | ||
|
||
def reload_vsc_modules(self): | ||
""" | ||
Cleanup and restore vsc namespace becasue we use vsc namespace tools very early | ||
So we need to make sure they are picked up from the paths as specified | ||
in setup_sys_path, not to mix with installed and already loaded modules | ||
""" | ||
loaded_vsc_modules = [name for name in sys.modules.keys() if name == 'vsc' or name.startswith('vsc.')] | ||
reload_vsc_modules = [] | ||
for name in loaded_vsc_modules: | ||
if hasattr(sys.modules[name], '__file__'): | ||
# only actual modules | ||
reload_vsc_modules.append(name) | ||
del(sys.modules[name]) | ||
|
||
# reimport | ||
for name in reload_vsc_modules: | ||
__import__(name) | ||
|
||
def run_tests(self): | ||
""" | ||
Actually run the tests, but start with | ||
passing the filter options via __builtin__ | ||
set sys.path | ||
reload vsc modules | ||
""" | ||
getattr(__builtin__,'__test_filter').update({ | ||
'function': self.test_filterf, | ||
'module': self.test_filterm, | ||
}) | ||
|
||
cleanup = self.setup_sys_path() | ||
|
||
self.reload_vsc_modules() | ||
|
||
res = TestCommand.run_tests(self) | ||
|
||
# clenaup any diretcories created | ||
for directory in cleanup: | ||
shutil.rmtree(directory) | ||
|
||
return res | ||
|
||
# shared target config | ||
SHARED_TARGET = { | ||
'url': '', | ||
|
@@ -184,6 +338,8 @@ def run(self): | |
"egg_info": vsc_egg_info, | ||
"bdist_rpm": vsc_bdist_rpm, | ||
}, | ||
'cmdclass': {'test': VscTestCommand}, | ||
'test_suite': DEFAULT_TEST_SUITE, | ||
} | ||
|
||
|
||
|
@@ -265,7 +421,7 @@ def build_setup_cfg_for_bdist_rpm(target): | |
setup_cfg.close() | ||
|
||
|
||
def action_target(target, setupfn=setup, extra_sdist=[]): | ||
def action_target(target, setupfn=setup, extra_sdist=[], urltemplate=None): | ||
# EXTRA_SDIST_FILES.extend(extra_sdist) | ||
|
||
do_cleanup = True | ||
|
@@ -283,6 +439,11 @@ def action_target(target, setupfn=setup, extra_sdist=[]): | |
if do_cleanup: | ||
cleanup() | ||
|
||
if urltemplate: | ||
target['url'] = urltemplate % target | ||
if 'github' in urltemplate: | ||
target['download_url'] = "%s/tarball/master" % target['url'] | ||
|
||
build_setup_cfg_for_bdist_rpm(target) | ||
x = parse_target(target) | ||
|
||
|
Oops, something went wrong.