class Job(object): """ The Job class is a simple container for the tester and its associated output file object, the DAG, the process object, the exit codes, and the start and end times. """ def __init__(self, tester, job_dag, options): self.options = options self.__tester = tester self.specs = tester.specs self.__job_dag = job_dag self.timer = Timer() self.__outfile = None self.__start_time = clock() self.__end_time = None self.__previous_time = None self.__joined_out = '' self.report_timer = None self.__slots = None self.__meta_data = {} # Alternate text we want to print as part of our status instead of the # pre-formatted status text (LAUNCHED (pbs) instead of FINISHED for example) self.__job_message = '' ### Enumerate the job statuses we want to use self.job_status = StatusSystem() self.hold = self.job_status.hold self.queued = self.job_status.queued self.running = self.job_status.running self.skip = self.job_status.skip self.error = self.job_status.error self.timeout = self.job_status.timeout self.finished = self.job_status.finished self.__finished_statuses = [ self.skip, self.error, self.timeout, self.finished ] # Initialize jobs with a holding status self.setStatus(self.hold) def getDAG(self): """ Return the DAG associated with this tester """ return self.__job_dag def getTester(self): """ Return the tester object """ return self.__tester def getTestName(self): """ Wrapper method to return the testers test name """ return self.__tester.getTestName() def getTestNameShort(self): """ Return the shorthand Test name """ return self.getTestName().split('.')[1] def getPrereqs(self): """ Wrapper method to return the testers prereqs """ return self.__tester.getPrereqs() def getTestDir(self): """ Wrapper method to return the testers working directory """ return self.__tester.getTestDir() def addCaveats(self, kwargs): """ Wrapper method for setting caveats """ return self.__tester.addCaveats(kwargs) def getCaveats(self): """ Wrapper method for getting caveats """ return self.__tester.getCaveats() def clearCaveats(self): """ Wrapper method for clearing caveats """ return self.__tester.clearCaveats() def getCommand(self): """ Wrapper method for returing command """ return self.__tester.getCommand(self.options) def getRunnable(self): """ Wrapper method to return getRunnable """ return self.__tester.getRunnable(self.options) def getOutputFiles(self): """ Wrapper method to return getOutputFiles """ return self.__tester.getOutputFiles() def getMaxTime(self): """ Wrapper method to return getMaxTime """ return self.__tester.getMaxTime() def getUniqueIdentifier(self): """ A unique identifier for this job object """ return os.path.join(self.getTestDir(), self.getTestName()) def getUniquePrereqs(self): """ Return a list of prereqs with what should be their unique identifier """ prereqs = self.getPrereqs() unique_prereqs = [] for prereq in prereqs: unique_prereqs.append(os.path.join(self.getTestDir(), prereq)) return unique_prereqs def addMetaData(self, **kwargs): """ Allow derived methods to store additional data which ends up in the data storage file """ for key, value in kwargs.iteritems(): self.__meta_data[key] = value def getMetaData(self): """ return data stored by addMetaData """ return self.__meta_data def getSlots(self): """ Return the number of slots this job consumes """ if self.__slots == None: return self.setSlots(self.__tester.getSlots(self.options)) return self.__slots def setSlots(self, slots): """ Set the number of processors this job consumes """ self.__slots = int(slots) return self.__slots def run(self): """ A blocking method to handle the exit status of the process object while keeping track of the time the process was active. When the process exits, read the output and close the file. """ # Do not execute app, but allow processResults to commence if not self.__tester.shouldExecute(): return self.__tester.prepare(self.options) self.__start_time = clock() self.timer.reset() self.__tester.run(self.timer, self.options) self.__start_time = self.timer.starts[0] self.__end_time = self.timer.ends[-1] self.__joined_out = self.__tester.joined_out def killProcess(self): """ Kill remaining process that may be running """ self.__tester.killCommand() def getStartTime(self): """ Return the time the process started """ return self.__start_time def getEndTime(self): """ Return the time the process exited """ return self.__end_time def getOutput(self): """ Return the contents of output """ return self.__joined_out def setOutput(self, output): """ Method to allow schedulers to overwrite the output if certain conditions are met """ if (not self.__tester.outfile is None and not self.__tester.outfile.closed and not self.__tester.errfile is None and not self.__tester.errfile.closed): return # Check for invalid unicode in output try: json.dumps(output) except UnicodeDecodeError: # convert invalid output to something json can handle output = output.decode('utf-8', 'replace').encode('ascii', 'replace') # Alert the user that output has invalid characters self.addCaveats('invalid characters in stdout') self.__joined_out = output def getActiveTime(self): """ Return active time """ m = re.search(r"Active time=(\S+)", self.__joined_out) if m != None: return m.group(1) def getSolveTime(self): """ Return solve time """ m = re.search(r"solve().*", self.__joined_out) if m != None: return m.group().split()[5] def setPreviousTime(self, t): """ Allow an arbitrary time to be set. This is used by the QueueManager to set the time as recorded by a previous TestHarness instance. """ self.__previous_time = t def getTiming(self): """ Return active time if available, if not return a comparison of start and end time """ if self.getActiveTime(): return self.getActiveTime() elif self.getEndTime() and self.getStartTime(): return self.timer.cumulativeDur() elif self.getStartTime() and self.isRunning(): # If the test is still running, return current run time instead return max(0.0, clock() - self.getStartTime()) elif self.__previous_time: return self.__previous_time else: return 0.0 def getStatus(self): return self.job_status.getStatus() def setStatus(self, status, message=''): if self.isFinished(): return self.getStatus() # Try and preserve job messages if not self.__job_message and message: self.__job_message = message return self.job_status.setStatus(status) def createStatus(self): return self.job_status.createStatus() def getStatusMessage(self): return self.__job_message ### Boolean status comparisons based on current Job _and_ Tester status. All finalized status calls ### should now call job.isSomeStatus for the definitive answer. # the following are more job related... def isError(self): _status = self.getStatus() return _status == self.error or _status == self.timeout def isSkip(self): _status = self.getStatus() return (_status == self.finished and self.__tester.isSkip()) \ or (_status == self.skip and self.isNoStatus()) \ or (_status == self.skip and self.__tester.isSkip()) def isHold(self): return self.getStatus() == self.hold def isQueued(self): _status = self.getStatus() return (_status == self.queued and self.isNoStatus()) \ or (_status in self.__finished_statuses and self.__tester.isQueued()) def isRunning(self): return self.getStatus() == self.running def isTimeout(self): return self.getStatus() == self.timeout def isFinished(self): return self.getStatus() in self.__finished_statuses # the following more tester related... def isSilent(self): return self.__tester.isSilent() def isNoStatus(self): return self.__tester.isNoStatus() def isSilent(self): return self.__tester.isSilent() or (not self.options.report_skipped and self.isSkip()) def isPass(self): return self.__tester.isPass() def isFail(self): return self.__tester.isFail() or self.isError() def isDiff(self): return self.__tester.isDiff() def isDeleted(self): return self.__tester.isDeleted() def getJointStatus(self): """ Return the most accurate message possible, based on the statuses of both the Tester and Job. Most of the time, we want to return a Tester status. As this class will have the most detail. """ # Tester has no status, use a job status instead if self.isTimeout() or self.isNoStatus(): return (self.getStatus().status, self.getStatusMessage(), self.getStatus().color, self.getStatus().code) # Tester has a finished status of some sort else: return (self.__tester.getStatus().status, self.__tester.getStatusMessage(), self.__tester.getStatus().color, self.__tester.getStatus().code)
class Tester(MooseObject): """ Base class from which all tester objects are instanced. """ @staticmethod def validParams(): params = MooseObject.validParams() # Common Options params.addRequiredParam( 'type', "The type of test of Tester to create for this test.") params.addParam( 'max_time', 300, "The maximum in seconds that the test will be allowed to run.") params.addParam('skip', "Provide a reason this test will be skipped.") params.addParam( 'deleted', "Tests that only show up when using the '-e' option (Permanently skipped or not implemented)." ) params.addParam( 'heavy', False, "Set to True if this test should only be run when the '--heavy' option is used." ) params.addParam('group', [], "A list of groups for which this test belongs.") params.addParam( 'prereq', [], "A list of prereq tests that need to run successfully before launching this test." ) params.addParam( 'skip_checks', False, "Tells the TestHarness to skip additional checks (This parameter is set automatically by the TestHarness during recovery tests)" ) params.addParam('scale_refine', 0, "The number of refinements to do when scaling") params.addParam('success_message', 'OK', "The successful message") params.addParam('cli_args', [], "Additional arguments to be passed to the test.") params.addParam( 'allow_test_objects', False, "Allow the use of test objects by adding --allow-test-objects to the command line." ) params.addParam( 'valgrind', 'NONE', "Set to (NONE, NORMAL, HEAVY) to determine which configurations where valgrind will run." ) params.addParam('tags', [], "A list of strings") params.addParam( 'max_buffer_size', None, "Bytes allowed in stdout/stderr before it is subjected to being trimmed. Set to -1 to ignore output size restrictions. " "If 'max_buffer_size' is not set, the default value of 'None' triggers a reasonable value (e.g. 100 kB)" ) params.addParam( 'parallel_scheduling', False, "Allow all tests in test spec file to run in parallel (adheres to prereq rules)." ) # Test Filters params.addParam( 'platform', ['ALL'], "A list of platforms for which this test will run on. ('ALL', 'DARWIN', 'LINUX', 'SL', 'LION', 'ML')" ) params.addParam( 'compiler', ['ALL'], "A list of compilers for which this test is valid on. ('ALL', 'GCC', 'INTEL', 'CLANG')" ) params.addParam( 'petsc_version', ['ALL'], "A list of petsc versions for which this test will run on, supports normal comparison operators ('<', '>', etc...)" ) params.addParam( 'petsc_version_release', ['ALL'], "A test that runs against PETSc master if FALSE ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'slepc_version', [], "A list of slepc versions for which this test will run on, supports normal comparison operators ('<', '>', etc...)" ) params.addParam( 'mesh_mode', ['ALL'], "A list of mesh modes for which this test will run ('DISTRIBUTED', 'REPLICATED')" ) params.addParam( 'ad_mode', ['ALL'], "A list of AD modes for which this test will run ('SPARSE', 'NONSPARSE')" ) params.addParam( 'method', ['ALL'], "A test that runs under certain executable configurations ('ALL', 'OPT', 'DBG', 'DEVEL', 'OPROF', 'PRO')" ) params.addParam( 'library_mode', ['ALL'], "A test that only runs when libraries are built under certain configurations ('ALL', 'STATIC', 'DYNAMIC')" ) params.addParam( 'dtk', ['ALL'], "A test that runs only if DTK is detected ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'unique_ids', ['ALL'], "A test that runs only if UNIQUE_IDs are enabled ('ALL', 'TRUE', 'FALSE')" ) params.addParam('recover', True, "A test that runs with '--recover' mode enabled") params.addParam( 'vtk', ['ALL'], "A test that runs only if VTK is detected ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'tecplot', ['ALL'], "A test that runs only if Tecplot is detected ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'dof_id_bytes', ['ALL'], "A test that runs only if libmesh is configured --with-dof-id-bytes = a specific number, e.g. '4', '8'" ) params.addParam( 'petsc_debug', ['ALL'], "{False,True} -> test only runs when PETSc is configured with --with-debugging={0,1}, otherwise test always runs." ) params.addParam( 'curl', ['ALL'], "A test that runs only if CURL is detected ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'threading', ['ALL'], "A list of threading models ths tests runs with ('ALL', 'TBB', 'OPENMP', 'PTHREADS', 'NONE')" ) params.addParam( 'superlu', ['ALL'], "A test that runs only if SuperLU is available via PETSc ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'chaco', ['ALL'], "A test that runs only if Chaco (partitioner) is available via PETSc ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'parmetis', ['ALL'], "A test that runs only if Parmetis (partitioner) is available via PETSc ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'party', ['ALL'], "A test that runs only if Party (partitioner) is available via PETSc ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'ptscotch', ['ALL'], "A test that runs only if PTScotch (partitioner) is available via PETSc ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'slepc', ['ALL'], "A test that runs only if SLEPc is available ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'unique_id', ['ALL'], "A test that runs only if libmesh is configured with --enable-unique-id ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'cxx11', ['ALL'], "A test that runs only if CXX11 is available ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'asio', ['ALL'], "A test that runs only if ASIO is available ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'fparser_jit', ['ALL'], "A test that runs only if FParser JIT is available ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'libpng', ['ALL'], "A test that runs only if libpng is available ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'depend_files', [], "A test that only runs if all depend files exist (files listed are expected to be relative to the base directory, not the test directory" ) params.addParam( 'env_vars', [], "A test that only runs if all the environment variables listed exist" ) params.addParam( 'should_execute', True, 'Whether or not the executable needs to be run. Use this to chain together multiple tests based off of one executeable invocation' ) params.addParam( 'required_submodule', [], "A list of initialized submodules for which this test requires.") params.addParam( 'required_objects', [], "A list of required objects that are in the executable.") params.addParam( 'required_applications', [], "A list of required registered applications that are in the executable." ) params.addParam('check_input', False, "Check for correct input file syntax") params.addParam( 'display_required', False, "The test requires and active display for rendering (i.e., ImageDiff tests)." ) params.addParam( 'timing', True, "If True, the test will be allowed to run with the timing flag (i.e. Manually turning on performance logging)." ) params.addParam( 'boost', ['ALL'], "A test that runs only if BOOST is detected ('ALL', 'TRUE', 'FALSE')" ) params.addParam( 'python', None, "Restrict the test to s specific version of python (e.g., 3.6 or 3.7.1)." ) params.addParam( 'required_python_packages', None, "Test will only run if the supplied python packages exist.") params.addParam( 'requires', None, "A list of programs required for the test to operate, as tested with shutil.which." ) # SQA params.addParam( "requirement", None, "The SQA requirement that this test satisfies (e.g., 'The Marker system shall provide means to mark elements for refinement within a box region.')" ) params.addParam( "design", [], "The list of markdown files that contain the design(s) associated with this test (e.g., '/Markers/index.md /BoxMarker.md')." ) params.addParam( "issues", [], "The list of github issues associated with this test (e.g., '#1234 #4321')" ) params.addParam( "detail", None, "Details of SQA requirement for use within sub-blocks.") params.addParam("validation", False, "Set to True to mark test as a validation problem.") params.addParam("verification", False, "Set to True to mark test as a verification problem.") params.addParam( "deprecated", False, "When True the test is no longer considered part SQA process and as such does not include the need for a requirement definition." ) return params # This is what will be checked for when we look for valid testers IS_TESTER = True def __init__(self, name, params): MooseObject.__init__(self, name, params) self.specs = params self.outfile = None self.errfile = None self.joined_out = '' self.exit_code = 0 self.process = None self.tags = params['tags'] self.__caveats = set([]) # Alternate text we want to print as part of our status instead of the # pre-formatted status text (SYNTAX PASS instead of OK for example) self.__tester_message = '' # Bool if test can run self._runnable = None # Set up common paramaters self.should_execute = self.specs['should_execute'] self.check_input = self.specs['check_input'] if self.specs["allow_test_objects"]: self.specs["cli_args"].append("--allow-test-objects") ### Enumerate the tester statuses we want to use self.test_status = StatusSystem() self.no_status = self.test_status.no_status self.queued = self.test_status.queued self.skip = self.test_status.skip self.silent = self.test_status.silent self.success = self.test_status.success self.fail = self.test_status.fail self.diff = self.test_status.diff self.deleted = self.test_status.deleted self.__failed_statuses = [self.fail, self.diff, self.deleted] self.__skipped_statuses = [self.skip, self.silent] def getStatus(self): return self.test_status.getStatus() def setStatus(self, status, message=''): self.__tester_message = message return self.test_status.setStatus(status) def createStatus(self): return self.test_status.createStatus() # Return a tuple (status, message, caveats) for this tester as found # in the .previous_test_results.json file (or supplied json object) def previousTesterStatus(self, options, previous_storage=None): if not previous_storage: previous_storage = options.results_storage status_exists = previous_storage.get(self.getTestDir(), {}).get(self.getTestName(), None) status = (self.test_status.createStatus(), '', '') if status_exists: status = (self.test_status.createStatus( str(status_exists['STATUS'])), str(status_exists['STATUS_MESSAGE']), status_exists['CAVEATS']) return (status) def getStatusMessage(self): return self.__tester_message # Return a boolean based on current status def isNoStatus(self): return self.getStatus() == self.no_status def isSkip(self): return self.getStatus() in self.__skipped_statuses def isQueued(self): return self.getStatus() == self.queued def isSilent(self): return self.getStatus() == self.silent def isPass(self): return self.getStatus() == self.success def isFail(self): return self.getStatus() in self.__failed_statuses def isDiff(self): return self.getStatus() == self.diff def isDeleted(self): return self.getStatus() == self.deleted def getTestName(self): """ return test name """ return self.specs['test_name'] def getPrereqs(self): """ return list of prerequisite tests this test depends on """ return self.specs['prereq'] def getMooseDir(self): """ return moose directory """ return self.specs['moose_dir'] def getTestDir(self): """ return directory in which this tester is located """ return self.specs['test_dir'] def getMinReportTime(self): """ return minimum time elapse before reporting a 'long running' status """ return self.specs['min_reported_time'] def getMaxTime(self): """ return maximum time elapse before reporting a 'timeout' status """ return float(self.specs['max_time']) def getRunnable(self, options): """ return bool and cache results, if this test can run """ if self._runnable is None: self._runnable = self.checkRunnableBase(options) return self._runnable def getInputFile(self): """ return the input file if applicable to this Tester """ return None def getOutputFiles(self): """ return the output files if applicable to this Tester """ return [] def getOutput(self): """ Return the contents of stdout and stderr """ return self.joined_out def getCheckInput(self): return self.check_input def setValgrindMode(self, mode): """ Increase the alloted time for tests when running with the valgrind option """ if mode == 'NORMAL': self.specs['max_time'] = float(self.specs['max_time']) * 2 elif mode == 'HEAVY': self.specs['max_time'] = float(self.specs['max_time']) * 6 def checkRunnable(self, options): """ Derived method to return tuple if this tester should be executed or not. The tuple should be structured as (boolean, 'reason'). If false, and the reason is left blank, this tester will be treated as silent (no status will be printed and will not be counted among the skipped tests). """ return True def shouldExecute(self): """ return boolean for tester allowed to execute its command see .getCommand for more information """ return self.should_execute def prepare(self, options): """ Method which is called prior to running the test. It can be used to cleanup files or do other preparations before the tester is run. """ return def getThreads(self, options): """ return number of threads to use for this tester """ return 1 def getProcs(self, options): """ return number of processors to use for this tester """ return 1 def getSlots(self, options): """ return number of slots to use for this tester """ return self.getThreads(options) * self.getProcs(options) def getCommand(self, options): """ return the executable command that will be executed by the tester """ return '' def runCommand(self, cmd, cwd, timer, options): """ Helper method for running external (sub)processes as part of the tester's execution. This uses the tester's getCommand and getTestDir methods to run a subprocess. The timer must be the same timer passed to the run method. Results from running the subprocess is stored in the tester's output and exit_code fields. """ cmd = self.getCommand(options) cwd = self.getTestDir() self.process = None try: f = SpooledTemporaryFile(max_size=1000000) # 1M character buffer e = SpooledTemporaryFile(max_size=100000) # 100K character buffer # On Windows, there is an issue with path translation when the command is passed in # as a list. if platform.system() == "Windows": process = subprocess.Popen( cmd, stdout=f, stderr=e, close_fds=False, shell=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, cwd=cwd) else: process = subprocess.Popen(cmd, stdout=f, stderr=e, close_fds=False, shell=True, preexec_fn=os.setsid, cwd=cwd) except: print("Error in launching a new task", cmd) raise self.process = process self.outfile = f self.errfile = e timer.start() process.wait() timer.stop() self.exit_code = process.poll() self.outfile.flush() self.errfile.flush() # store the contents of output, and close the file self.joined_out = util.readOutput(self.outfile, self.errfile, self) self.outfile.close() self.errfile.close() def killCommand(self): """ Kills any currently executing process started by the runCommand method. """ if self.process is not None: try: if platform.system() == "Windows": from distutils import spawn if spawn.find_executable("taskkill"): subprocess.call([ 'taskkill', '/F', '/T', '/PID', str(self.process.pid) ]) else: self.process.terminate() else: pgid = os.getpgid(self.process.pid) os.killpg(pgid, SIGTERM) except OSError: # Process already terminated pass def run(self, timer, options): """ This is a method that is the tester's main execution code. Subclasses can override this method with custom code relevant to their specific testing needs. By default this method calls runCommand. runCommand is provided as a helper for running (external) subprocesses as part of the tester's execution and should be the *only* way subprocesses are executed if needed. The run method is responsible to call the start+stop methods on timer to record the time taken to run the actual test. start+stop can be called multiple times. """ cmd = self.getCommand(options) cwd = self.getTestDir() self.runCommand(cmd, cwd, timer, options) def processResultsCommand(self, moose_dir, options): """ method to return the commands (list) used for processing results """ return [] def processResults(self, moose_dir, options, output): """ method to process the results of a finished tester """ return def hasRedirectedOutput(self, options): """ return bool on tester having redirected output """ return (self.specs.isValid('redirect_output') and self.specs['redirect_output'] == True and self.getProcs(options) > 1) def getRedirectedOutputFiles(self, options): """ return a list of redirected output """ return [ os.path.join(self.getTestDir(), self.name() + '.processor.{}'.format(p)) for p in range(self.getProcs(options)) ] def addCaveats(self, *kwargs): """ Add caveat(s) which will be displayed with the final test status """ for i in [x for x in kwargs if x]: if type(i) == type([]): self.__caveats.update(i) else: self.__caveats.add(i) return self.getCaveats() def getCaveats(self): """ Return caveats accumalted by this tester """ return self.__caveats def clearCaveats(self): """ Clear any caveats stored in tester """ self.__caveats = set([]) return self.getCaveats() def checkRunnableBase(self, options): """ Method to check for caveats that would prevent this tester from executing correctly (or not at all). DO NOT override this method. Instead, see .checkRunnable() """ reasons = {} checks = options._checks tag_match = False for t in self.tags: if t in options.runtags: tag_match = True break if len(options.runtags) > 0 and not tag_match: self.setStatus(self.silent) return False # If something has already deemed this test a failure if self.isFail(): return False # Check if we only want to run syntax tests if options.check_input and not self.specs['check_input']: self.setStatus(self.silent) return False # Check if we want to exclude syntax tests if options.no_check_input and self.specs['check_input']: self.setStatus(self.silent) return False # Are we running only tests in a specific group? if options.group != 'ALL' and options.group not in self.specs['group']: self.setStatus(self.silent) return False if options.not_group != '' and options.not_group in self.specs['group']: self.setStatus(self.silent) return False # Store regexp for matching tests if --re is used if options.reg_exp: match_regexp = re.compile(options.reg_exp) # If --re then only test matching regexp. Needs to run before other SKIP methods # This also needs to be in its own bucket group. We normally print skipped messages. # But we do not want to print tests that didn't match regex. if options.reg_exp and not match_regexp.search( self.specs['test_name']): self.setStatus(self.silent) return False # Short circuit method and run this test if we are ignoring all caveats if options.ignored_caveats == 'all': # Still, we should abide by the derived classes return self.checkRunnable(options) # Check for deleted tests if self.specs.isValid('deleted'): reasons['deleted'] = str(self.specs['deleted']) # Skipped by external means (example: TestHarness part2 with --check-input) if self.isSkip() and self.getStatusMessage(): reasons['skip'] = self.getStatusMessage() # Test is skipped elif self.specs.type('skip') is bool and self.specs['skip']: # Backwards compatible (no reason) reasons['skip'] = 'no reason' elif self.specs.type('skip') is not bool and self.specs.isValid( 'skip'): reasons['skip'] = self.specs['skip'] # If were testing for SCALE_REFINE, then only run tests with a SCALE_REFINE set elif (options.scaling) and self.specs['scale_refine'] == 0: self.setStatus(self.silent) return False # If we're testing with valgrind, then skip tests that require parallel or threads or don't meet the valgrind setting elif options.valgrind_mode != '': tmp_reason = '' if self.specs['valgrind'].upper() == 'NONE': tmp_reason = 'Valgrind==NONE' elif self.specs['valgrind'].upper( ) == 'HEAVY' and options.valgrind_mode.upper() == 'NORMAL': tmp_reason = 'Valgrind==HEAVY' elif int(self.specs['min_threads']) > 1: tmp_reason = 'Valgrind requires non-threaded' elif self.specs["check_input"]: tmp_reason = 'check_input==True' if tmp_reason != '': reasons['valgrind'] = tmp_reason # If we're running in recover mode skip tests that have recover = false elif options.enable_recover and self.specs['recover'] == False: reasons['recover'] = 'NO RECOVER' # Check for PETSc versions (petsc_status, petsc_version) = util.checkPetscVersion(checks, self.specs) if not petsc_status: reasons['petsc_version'] = 'using PETSc ' + str( checks['petsc_version']) + ' REQ: ' + petsc_version # Check for SLEPc versions (slepc_status, slepc_version) = util.checkSlepcVersion(checks, self.specs) if not slepc_status and len(self.specs['slepc_version']) != 0: if slepc_version != None: reasons['slepc_version'] = 'using SLEPc ' + str( checks['slepc_version']) + ' REQ: ' + slepc_version elif slepc_version == None: reasons['slepc_version'] = 'SLEPc is not installed' # PETSc and SLEPc is being explicitly checked above local_checks = [ 'platform', 'compiler', 'mesh_mode', 'ad_mode', 'method', 'library_mode', 'dtk', 'unique_ids', 'vtk', 'tecplot', 'petsc_debug', 'curl', 'superlu', 'cxx11', 'asio', 'unique_id', 'slepc', 'petsc_version_release', 'boost', 'fparser_jit', 'parmetis', 'chaco', 'party', 'ptscotch', 'threading', 'libpng' ] for check in local_checks: test_platforms = set() operator_display = '!=' inverse_set = False for x in self.specs[check]: if x[0] == '!': if inverse_set: reasons[check] = 'Multiple Negation Unsupported' inverse_set = True operator_display = '==' x = x[1:] # Strip off the ! x_upper = x.upper() if x_upper in test_platforms: reasons[ x_upper] = 'Duplicate Entry or Negative of Existing Entry' test_platforms.add(x.upper()) match_found = len(test_platforms.intersection(checks[check])) > 0 # Either we didn't find the match when we were using normal "include" logic # or we did find the match when we wanted to exclude it if inverse_set == match_found: reasons[check] = re.sub(r'\[|\]', '', check).upper( ) + operator_display + ', '.join(test_platforms) # Check for heavy tests if options.all_tests or options.heavy_tests: if not self.specs['heavy'] and options.heavy_tests: reasons['heavy'] = 'NOT HEAVY' elif self.specs['heavy']: reasons['heavy'] = 'HEAVY' # There should only be one entry in self.specs['dof_id_bytes'] for x in self.specs['dof_id_bytes']: if x != 'ALL' and not x in checks['dof_id_bytes']: reasons['dof_id_bytes'] = '--with-dof-id-bytes!=' + x # Check to make sure depend files exist for file in self.specs['depend_files']: if not os.path.isfile(os.path.join(self.specs['base_dir'], file)): reasons['depend_files'] = 'DEPEND FILES' # We calculate the exe_objects only if we need them if self.specs["required_objects"] and checks["exe_objects"] is None: checks["exe_objects"] = util.getExeObjects( self.specs["executable"]) # Check to see if we have the required object names for var in self.specs['required_objects']: if var not in checks["exe_objects"]: reasons[ 'required_objects'] = '%s not found in executable' % var break # We extract the registered apps only if we need them if self.specs[ "required_applications"] and checks["registered_apps"] is None: checks["registered_apps"] = util.getExeRegisteredApps( self.specs["executable"]) # Check to see if we have the required application names for var in self.specs['required_applications']: if var not in checks["registered_apps"]: reasons[ 'required_applications'] = 'App %s not registered in executable' % var break # Check to make sure required submodules are initialized for var in self.specs['required_submodule']: if var not in checks["submodules"]: reasons[ 'required_submodule'] = '%s submodule not initialized' % var # Check to make sure environment variable exists for var in self.specs['env_vars']: if not os.environ.get(var): reasons['env_vars'] = 'ENV VAR NOT SET' # Check for display if self.specs['display_required'] and not os.getenv('DISPLAY', False): reasons['display_required'] = 'NO DISPLAY' # Check python version py_version = self.specs['python'] if (py_version is not None): if isinstance(py_version, int) and (py_version != sys.version_info[0]): reasons['python'] = 'PYTHON != {}'.format(py_version) elif isinstance(py_version, float) and (py_version != float( '{}.{}'.format(*sys.version_info[0:2]))): reasons['python'] = 'PYTHON != {}'.format(py_version) elif isinstance(py_version, str): ver = py_version.split('.') if any(sys.version_info[i] != int(v) for i, v in enumerate(ver)): reasons['python'] = 'PYTHON != {}'.format(py_version) # Check python packages py_packages = self.specs['required_python_packages'] if py_packages is not None: missing = mooseutils.check_configuration(py_packages.split(), message=False) if missing: reasons['python_packages_required'] = ', '.join( ['no {}'.format(p) for p in missing]) # Check for programs programs = self.specs['requires'] if (programs is not None): missing = [] for prog in programs.split(): if shutil.which(prog) is None: missing.append(prog) if missing: reasons['requires'] = ', '.join( ['no {}'.format(p) for p in missing]) # Remove any matching user supplied caveats from accumulated checkRunnable caveats that # would normally produce a skipped test. caveat_list = set() if options.ignored_caveats: caveat_list = set( [x.lower() for x in options.ignored_caveats.split()]) if len(set(reasons.keys()) - caveat_list) > 0: tmp_reason = [] for key, value in reasons.items(): if key.lower() not in caveat_list: tmp_reason.append(value) flat_reason = ', '.join(tmp_reason) # If the test is deleted we still need to treat this differently self.addCaveats(flat_reason) if 'deleted' in reasons.keys(): if options.extra_info: self.setStatus(self.deleted) else: self.setStatus(self.silent) else: self.setStatus(self.skip) return False # Check the return values of the derived classes self._runnable = self.checkRunnable(options) return self._runnable