123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- #!/usr/bin/env python
- # coding: utf-8
- # Copyright (C) 1994-2018 Altair Engineering, Inc.
- # For more information, contact Altair at www.altair.com.
- #
- # This file is part of the PBS Professional ("PBS Pro") software.
- #
- # Open Source License Information:
- #
- # PBS Pro is free software. You can redistribute it and/or modify it under the
- # terms of the GNU Affero General Public License as published by the Free
- # Software Foundation, either version 3 of the License, or (at your option) any
- # later version.
- #
- # PBS Pro is distributed in the hope that it will be useful, but WITHOUT ANY
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- # FOR A PARTICULAR PURPOSE.
- # See the GNU Affero General Public License for more details.
- #
- # You should have received a copy of the GNU Affero General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
- # Commercial License Information:
- #
- # For a copy of the commercial license terms and conditions,
- # go to: (http://www.pbspro.com/UserArea/agreement.html)
- # or contact the Altair Legal Department.
- #
- # Altair’s dual-license business model allows companies, individuals, and
- # organizations to create proprietary derivative works of PBS Pro and
- # distribute them - whether embedded or bundled with other software -
- # under a commercial license agreement.
- #
- # Use of Altair’s trademarks, including but not limited to "PBS™",
- # "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
- # trademark licensing policies.
- import sys
- import os
- import getopt
- import logging
- import logging.config
- import platform
- import errno
- import signal
- import importlib
- import ptl
- import nose
- from nose.plugins.base import Plugin
- from ptl.lib.pbs_testlib import PtlConfig
- from distutils.version import LooseVersion
- from ptl.utils.pbs_cliutils import CliUtils
- from ptl.utils.plugins.ptl_test_loader import PTLTestLoader
- from ptl.utils.plugins.ptl_test_runner import PTLTestRunner
- from ptl.utils.plugins.ptl_test_db import PTLTestDb
- from ptl.utils.plugins.ptl_test_info import PTLTestInfo
- from ptl.utils.plugins.ptl_test_tags import PTLTestTags
- from ptl.utils.plugins.ptl_test_data import PTLTestData
- # trap SIGINT and SIGPIPE
- def trap_exceptions(etype, value, tb):
- sys.excepthook = sys.__excepthook__
- if issubclass(etype, IOError) and value.errno == errno.EPIPE:
- pass
- else:
- sys.__excepthook__(etype, value, tb)
- sys.excepthook = trap_exceptions
- def sighandler(signum, frames):
- signal.alarm(0)
- raise KeyboardInterrupt('Signal %d received' % (signum))
- # join process group of caller makes it possible to programmatically interrupt
- # when run in a subshell
- if os.getpgrp() != os.getpid():
- os.setpgrp()
- signal.signal(signal.SIGINT, sighandler)
- signal.signal(signal.SIGTERM, sighandler)
- def usage():
- msg = []
- msg += ['Usage: ' + os.path.basename(sys.argv[0]) + ' [OPTION]\n\n']
- msg += [' Test harness used to run or list test suites and test ' +
- 'cases\n\n']
- msg += ['-f <file names>: comma-separated list of file names to run\n']
- msg += ['-F: set logging format to include timestamp and level\n']
- msg += ['-g <testgroup file>: path to file containing comma-separated']
- msg += [' list of testsuites\n']
- msg += ['-h: display usage information\n']
- msg += ['-i: show test info\n']
- msg += ['-l <level>: log level\n']
- msg += ['-L: display list of tests\n']
- msg += ['-o <logfile>: log file name\n']
- msg += ['-p <param>: test parameter. Comma-separated list of key=val']
- msg += [' pairs. Note that the comma can not be used in val\n']
- msg += ['-t <test suites>: comma-separated list of test suites to run\n']
- msg += ['--exclude=<names>: comma-separated string of tests to exclude\n']
- msg += ['--user-plugins=<names>: comma-separated list of key=val of']
- msg += [' user plugins to load, where key is module and val is']
- msg += [' classname of plugin which is subclass of nose.plugins.base']
- msg += ['.Plugin\n']
- msg += ['--db-type=<type>: Type of database to use.']
- msg += [' can be one of "html", "file", "sqlite", "pgsql", "json".']
- msg += [' Default to "file"\n']
- msg += ['--db-name=<name>: database name. Default to']
- msg += [' ptl_test_results.db\n']
- msg += ['--db-access=<path>: Path to a file that defines db options '
- '(PostreSQL only)\n']
- msg += ['--lcov-bin=<bin>: path to lcov binary. Defaults to lcov\n']
- msg += ['--genhtml-bin=<bin>: path to genhtml binary. '
- 'Defaults to genhtml\n']
- msg += ['--lcov-data=<dir>: path to directory containig .gcno files\n']
- msg += ['--lcov-out=<dir>: path to output directory\n']
- msg += ['--lcov-baseurl=<url>: use <url> as baseurl in html report\n']
- msg += ['--lcov-nosrc: don\'t include PBS source in coverage analysis.']
- msg += [' Default PBS source will be included in coverage analysis\n']
- msg += ['--log-conf=<file>: logging config file\n']
- msg += ['--min-pyver=<version>: minimum Python version\n']
- msg += ['--max-pyver=<version>: maximum Python version\n']
- msg += ['--param-file=<file>: get params from file. Overrides -p\n']
- msg += ['--post-analysis-data=<dir>: path to post analysis data' +
- ' directory\n']
- msg += ['--max-postdata-threshold=<count>: max post analysis data' +
- ' threshold per testsuite. Defaults to 10. <count>=0 will' +
- ' disable this threshold\n']
- msg += ['--tc-failure-threshold=<count>: test case failure threshold' +
- ' per testsuite. Defaults to 10. <count>=0 will disable this' +
- ' threshold\n']
- msg += ['--cumulative-tc-failure-threshold=<count>: cumulative test' +
- ' case failure threshold. Defaults to 100. <count>=0 will' +
- ' disable this threshold.\n ' +
- ' Must be greater or equal to' +
- ' \'tc-failure-threshold\'\n']
- msg += ['--stop-on-failure: if set, stop when one of multiple tests ' +
- 'fails\n']
- msg += ['--timeout=<seconds>: duration after which no test suites are '
- 'run\n']
- msg += ['--follow-child: if set, walk the test hierarchy and run ' +
- 'each test\n']
- msg += ['--tags=<tag>: Select only tests that have <tag> tag.']
- msg += [' can be applied multiple times\n']
- msg += [' Format: [!]tag[,tag]\n']
- msg += [' Example:\n']
- msg += [' smoke - This will select all tests which has']
- msg += [' "smoke" tag\n']
- msg += [' !smoke - This will select all tests which']
- msg += [' doesn\'t have "smoke" tag\n']
- msg += [' smoke,regression - This will select all tests']
- msg += [' which has both "smoke" and "regression" tag\n']
- msg += ['--eval-tags=\'<Python expression>\': Select only tests for whose']
- msg += [' tags evaluates <Python expression> to True.']
- msg += [' can be applied multiple times\n']
- msg += [' Example:\n']
- msg += [' \'smoke and (not regression)\' - This will select']
- msg += [' all tests which has "smoke" tag and does\'t have "regression"']
- msg += [' tag\n']
- msg += [' \'priority>4\' - This will select all tests which']
- msg += [' has "priority" tag and its value is >4\n']
- msg += ['--tags-info: List all selected test suite (or test cases if ']
- msg += ['--verbose applied)\n']
- msg += [
- ' used with --tags or --eval-tags (also -t or --exclude']
- msg += [' can be applied to limit selection)\n']
- msg += ['--list-tags: List all currenlty used tags\n']
- msg += [
- '--verbose: show verbose output (used with -i, -L or --tag-info)\n']
- msg += ['--version: show version number and exit\n']
- print ''.join(msg)
- if __name__ == '__main__':
- if len(sys.argv) < 2:
- usage()
- sys.exit(1)
- level = 'INFOCLI2'
- fmt = '%(asctime)-15s %(levelname)-8s %(message)s'
- outfile = None
- dbtype = None
- dbname = None
- dbaccess = None
- testfiles = None
- testsuites = None
- testparam = None
- testgroup = None
- list_test = False
- showinfo = False
- follow = False
- excludes = None
- stoponfail = False
- paramfile = None
- logconf = None
- minpyver = None
- maxpyver = None
- verbose = False
- lcov_data = None
- lcov_bin = None
- lcov_out = None
- lcov_nosrc = False
- lcov_baseurl = None
- genhtml_bin = None
- timeout = None
- nosedebug = False
- only_info = False
- tags = []
- eval_tags = []
- tags_info = False
- list_tags = False
- post_data_dir = None
- gen_ts_tree = False
- tc_failure_threshold = 10
- cumulative_tc_failure_threshold = 100
- max_postdata_threshold = 10
- user_plugins = None
- PtlConfig()
- largs = ['exclude=', 'log-conf=', 'timeout=']
- largs += ['param-file=', 'min-pyver=', 'max-pyver=']
- largs += ['db-name=', 'db-access=', 'db-type=', 'genhtml-bin=']
- largs += ['lcov-bin=', 'lcov-data=', 'lcov-out=', 'lcov-nosrc']
- largs += ['lcov-baseurl=', 'tags=', 'eval-tags=', 'tags-info', 'list-tags']
- largs += ['version', 'verbose', 'follow-child']
- largs += ['stop-on-failure', 'enable-nose-debug']
- largs += ['post-analysis-data=', 'gen-ts-tree']
- largs += ['tc-failure-threshold=', 'cumulative-tc-failure-threshold=']
- largs += ['max-postdata-threshold=', 'user-plugins=']
- try:
- opts, args = getopt.getopt(
- sys.argv[1:], 'f:il:t:o:p:g:hLF', largs)
- except:
- sys.stderr.write('Unrecognized option. Exiting\n')
- usage()
- sys.exit(1)
- if args:
- sys.stderr.write('Invalid usage. Exiting\n')
- usage()
- sys.exit(1)
- for o, val in opts:
- if o == '-i':
- showinfo = True
- list_test = False
- gen_ts_tree = False
- only_info = True
- elif o == '-L':
- showinfo = False
- list_test = True
- gen_ts_tree = False
- only_info = True
- elif o == '--gen-ts-tree':
- showinfo = False
- list_test = False
- gen_ts_tree = True
- only_info = True
- elif o == '-l':
- level = val
- elif o == '-o':
- outfile = CliUtils.expand_abs_path(val)
- elif o == '-f':
- testfiles = CliUtils.expand_abs_path(val)
- elif o == '-t':
- testsuites = val
- elif o == '--user-plugins':
- user_plugins = val
- elif o == '--tags':
- tags.append(val.strip())
- elif o == '--eval-tags':
- eval_tags.append(val.strip())
- elif o == '--tags-info':
- tags_info = True
- elif o == '--list-tags':
- list_tags = True
- elif o == '--exclude':
- excludes = val
- elif o == '-F':
- fmt = '%(asctime)-15s %(levelname)-8s %(message)s'
- elif o == '-p':
- testparam = val
- elif o == '-g':
- testgroup = val
- elif o == '--timeout':
- timeout = int(val)
- elif o == '--db-type':
- dbtype = val
- elif o == '--db-name':
- dbname = CliUtils.expand_abs_path(val)
- elif o == '--db-access':
- dbaccess = CliUtils.expand_abs_path(val)
- elif o == '--genhtml-bin':
- genhtml_bin = CliUtils.expand_abs_path(val)
- elif o == '--lcov-bin':
- lcov_bin = CliUtils.expand_abs_path(val)
- elif o == '--lcov-data':
- lcov_data = CliUtils.expand_abs_path(val)
- elif o == '--lcov-out':
- lcov_out = CliUtils.expand_abs_path(val)
- elif o == '--lcov-nosrc':
- lcov_nosrc = True
- elif o == '--lcov-baseurl':
- lcov_baseurl = val
- elif o == '--param-file':
- paramfile = CliUtils.expand_abs_path(val)
- elif o == '--stop-on-failure':
- stoponfail = True
- elif o == '--follow-child':
- follow = True
- elif o == '--log-conf':
- logconf = val
- elif o == '--min-pyver':
- minpyver = val
- elif o == '--max-pyver':
- maxpyver = val
- elif o == '--enable-nose-debug':
- nosedebug = True
- elif o == '--verbose':
- verbose = True
- elif o == '--post-analysis-data':
- post_data_dir = CliUtils.expand_abs_path(val)
- elif o == '--tc-failure-threshold':
- tc_failure_threshold = val
- elif o == '--cumulative-tc-failure-threshold':
- cumulative_tc_failure_threshold = val
- elif o == '--max-postdata-threshold':
- max_postdata_threshold = val
- elif o == '-h':
- usage()
- sys.exit(0)
- elif o == '--version':
- print ptl.__version__
- sys.exit(0)
- else:
- sys.stderr.write('Unreocgnized option %s\n' % o)
- usage()
- sys.exit(1)
- if nosedebug:
- level = 'DEBUG'
- l = CliUtils.get_logging_level(level)
- if logconf:
- logging.config.fileConfig(logconf)
- else:
- logging.basicConfig(filename=outfile, filemode='w+', level=l,
- format=fmt)
- if outfile is not None:
- stream_hdlr = logging.StreamHandler()
- stream_hdlr.setLevel(l)
- stream_hdlr.setFormatter(logging.Formatter(fmt))
- ptl_logger = logging.getLogger('ptl')
- ptl_logger.addHandler(stream_hdlr)
- ptl_logger.setLevel(l)
- pyver = platform.python_version()
- if minpyver is not None and LooseVersion(pyver) < LooseVersion(minpyver):
- logging.error('Python version ' + str(pyver) + ' does not meet ' +
- 'required minimum version of ' + minpyver)
- sys.exit(1)
- if maxpyver is not None and LooseVersion(pyver) > LooseVersion(maxpyver):
- logging.error('Python version ' + str(pyver) + ' does not meet ' +
- 'required max version of ' + maxpyver)
- sys.exit(1)
- if showinfo and testsuites is None:
- logging.error(
- 'Testsuites names require (see -t) along with -i option!')
- sys.exit(1)
- try:
- tc_failure_threshold = int(tc_failure_threshold)
- if tc_failure_threshold < 0:
- raise ValueError
- except:
- _msg = 'Invalid value provided for testcase failure threshold, '
- _msg += 'please provide integer'
- logging.error(_msg)
- sys.exit(1)
- try:
- cumulative_tc_failure_threshold = int(cumulative_tc_failure_threshold)
- if cumulative_tc_failure_threshold < 0:
- raise ValueError
- except:
- _msg = 'Invalid value provided for cumulative-tc-failure-threshold, '
- _msg += 'please provide integer'
- logging.error(_msg)
- sys.exit(1)
- if cumulative_tc_failure_threshold < tc_failure_threshold:
- _msg = 'Value for cumulative-tc-failure-threshould should'
- _msg += ' be greater or equal to \'tc-failure-threshold\''
- logging.error(_msg)
- sys.exit(1)
- try:
- max_postdata_threshold = int(max_postdata_threshold)
- if max_postdata_threshold < 0:
- raise ValueError
- except:
- _msg = 'Invalid value provided for max-postdata-threshold, '
- _msg += 'please provide integer'
- logging.error(_msg)
- sys.exit(1)
- if outfile is not None and not os.path.isdir(os.path.dirname(outfile)):
- os.mkdir(os.path.dirname(outfile))
- if timeout is not None:
- signal.signal(signal.SIGALRM, sighandler)
- signal.alarm(timeout)
- if list_test:
- excludes = None
- testgroup = None
- follow = True
- if testfiles is not None:
- tests = testfiles.split(',')
- else:
- tests = os.getcwd()
- if testsuites is None:
- testsuites = 'PBSTestSuite'
- follow = True
- if only_info:
- testinfo = PTLTestInfo()
- testinfo.set_data(testsuites, list_test,
- showinfo, verbose, gen_ts_tree)
- plugins = (testinfo,)
- elif (tags_info or list_tags):
- loader = PTLTestLoader()
- testtags = PTLTestTags()
- loader.set_data(testgroup, testsuites, excludes, True)
- testtags.set_data(tags, eval_tags, tags_info, list_tags, verbose)
- plugins = (loader, testtags)
- else:
- loader = PTLTestLoader()
- testtags = PTLTestTags()
- runner = PTLTestRunner()
- db = PTLTestDb()
- data = PTLTestData()
- loader.set_data(testgroup, testsuites, excludes, follow)
- testtags.set_data(tags, eval_tags)
- runner.set_data(paramfile, testparam, lcov_bin, lcov_data, lcov_out,
- genhtml_bin, lcov_nosrc, lcov_baseurl,
- tc_failure_threshold, cumulative_tc_failure_threshold)
- db.set_data(dbtype, dbname, dbaccess)
- data.set_data(post_data_dir, max_postdata_threshold)
- plugins = (loader, testtags, runner, db, data)
- if user_plugins:
- for plugin in user_plugins.split(','):
- if '=' not in plugin:
- _msg = 'Invalid value (%s)' % (plugin)
- _msg += ' provided in user-plugins, it should be key value'
- _msg += ' pair where key is module name and value is class'
- _msg += ' name of plugin'
- logging.error(_msg)
- sys.exit(1)
- mod, clsname = plugin.split('=', 1)
- try:
- loaded_mod = importlib.import_module(mod)
- except ImportError:
- _msg = 'Failed to load module (%s)' % mod
- _msg += ' for plugin (%s)' % plugin
- logging.error(_msg)
- sys.exit(1)
- _plugin = getattr(loaded_mod, clsname, None)
- if not _plugin:
- _msg = 'Could not find class named "%s"' % clsname
- _msg += ' in module (%s)' % mod
- logging.error(_msg)
- sys.exit(1)
- if not issubclass(_plugin, Plugin):
- _msg = 'Plugin class (%s) should be subclass of ' % (clsname)
- _msg += 'nose.plugins.base.Plugin'
- logging.error(_msg)
- sys.exit(1)
- plugins += (_plugin(),)
- test_regex = r'(^(?:[\w]+|^)Test|pbs_|^test_[\(]*)'
- os.environ['NOSE_TESTMATCH'] = test_regex
- if nosedebug:
- os.environ['NOSE_VERBOSE'] = '7'
- if outfile:
- os.environ['NOSE_DEBUG_LOG'] = outfile
- else:
- os.environ['NOSE_VERBOSE'] = '2'
- if stoponfail:
- os.environ['NOSE_STOP'] = '1'
- nose.main(defaultTest=tests, argv=[sys.argv[0]], plugins=plugins)
|