def test_perf(params=''): """ Runs the performance tests against the configured service and produce the report in dist/ :params str params: Parameters to pass to Funkload bench Examples:: fab dist.test_perf fab dist.test_perf:"-c 1:15 -D 1" """ try: import funkload except ImportError: abort('Funkload module missing, please install it first') # Create report folder if needed report_dir = dist_join('report/html') if not os.path.exists(report_dir): os.makedirs(report_dir) # Run the Funkload tests in perf test folder with lcd(rel_join('tests/perf')): with settings(show('running','stdout')): local('fl-run-bench %s tests.py MultiprojectTestCase.test_smoke' % params) local('fl-build-report -o %s --html smoke-bench.xml' % report_dir) logger.info('Testing completed. Test report can be found in: %s' % report_dir)
def test_perf(params=''): """ Runs the performance tests against the configured service and produce the report in dist/ :params str params: Parameters to pass to Funkload bench Examples:: fab dist.test_perf fab dist.test_perf:"-c 1:15 -D 1" """ try: import funkload except ImportError: abort('Funkload module missing, please install it first') # Create report folder if needed report_dir = dist_join('report/html') if not os.path.exists(report_dir): os.makedirs(report_dir) # Run the Funkload tests in perf test folder with lcd(rel_join('tests/perf')): with settings(show('running', 'stdout')): local('fl-run-bench %s tests.py MultiprojectTestCase.test_smoke' % params) local('fl-build-report -o %s --html smoke-bench.xml' % report_dir) logger.info('Testing completed. Test report can be found in: %s' % report_dir)
def test(case='', config='tests.ini'): """ Runs the functional tests against the setup specified in configuration :param str case: Name or path to case file :param str config: Path to config file, relative to current directory Examples:: fab dist.test:smoke fab dist.test:path/to/case.py fab dist.test:smoke,~/firefox.ini fab dist.test:smoke,config=path/to/config.ini """ try: from nose.core import TestProgram from nose.plugins import Plugin except ImportError: TestProgram = None Plugin = object return abort('For running tests, Nose testing framework is required. Please install it first: "pip install nose"') if not case: return abort('Please provide either name or path to test case') # Determine the case file: name or path accepted webtests_dir = rel_join('tests/webtests') casepath = os.path.abspath(case) if case.endswith('.py') else join(webtests_dir, 'cases/%s.py' % case) configpath = os.path.join(os.curdir, os.path.expanduser(config)) logger.info('Running functional tests from: %s' % casepath) logger.info('Reading tests configuration from: %s' % configpath) class TestConfigPlugin(Plugin): """ Simple Nose plugin to set test configuration path to testcase:: class MyTestcase(unittest.TestCase) def setUp(self): self.config_path """ name = 'testconfig' can_configure = True enabled = True def options(self, parser, env): pass def configure(self, options, conf): pass def startTest(self, test): test_case = test.test.__class__ test_case.config_path = configpath TestProgram(argv=['fab', casepath], addplugins=[TestConfigPlugin()])
def trac_admin(env, cmd, sudoer=None): """ Runs trac-admin command on specified environment with permiss :param env: Name or path to the environment :param cmd: Command to pass to trac-admin :param sudoer: Optional sudo user, defaults to Apache user Examples:: fab dist.trac_admin:home,help fab dist.trac_admin:"home","mp deploy" fab dist.trac_admin:env="home",cmd="mp deploy",sudoer="root" fab dist.trac_admin:"/var/www/trac/projects/projectx","upgrade" fab dist.trac_admin:"/var/www/trac/projects/projectx","upgrade","www-data" """ trac_root_dir = config['trac_root'] sudoer = sudoer or config['webserver_user'] if sudoer == 'root': sudoer = None # Check if path is given, otherwise consider it project name if not exists(env): env = os.path.join(trac_root_dir, 'projects', env) if not exists(env): return abort('Given environment "%s" cannot be found on server' % env) with cd(env): tracadmin_cmd = 'trac-admin %s %s' % (env, cmd) sudo(tracadmin_cmd, user=sudoer)
def deploy(package, opts=''): """ Uploads the given package to remote host and deploys it there. :param str package: Path to tar.gz package in local file system. In case of wildcard, all the matched package are deployed. Package can be in formats: tar.gz (custom package structure, deb, rpm :param str opts: Optional parameters to pass to deploying app (easy_install, rpm, dpkg, deploy.sh) Examples:: fab dist.deploy:package=../../package.tar.gz fab dist.deploy:package=../../package.tar.gz,opts="--theme --activate" fab dist.deploy:package=../../*.deb fab dist.deploy:package=../../*.deb,opts='--force' """ # Use glob to find package from local filesystem (glob supports wildcards) pmatches = glob(os.path.expandvars(os.path.expanduser(package))) if not pmatches: return abort('No package can be found with name: %s' % package) # Iterate matched packages # Upload package(s) to remote host and determine the name of release folder for pmatch in pmatches: package = os.path.normpath(pmatch) # Get the release name from package: drop the extension and version packagename = os.path.basename(package) releasename, releaseversion, releaseextension = split_package_name( packagename) # Upload package to home directory, with same as the orig logger.info('Uploading the package: %s -> %s' % (package, packagename)) put(package, packagename) logger.info('Release name: %s' % releasename) # Run the package specific deployment actions if releaseextension == 'tar.gz': deploy_targz(packagename, opts) elif releaseextension == 'egg': opts = opts or '-Z' sudo('easy_install %s %s' % (opts, packagename)) elif releaseextension == 'deb': opts = opts or '--install' sudo('dpkg %s %s' % (opts, packagename)) elif releaseextension == 'rpm': opts = opts or '-Uvh' sudo('rpm %s %s' % (opts, packagename)) # Remove the package with cd('~'): sudo('rm -f ./%s' % packagename) # Restart apache logger.info('Restarting apache') apache = Apache() apache.restart()
def deploy(package, opts=''): """ Uploads the given package to remote host and deploys it there. :param str package: Path to tar.gz package in local file system. In case of wildcard, all the matched package are deployed. Package can be in formats: tar.gz (custom package structure, deb, rpm :param str opts: Optional parameters to pass to deploying app (easy_install, rpm, dpkg, deploy.sh) Examples:: fab dist.deploy:package=../../package.tar.gz fab dist.deploy:package=../../package.tar.gz,opts="--theme --activate" fab dist.deploy:package=../../*.deb fab dist.deploy:package=../../*.deb,opts='--force' """ # Use glob to find package from local filesystem (glob supports wildcards) pmatches = glob(os.path.expandvars(os.path.expanduser(package))) if not pmatches: return abort('No package can be found with name: %s' % package) # Iterate matched packages # Upload package(s) to remote host and determine the name of release folder for pmatch in pmatches: package = os.path.normpath(pmatch) # Get the release name from package: drop the extension and version packagename = os.path.basename(package) releasename, releaseversion, releaseextension = split_package_name(packagename) # Upload package to home directory, with same as the orig logger.info('Uploading the package: %s -> %s' % (package, packagename)) put(package, packagename) logger.info('Release name: %s' % releasename) # Run the package specific deployment actions if releaseextension == 'tar.gz': deploy_targz(packagename, opts) elif releaseextension == 'egg': opts = opts or '-Z' sudo('easy_install %s %s' % (opts, packagename)) elif releaseextension == 'deb': opts = opts or '--install' sudo('dpkg %s %s' % (opts, packagename)) elif releaseextension == 'rpm': opts = opts or '-Uvh' sudo('rpm %s %s' % (opts, packagename)) # Remove the package with cd('~'): sudo('rm -f ./%s' % packagename) # Restart apache logger.info('Restarting apache') apache = Apache() apache.restart()
def upload(package, rdir=''): """ Uploads the given package to remote host :param str package: Path to package, absolute or relative :param str rdir: Remote directory where to upload the package. Defaults to users home directory Examples:: fab dist.upload:package.tar.gz,/tmp fab dist.upload:package=../../package.tar.gz fab dist.upload:package=../../packa*.tar.gz fab dist.upload:package=../../package.tar.gz,rdir=/tmp .. NOTE:: Special paths, containing environment variables or tilde characters are not supported. """ # Use glob to find package from local filesystem (glob supports wildcards) pmatches = glob(os.path.expandvars(os.path.expanduser(package))) if not pmatches: return abort('No package can be found with name: %s' % package) # Upload package(s) to remote host and determine the name of release folder for pmatch in pmatches: package = os.path.normpath(pmatch) # Get the release name from package: drop the extension and version packagename = os.path.basename(package) target_path = join(rdir, packagename) if rdir else packagename target_dir = os.path.dirname(target_path) # Upload package to specified directory, with the same name as the orig logger.info('Uploading the package: %s -> %s' % (package, target_path)) if not exists(target_dir): run(target_dir) put(package, target_path)
def build(release='false', compress='false', docs='', pkgs='tar', version='', ext='true', extbranch='master'): """ Create distributable packages. Builds eggs and tar.gz compressed packages, based on parameters. Also capable of downloading and patching external dependencies. :param release: Make release build or not. Release sets/increments the version number. Default 'false' :param compress: Compress js/css files nor not. Default 'false' :param docs: Names of the documentation targets to build. Default '' means no doc building :param pkgs: Package formats to build sources into, separated with space. Valid values: tar deb rpm :param version: Version number to set for whole package. Default '' -> take the version from VERSION.txt (or default to 1.0.0) :param ext: Build and include external modules into big package. Default is 'false'. If ext is 'all', builds also other than own forks (GitResources). :param extbranch: Defines from which branch the fork packages are to be built from. Examples:: fab dist.build fab dist.build:release=true,docs=html fab dist.build:compress=true,version=1.2.3,pkgs="deb tar rpm" .. NOTE:: Python modules get their version number from setup.py """ # NOTE: Fabric parameters are always in string format # Get the list of package formats (space delimeter) pkg_formats = pkgs.split(' ') # Determine the version: parameter vs. VERSION.txt vs. default if not version: version_path = os.path.join(PROJECT_DIR, 'VERSION.txt') if os.path.exists(version_path): version = set_version_in_file(version_path, version) else: version = '1.0.0' # Create package name from pkg name and version package_name = '%s-%s' % (PKG_NAME, version) pkg_join = lambda *path: join(BUILD_DIR, package_name, *path) logger.info('Preparing build env...') # Copy relevant files to build dir (so that they can be edited directly) shutil.rmtree(BUILD_DIR, ignore_errors=True) del SRC_DIRS[SRC_DIRS.index('libs')] del SRC_DIRS[SRC_DIRS.index('etc')] for src_dir in SRC_DIRS: shutil.copytree(src_dir, pkg_join(src_dir)) if not os.path.exists(DIST_DIR): os.makedirs(DIST_DIR) # Copy additional files shutil.copy('README.rst', pkg_join('README')) os.makedirs(pkg_join('scripts')) shutil.copy(rel_join('scripts/deploy.sh'), pkg_join('scripts/deploy.sh')) shutil.copy(rel_join('scripts/update.py'), pkg_join('scripts/update.py')) shutil.copytree(rel_join('scripts/hooks'), pkg_join('scripts/hooks')) shutil.copytree(rel_join('scripts/cron'), pkg_join('scripts/cron')) # Build documentation if docs: # List the target formats/builders builddoc(docs, pkg_join('docs')) else: # Ensure there is at least empty directory (for archive) os.makedirs(pkg_join('docs')) # Build configuration buildetc(outdir=pkg_join('etc'), section='DEFAULT') # Increment version of each plugin if making a release if get_bool_str(release): logger.info('Setting/incrementing version numbers...') for setuppy_path in get_files(pkg_join('plugins'), 'setup.py', recursive=True): # Check if plugin folder contains VERSION.txt (non-versioned file) version_path = os.path.join(os.path.dirname(setuppy_path), 'VERSION.txt') if not os.path.exists(version_path): logger.warning('VERSION.txt missing, using version found in setup.py') version_path = setuppy_path # Set version information in file. # NOTE: If version is empty, it is determined from version file (either VERSION.txt or setup.py) set_version_in_file(version_path, version) # Optional compress (edits copied files under build) if get_bool_str(compress): logger.info('Compressing files...') with settings(warn_only=True): # Compress theme resources for respath in get_files(pkg_join('themes'), '*.css', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) for respath in get_files(pkg_join('themes'), '*.js', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) # Compress plugin resources for respath in get_files(pkg_join('plugins'), '*.css', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) for respath in get_files(pkg_join('plugins'), '*.js', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) # Aggregate js+css resources into bundle for template_path in get_files(pkg_join('themes'), 'resources.html', recursive=True): logger.info('Template path: %s' % template_path) bundle(template_path, pkg_join('themes/default/htdocs')) logger.info('Compression completed.') # Build eggs and source packages (in build dir) logger.info('Laying eggs and source packages...') with lcd(pkg_join()): for plugin_dir in PLUGIN_DIRS: with lcd(plugin_dir): local('python setup.py bdist_egg') local('python setup.py sdist') # Build external plugins as well, optionally even non-fork plugins # Retrieve and build external plugins and copy the artifacts into plugins folder. # NOTE: Next egg copying will put them into correct place, no need to rerun the file copy allext = 'true' if ext.lower() == 'all' else 'false' if get_bool_str(ext) or allext: buildext(allext=allext,branch=extbranch) for egg in get_files(build_join('ext'), '*.egg', recursive=True): shutil.copy(egg, pkg_join('plugins', os.path.basename(egg))) # Copy eggs and sdisted files from plugins directory to dist and plugin directories for egg in get_files(pkg_join('plugins/multiproject'), '*.egg', recursive=True): shutil.copy(egg, dist_join(os.path.basename(egg))) shutil.copy(egg, pkg_join('plugins')) for targz in get_files(pkg_join('plugins'), '*.tar.gz', recursive=True): shutil.copy(targz, dist_join(os.path.basename(targz))) # Create dist if not available if not os.path.exists(DIST_DIR): os.makedirs(DIST_DIR) # Create one big package to contain 'em all if 'tar.gz' in pkg_formats or 'tar' in pkg_formats: logger.info('Creating complete .tar.gz package...') # TODO: Archive could be implemented in pure python # TODO: These patterns seem to assume build dir == project dir exclude_patterns = [ '.*', 'tests', 'documents', '*.egg-info', 'ext/libs', 'ext/plugins', 'sample', 'build', 'plugins/multiproject' ] exclude_param = ' '.join(['--exclude=%s' % pt for pt in exclude_patterns]) with lcd(BUILD_DIR): #local('tar -czf %s.tar.gz --exclude-vcs %s %s' % # (dist_join(package_name), exclude_param, package_name)) local('tar -czf %s.tar.gz %s' % (dist_join(package_name), package_name)) # Debian package if 'deb' in pkg_formats: logger.info('Creating .deb package...') try: from stdeb import command except ImportError: command = None abort('Module stddep (http://pypi.python.org/pypi/stdeb) was not found, cannot build .deb package') # Run setup.py bdist_deb inside each plugin. It generates deb_dist/<pkgname>/ directory for setuppy_path in get_files(os.path.abspath(pkg_join('plugins')), 'setup.py', recursive=True): plugin_dir = os.path.dirname(setuppy_path) with settings(hide('stdout', 'stderr')): with lcd(plugin_dir): local('python setup.py --command-packages=stdeb.command bdist_deb') # Package command needs to be run inside the generated folder. Find it and run the command for debdist_path in get_files(os.path.join(plugin_dir, 'deb_dist'), 'setup.py', recursive=True): with lcd(os.path.dirname(debdist_path)): local('dpkg-buildpackage -rfakeroot -uc -us') # Copy .deb packages to dist for deb_path in get_files(pkg_join('plugins'), '*.deb', recursive=True): shutil.copy(deb_path, dist_join(os.path.basename(deb_path))) # Redhat package if 'rpm' in pkg_formats: logger.info('Creating .rpm package...') with settings(hide('stdout', 'running')): # Run setup.py bdist_rpm inside each plugin. It generates deb_dist/<pkgname>/ directory for setuppy_path in get_files(os.path.abspath(pkg_join('plugins')), 'setup.py', recursive=True): plugin_dir = os.path.dirname(setuppy_path) with lcd(plugin_dir): local('python setup.py bdist_rpm') # Copy .rpm packages to dist for rpm_path in get_files(pkg_join('plugins'), '*.rpm', recursive=True): shutil.copy(rpm_path, dist_join(os.path.basename(rpm_path))) logger.info('Building completed.')
def build(release='false', compress='false', docs='', pkgs='tar', version='', ext='true', extbranch='master'): """ Create distributable packages. Builds eggs and tar.gz compressed packages, based on parameters. Also capable of downloading and patching external dependencies. :param release: Make release build or not. Release sets/increments the version number. Default 'false' :param compress: Compress js/css files nor not. Default 'false' :param docs: Names of the documentation targets to build. Default '' means no doc building :param pkgs: Package formats to build sources into, separated with space. Valid values: tar deb rpm :param version: Version number to set for whole package. Default '' -> take the version from VERSION.txt (or default to 1.0.0) :param ext: Build and include external modules into big package. Default is 'false'. If ext is 'all', builds also other than own forks (GitResources). :param extbranch: Defines from which branch the fork packages are to be built from. Examples:: fab dist.build fab dist.build:release=true,docs=html fab dist.build:compress=true,version=1.2.3,pkgs="deb tar rpm" .. NOTE:: Python modules get their version number from setup.py """ # NOTE: Fabric parameters are always in string format # Get the list of package formats (space delimeter) pkg_formats = pkgs.split(' ') # Determine the version: parameter vs. VERSION.txt vs. default if not version: version_path = os.path.join(PROJECT_DIR, 'VERSION.txt') if os.path.exists(version_path): version = set_version_in_file(version_path, version) else: version = '1.0.0' # Create package name from pkg name and version package_name = '%s-%s' % (PKG_NAME, version) pkg_join = lambda *path: join(BUILD_DIR, package_name, *path) logger.info('Preparing build env...') # Copy relevant files to build dir (so that they can be edited directly) shutil.rmtree(BUILD_DIR, ignore_errors=True) del SRC_DIRS[SRC_DIRS.index('libs')] del SRC_DIRS[SRC_DIRS.index('etc')] for src_dir in SRC_DIRS: shutil.copytree(src_dir, pkg_join(src_dir)) if not os.path.exists(DIST_DIR): os.makedirs(DIST_DIR) # Copy additional files shutil.copy('README.rst', pkg_join('README')) os.makedirs(pkg_join('scripts')) shutil.copy(rel_join('scripts/deploy.sh'), pkg_join('scripts/deploy.sh')) shutil.copy(rel_join('scripts/update.py'), pkg_join('scripts/update.py')) shutil.copytree(rel_join('scripts/hooks'), pkg_join('scripts/hooks')) shutil.copytree(rel_join('scripts/cron'), pkg_join('scripts/cron')) # Build documentation if docs: # List the target formats/builders builddoc(docs, pkg_join('docs')) else: # Ensure there is at least empty directory (for archive) os.makedirs(pkg_join('docs')) # Build configuration buildetc(outdir=pkg_join('etc'), section='DEFAULT') # Increment version of each plugin if making a release if get_bool_str(release): logger.info('Setting/incrementing version numbers...') for setuppy_path in get_files(pkg_join('plugins'), 'setup.py', recursive=True): # Check if plugin folder contains VERSION.txt (non-versioned file) version_path = os.path.join(os.path.dirname(setuppy_path), 'VERSION.txt') if not os.path.exists(version_path): logger.warning( 'VERSION.txt missing, using version found in setup.py') version_path = setuppy_path # Set version information in file. # NOTE: If version is empty, it is determined from version file (either VERSION.txt or setup.py) set_version_in_file(version_path, version) # Optional compress (edits copied files under build) if get_bool_str(compress): logger.info('Compressing files...') with settings(warn_only=True): # Compress theme resources for respath in get_files(pkg_join('themes'), '*.css', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) for respath in get_files(pkg_join('themes'), '*.js', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) # Compress plugin resources for respath in get_files(pkg_join('plugins'), '*.css', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) for respath in get_files(pkg_join('plugins'), '*.js', recursive=True): local('yui-compressor --charset utf-8 -o %s %s' % (respath, respath)) # Aggregate js+css resources into bundle for template_path in get_files(pkg_join('themes'), 'resources.html', recursive=True): logger.info('Template path: %s' % template_path) bundle(template_path, pkg_join('themes/default/htdocs')) logger.info('Compression completed.') # Build eggs and source packages (in build dir) logger.info('Laying eggs and source packages...') with lcd(pkg_join()): for plugin_dir in PLUGIN_DIRS: with lcd(plugin_dir): local('python setup.py bdist_egg') local('python setup.py sdist') # Build external plugins as well, optionally even non-fork plugins # Retrieve and build external plugins and copy the artifacts into plugins folder. # NOTE: Next egg copying will put them into correct place, no need to rerun the file copy allext = 'true' if ext.lower() == 'all' else 'false' if get_bool_str(ext) or allext: buildext(allext=allext, branch=extbranch) for egg in get_files(build_join('ext'), '*.egg', recursive=True): shutil.copy(egg, pkg_join('plugins', os.path.basename(egg))) # Copy eggs and sdisted files from plugins directory to dist and plugin directories for egg in get_files(pkg_join('plugins/multiproject'), '*.egg', recursive=True): shutil.copy(egg, dist_join(os.path.basename(egg))) shutil.copy(egg, pkg_join('plugins')) for targz in get_files(pkg_join('plugins'), '*.tar.gz', recursive=True): shutil.copy(targz, dist_join(os.path.basename(targz))) # Create dist if not available if not os.path.exists(DIST_DIR): os.makedirs(DIST_DIR) # Create one big package to contain 'em all if 'tar.gz' in pkg_formats or 'tar' in pkg_formats: logger.info('Creating complete .tar.gz package...') # TODO: Archive could be implemented in pure python # TODO: These patterns seem to assume build dir == project dir exclude_patterns = [ '.*', 'tests', 'documents', '*.egg-info', 'ext/libs', 'ext/plugins', 'sample', 'build', 'plugins/multiproject' ] exclude_param = ' '.join( ['--exclude=%s' % pt for pt in exclude_patterns]) with lcd(BUILD_DIR): #local('tar -czf %s.tar.gz --exclude-vcs %s %s' % # (dist_join(package_name), exclude_param, package_name)) local('tar -czf %s.tar.gz %s' % (dist_join(package_name), package_name)) # Debian package if 'deb' in pkg_formats: logger.info('Creating .deb package...') try: from stdeb import command except ImportError: command = None abort( 'Module stddep (http://pypi.python.org/pypi/stdeb) was not found, cannot build .deb package' ) # Run setup.py bdist_deb inside each plugin. It generates deb_dist/<pkgname>/ directory for setuppy_path in get_files(os.path.abspath(pkg_join('plugins')), 'setup.py', recursive=True): plugin_dir = os.path.dirname(setuppy_path) with settings(hide('stdout', 'stderr')): with lcd(plugin_dir): local( 'python setup.py --command-packages=stdeb.command bdist_deb' ) # Package command needs to be run inside the generated folder. Find it and run the command for debdist_path in get_files(os.path.join( plugin_dir, 'deb_dist'), 'setup.py', recursive=True): with lcd(os.path.dirname(debdist_path)): local('dpkg-buildpackage -rfakeroot -uc -us') # Copy .deb packages to dist for deb_path in get_files(pkg_join('plugins'), '*.deb', recursive=True): shutil.copy(deb_path, dist_join(os.path.basename(deb_path))) # Redhat package if 'rpm' in pkg_formats: logger.info('Creating .rpm package...') with settings(hide('stdout', 'running')): # Run setup.py bdist_rpm inside each plugin. It generates deb_dist/<pkgname>/ directory for setuppy_path in get_files(os.path.abspath(pkg_join('plugins')), 'setup.py', recursive=True): plugin_dir = os.path.dirname(setuppy_path) with lcd(plugin_dir): local('python setup.py bdist_rpm') # Copy .rpm packages to dist for rpm_path in get_files(pkg_join('plugins'), '*.rpm', recursive=True): shutil.copy(rpm_path, dist_join(os.path.basename(rpm_path))) logger.info('Building completed.')
Examples:: fab dist.test:smoke fab dist.test:path/to/case.py fab dist.test:smoke,~/firefox.ini fab dist.test:smoke,config=path/to/config.ini """ try: from nose.core import TestProgram from nose.plugins import Plugin except ImportError: TestProgram = None Plugin = object return abort( 'For running tests, Nose testing framework is required. Please install it first: "pip install nose"' ) if not case: return abort('Please provide either name or path to test case') # Determine the case file: name or path accepted webtests_dir = rel_join('tests/webtests') casepath = os.path.abspath(case) if case.endswith('.py') else join( webtests_dir, 'cases/%s.py' % case) configpath = os.path.join(os.curdir, os.path.expanduser(config)) logger.info('Running functional tests from: %s' % casepath) logger.info('Reading tests configuration from: %s' % configpath) class TestConfigPlugin(Plugin):