示例#1
0
class TestHarness:

  @staticmethod
  def buildAndRun(argv, app_name, moose_dir):
    if '--store-timing' in argv:
      harness = TestTimer(argv, app_name, moose_dir)
    else:
      harness = TestHarness(argv, app_name, moose_dir)

    harness.findAndRunTests()

  def __init__(self, argv, app_name, moose_dir):
    self.factory = Factory()

    # Get dependant applications and load dynamic tester plugins
    # If applications have new testers, we expect to find them in <app_dir>/scripts/TestHarness/testers
    dirs = [os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))]
    sys.path.append(os.path.join(moose_dir, 'framework', 'scripts'))   # For find_dep_apps.py

    # Use the find_dep_apps script to get the dependant applications for an app
    import find_dep_apps
    depend_app_dirs = find_dep_apps.findDepApps(app_name)
    dirs.extend([os.path.join(my_dir, 'scripts', 'TestHarness') for my_dir in depend_app_dirs.split('\n')])

    # Finally load the plugins!
    self.factory.loadPlugins(dirs, 'testers', Tester)

    self.test_table = []
    self.num_passed = 0
    self.num_failed = 0
    self.num_skipped = 0
    self.num_pending = 0
    self.host_name = gethostname()
    self.moose_dir = moose_dir
    self.run_tests_dir = os.path.abspath('.')
    self.code = '2d2d6769726c2d6d6f6465'
    # Assume libmesh is a peer directory to MOOSE if not defined
    if os.environ.has_key("LIBMESH_DIR"):
      self.libmesh_dir = os.environ['LIBMESH_DIR']
    else:
      self.libmesh_dir = os.path.join(self.moose_dir, 'libmesh', 'installed')
    self.file = None

    # Parse arguments
    self.parseCLArgs(argv)

    self.checks = {}
    self.checks['platform'] = getPlatforms()
    self.checks['compiler'] = getCompilers(self.libmesh_dir)
    self.checks['petsc_version'] = getPetscVersion(self.libmesh_dir)
    self.checks['mesh_mode'] = getLibMeshConfigOption(self.libmesh_dir, 'mesh_mode')
    self.checks['dtk'] =  getLibMeshConfigOption(self.libmesh_dir, 'dtk')
    self.checks['library_mode'] = getSharedOption(self.libmesh_dir)
    self.checks['unique_ids'] = getLibMeshConfigOption(self.libmesh_dir, 'unique_ids')
    self.checks['vtk'] =  getLibMeshConfigOption(self.libmesh_dir, 'vtk')
    self.checks['tecplot'] =  getLibMeshConfigOption(self.libmesh_dir, 'tecplot')

    # Override the MESH_MODE option if using '--parallel-mesh' option
    if self.options.parallel_mesh == True or \
          (self.options.cli_args != None and \
          self.options.cli_args.find('--parallel-mesh') != -1):

      option_set = set()
      option_set.add('ALL')
      option_set.add('PARALLEL')
      self.checks['mesh_mode'] = option_set

    method = set()
    method.add('ALL')
    method.add(self.options.method.upper())
    self.checks['method'] = method

    self.initialize(argv, app_name)

  def findAndRunTests(self):
    self.preRun()
    self.start_time = clock()

    # PBS STUFF
    if self.options.pbs and os.path.exists(self.options.pbs):
      self.options.processingPBS = True
      self.processPBSResults()
    else:
      self.options.processingPBS = False
      for dirpath, dirnames, filenames in os.walk(os.getcwd(), followlinks=True):
        if (self.test_match.search(dirpath) and "contrib" not in os.path.relpath(dirpath, os.getcwd())):
          for file in filenames:
            # set cluster_handle to be None initially (happens for each test)
            self.options.cluster_handle = None
            # See if there were other arguments (test names) passed on the command line
            if file == self.options.input_file_name: #and self.test_match.search(file):
              saved_cwd = os.getcwd()
              sys.path.append(os.path.abspath(dirpath))
              os.chdir(dirpath)

              if self.prunePath(file):
                continue

              # Build a Warehouse to hold the MooseObjects
              warehouse = Warehouse()

              # Build a Parser to parse the objects
              parser = Parser(self.factory, warehouse)

              # Parse it
              parser.parse(file)

              # Retrieve the tests from the warehouse
              testers = warehouse.getAllObjects()

              # Augment the Testers with additional information directly from the TestHarness
              for tester in testers:
                self.augmentParameters(file, tester)

              if self.options.enable_recover:
                testers = self.appendRecoverableTests(testers)

              # Go through the Testers and run them
              for tester in testers:
                # Double the alloted time for tests when running with the valgrind option
                tester.setValgrindMode(self.options.valgrind_mode)

                # When running in valgrind mode, we end up with a ton of output for each failed
                # test.  Therefore, we limit the number of fails...
                if self.options.valgrind_mode and self.num_failed > self.options.valgrind_max_fails:
                  (should_run, reason) = (False, 'Max Fails Exceeded')
                else:
                  (should_run, reason) = tester.checkRunnableBase(self.options, self.checks)

                if should_run:
                  # Create the cluster launcher input file
                  if self.options.pbs and self.options.cluster_handle == None:
                    self.options.cluster_handle = open(dirpath + '/tests.cluster', 'a')
                    self.options.cluster_handle.write('[Jobs]\n')

                  command = tester.getCommand(self.options)
                  # This method spawns another process and allows this loop to continue looking for tests
                  # RunParallel will call self.testOutputAndFinish when the test has completed running
                  # This method will block when the maximum allowed parallel processes are running
                  self.runner.run(tester, command)
                else: # This job is skipped - notify the runner
                  if (reason != ''):
                    self.handleTestResult(tester.parameters(), '', reason)
                  self.runner.jobSkipped(tester.parameters()['test_name'])

                if self.options.cluster_handle != None:
                  self.options.cluster_handle.write('[]\n')
                  self.options.cluster_handle.close()
                  self.options.cluster_handle = None

              os.chdir(saved_cwd)
              sys.path.pop()

    self.runner.join()
    # Wait for all tests to finish
    if self.options.pbs and self.options.processingPBS == False:
      print '\n< checking batch status >\n'
      self.options.processingPBS = True
      self.processPBSResults()
      self.cleanupAndExit()
    else:
      self.cleanupAndExit()

  def prunePath(self, filename):
    test_dir = os.path.abspath(os.path.dirname(filename))

    # Filter tests that we want to run
    # Under the new format, we will filter based on directory not filename since it is fixed
    prune = True
    if len(self.tests) == 0:
      prune = False # No filter
    else:
      for item in self.tests:
        if test_dir.find(item) > -1:
          prune = False

    # Return the inverse of will_run to indicate that this path should be pruned
    return prune

  def augmentParameters(self, filename, tester):
    params = tester.parameters()

    # We are going to do some formatting of the path that is printed
    # Case 1.  If the test directory (normally matches the input_file_name) comes first,
    #          we will simply remove it from the path
    # Case 2.  If the test directory is somewhere in the middle then we should preserve
    #          the leading part of the path
    test_dir = os.path.abspath(os.path.dirname(filename))
    relative_path = test_dir.replace(self.run_tests_dir, '')
    relative_path = relative_path.replace('/' + self.options.input_file_name + '/', ':')
    relative_path = re.sub('^[/:]*', '', relative_path)  # Trim slashes and colons
    formatted_name = relative_path + '.' + tester.name()

    params['test_name'] = formatted_name
    params['test_dir'] = test_dir
    params['relative_path'] = relative_path
    params['executable'] = self.executable
    params['hostname'] = self.host_name
    params['moose_dir'] = self.moose_dir

    if params.isValid('prereq'):
      if type(params['prereq']) != list:
        print "Option 'prereq' needs to be of type list in " + params['test_name']
        sys.exit(1)
      params['prereq'] = [relative_path.replace('/tests/', '') + '.' + item for item in params['prereq']]

  # This method splits a lists of tests into two pieces each, the first piece will run the test for
  # approx. half the number of timesteps and will write out a restart file.  The second test will
  # then complete the run using the MOOSE recover option.
  def appendRecoverableTests(self, testers):
    new_tests = []

    for part1 in testers:
      if part1.parameters()['recover'] == True:
        # Clone the test specs
        part2 = copy.deepcopy(part1)

        # Part 1:
        part1_params = part1.parameters()
        part1_params['test_name'] += '_part1'
        part1_params['cli_args'].append('--half-transient')
        part1_params['cli_args'].append('Outputs/auto_recovery_part1=true')
        part1_params['skip_checks'] = True

        # Part 2:
        part2_params = part2.parameters()
        part2_params['prereq'].append(part1.parameters()['test_name'])
        part2_params['delete_output_before_running'] = False
        part2_params['cli_args'].append('Outputs/auto_recovery_part2=true')
        part2_params['cli_args'].append('--recover')
        part2_params.addParam('caveats', ['recover'], "")

        new_tests.append(part2)

    testers.extend(new_tests)
    return testers

  ## Finish the test by inspecting the raw output
  def testOutputAndFinish(self, tester, retcode, output, start=0, end=0):
    caveats = []
    test = tester.specs  # Need to refactor

    if test.isValid('caveats'):
      caveats = test['caveats']

    if self.options.pbs and self.options.processingPBS == False:
      (reason, output) = self.buildPBSBatch(output, tester)
    else:
      (reason, output) = tester.processResults(self.moose_dir, retcode, self.options, output)

    if self.options.scaling and test['scale_refine']:
      caveats.append('scaled')

    did_pass = True
    if reason == '':
      # It ran OK but is this test set to be skipped on any platform, compiler, so other reason?
      if self.options.extra_info:
        checks = ['platform', 'compiler', 'petsc_version', 'mesh_mode', 'method', 'library_mode', 'dtk', 'unique_ids']
        for check in checks:
          if not 'ALL' in test[check]:
            caveats.append(', '.join(test[check]))
      if len(caveats):
        result = '[' + ', '.join(caveats).upper() + '] OK'
      elif self.options.pbs and self.options.processingPBS == False:
        result = 'LAUNCHED'
      else:
        result = 'OK'
    else:
      result = 'FAILED (%s)' % reason
      did_pass = False
    self.handleTestResult(tester.specs, output, result, start, end)
    return did_pass

  def getTiming(self, output):
    time = ''
    m = re.search(r"Active time=(\S+)", output)
    if m != None:
      return m.group(1)

  def getSolveTime(self, output):
    time = ''
    m = re.search(r"solve().*", output)
    if m != None:
      return m.group().split()[5]

  def checkExpectError(self, output, expect_error):
    if re.search(expect_error, output, re.MULTILINE | re.DOTALL) == None:
      #print "%" * 100, "\nExpect Error Pattern not found:\n", expect_error, "\n", "%" * 100, "\n"
      return False
    else:
      return True

# PBS Defs
  def processPBSResults(self):
    # If batch file exists, check the contents for pending tests.
    if os.path.exists(self.options.pbs):
      # Build a list of launched jobs
      batch_file = open(self.options.pbs)
      batch_list = [y.split(':') for y in [x for x in batch_file.read().split('\n')]]
      batch_file.close()
      del batch_list[-1:]

      # Loop through launched jobs and match the TEST_NAME to determin correct stdout (Output_Path)
      for job in batch_list:
        file = '/'.join(job[2].split('/')[:-2]) + '/' + job[3]
        tests = self.parseGetPotTestFormat(file)
        for test in tests:
          # Build the requested Tester object
          if job[1] == test['test_name']:
            # Create Test Type
            tester = self.factory.create(test['type'], test)

            # Get job status via qstat
            qstat = ['qstat', '-f', '-x', str(job[0])]
            qstat_command = subprocess.Popen(qstat, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            qstat_stdout = qstat_command.communicate()[0]
            if qstat_stdout != None:
              output_value = re.search(r'job_state = (\w+)', qstat_stdout).group(1)
            else:
              return ('QSTAT NOT FOUND', '')

            # Report the current status of JOB_ID
            if output_value == 'F':
              # F = Finished. Get the exit code reported by qstat
              exit_code = int(re.search(r'Exit_status = (-?\d+)', qstat_stdout).group(1))

              # Read the stdout file
              if os.path.exists(job[2]):
                output_file = open(job[2], 'r')
                # Not sure I am doing this right: I have to change the TEST_DIR to match the temporary cluster_launcher TEST_DIR location, thus violating the tester.specs...
                test['test_dir'] = '/'.join(job[2].split('/')[:-1])
                outfile = output_file.read()
                output_file.close()
              else:
                # I ran into this scenario when the cluster went down, but launched/completed my job :)
                self.handleTestResult(tester.specs, '', 'FAILED (NO STDOUT FILE)', 0, 0, True)

              self.testOutputAndFinish(tester, exit_code, outfile)

            elif output_value == 'R':
              # Job is currently running
              self.handleTestResult(tester.specs, '', 'RUNNING', 0, 0, True)
            elif output_value == 'E':
              # Job is exiting
              self.handleTestResult(tester.specs, '', 'EXITING', 0, 0, True)
            elif output_value == 'Q':
              # Job is currently queued
              self.handleTestResult(tester.specs, '', 'QUEUED', 0, 0, True)
    else:
      return ('BATCH FILE NOT FOUND', '')

  def buildPBSBatch(self, output, tester):
    # Create/Update the batch file
    if 'command not found' in output:
      return('QSUB NOT FOUND', '')
    else:
      # Get the PBS Job ID using qstat
      # TODO: Build an error handler. If there was any issue launching the cluster launcher due to <any thing>, why die here.
      job_id = re.findall(r'.*JOB_ID: (\d+)', output)[0]
      qstat = ['qstat', '-f', '-x', str(job_id)]
      qstat_command = subprocess.Popen(qstat, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      qstat_stdout = qstat_command.communicate()[0]

      # Get the Output_Path from qstat stdout
      if qstat_stdout != None:
        output_value = re.search(r'Output_Path(.*?)(^ +)', qstat_stdout, re.S | re.M).group(1)
        output_value = output_value.split(':')[1].replace('\n', '').replace('\t', '')
      else:
        return ('QSTAT NOT FOUND', '')

      # Write job_id, test['test_name'], and Ouput_Path to the batch file
      file_name = self.options.pbs
      job_list = open(os.path.abspath(os.path.join(tester.specs['executable'], os.pardir)) + '/' + file_name, 'a')
      job_list.write(str(job_id) + ':' + tester.specs['test_name'] + ':' + output_value + ':' + self.options.input_file_name  + '\n')
      job_list.close()

      # Return to TestHarness and inform we have launched the job
      return ('', 'LAUNCHED')

  def cleanPBSBatch(self):
    # Open the PBS batch file and assign it to a list
    if os.path.exists(self.options.pbs_cleanup):
      batch_file = open(self.options.pbs_cleanup, 'r')
      batch_list = [y.split(':') for y in [x for x in batch_file.read().split('\n')]]
      batch_file.close()
      del batch_list[-1:]
    else:
      print 'PBS batch file not found:', self.options.pbs_cleanup
      sys.exit(1)

    # Loop through launched jobs and delete whats found.
    for job in batch_list:
      if os.path.exists(job[2]):
        batch_dir = os.path.abspath(os.path.join(job[2], os.pardir)).split('/')
        if os.path.exists('/'.join(batch_dir)):
          shutil.rmtree('/'.join(batch_dir))
        if os.path.exists('/'.join(batch_dir[:-1]) + '/' + job[3] + '.cluster'):
          os.remove('/'.join(batch_dir[:-1]) + '/' + job[3] + '.cluster')
    os.remove(self.options.pbs_cleanup)

# END PBS Defs

  ## Update global variables and print output based on the test result
  # Containing OK means it passed, skipped means skipped, anything else means it failed
  def handleTestResult(self, specs, output, result, start=0, end=0, add_to_table=True):
    timing = ''

    if self.options.timing:
      timing = self.getTiming(output)
    elif self.options.store_time:
      timing = self.getSolveTime(output)

    # Only add to the test_table if told to. We now have enough cases where we wish to print to the screen, but not
    # in the 'Final Test Results' area.
    if add_to_table:
      self.test_table.append( (specs, output, result, timing, start, end) )
      if result.find('OK') != -1:
        self.num_passed += 1
      elif result.find('skipped') != -1:
        self.num_skipped += 1
      elif result.find('deleted') != -1:
        self.num_skipped += 1
      elif result.find('LAUNCHED') != -1 or result.find('RUNNING') != -1 or result.find('QUEUED') != -1 or result.find('EXITING') != -1:
        self.num_pending += 1
      else:
        self.num_failed += 1

    self.postRun(specs, timing)

    if self.options.show_directory:
      print printResult(specs['relative_path'] + '/' + specs['test_name'].split('/')[-1], result, timing, start, end, self.options)
    else:
      print printResult(specs['test_name'], result, timing, start, end, self.options)

    if self.options.verbose or ('FAILED' in result and not self.options.quiet):
      output = output.replace('\r', '\n')  # replace the carriage returns with newlines
      lines = output.split('\n');
      color = ''
      if 'EXODIFF' in result or 'CSVDIFF' in result:
        color = 'YELLOW'
      elif 'FAILED' in result:
        color = 'RED'
      else:
        color = 'GREEN'
      test_name = colorText(specs['test_name']  + ": ", self.options, color)
      output = ("\n" + test_name).join(lines)
      print output

      # Print result line again at the bottom of the output for failed tests
      if self.options.show_directory:
        print printResult(specs['relative_path'] + '/' + specs['test_name'].split('/')[-1], result, timing, start, end, self.options), "(reprint)"
      else:
        print printResult(specs['test_name'], result, timing, start, end, self.options), "(reprint)"


    if not 'skipped' in result:
      if self.options.file:
        if self.options.show_directory:
          self.file.write(printResult( specs['relative_path'] + '/' + specs['test_name'].split('/')[-1], result, timing, start, end, self.options, color=False) + '\n')
          self.file.write(output)
        else:
          self.file.write(printResult( specs['test_name'], result, timing, start, end, self.options, color=False) + '\n')
          self.file.write(output)

      if self.options.sep_files or (self.options.fail_files and 'FAILED' in result) or (self.options.ok_files and result.find('OK') != -1):
        fname = os.path.join(specs['test_dir'], specs['test_name'].split('/')[-1] + '.' + result[:6] + '.txt')
        f = open(fname, 'w')
        f.write(printResult( specs['test_name'], result, timing, start, end, self.options, color=False) + '\n')
        f.write(output)
        f.close()

  # Write the app_name to a file, if the tests passed
  def writeState(self, app_name):
    # If we encounter bitten_status_moose environment, build a line itemized list of applications which passed their tests
    if os.environ.has_key("BITTEN_STATUS_MOOSE"):
      result_file = open(os.path.join(self.moose_dir, 'test_results.log'), 'a')
      result_file.write(str(os.path.split(app_name)[1][:-4]) + '\n')
      result_file.close()

  # Print final results, close open files, and exit with the correct error code
  def cleanupAndExit(self):
    # Print the results table again if a bunch of output was spewed to the screen between
    # tests as they were running
    if self.options.verbose or (self.num_failed != 0 and not self.options.quiet):
      print '\n\nFinal Test Results:\n' + ('-' * (TERM_COLS-1))
      for (test, output, result, timing, start, end) in self.test_table:
        if self.options.show_directory:
          print printResult(test['relative_path'] + '/' + specs['test_name'].split('/')[-1], result, timing, start, end, self.options)
        else:
          print printResult(test['test_name'], result, timing, start, end, self.options)

    time = clock() - self.start_time
    print '-' * (TERM_COLS-1)
    print 'Ran %d tests in %.1f seconds' % (self.num_passed+self.num_failed, time)

    if self.num_passed:
      summary = '<g>%d passed</g>'
    else:
      summary = '<b>%d passed</b>'
    summary += ', <b>%d skipped</b>'
    if self.num_pending:
      summary += ', <c>%d pending</c>, '
    else:
      summary += ', <b>%d pending</b>, '
    if self.num_failed:
      summary += '<r>%d FAILED</r>'
    else:
      summary += '<b>%d failed</b>'

    print colorText( summary % (self.num_passed, self.num_skipped, self.num_pending, self.num_failed), self.options, "", html=True )
    if self.options.pbs:
      print '\nYour PBS batch file:', self.options.pbs
    if self.file:
      self.file.close()

    if self.num_failed == 0:
      self.writeState(self.executable)
      sys.exit(0)
    else:
      sys.exit(1)

  def initialize(self, argv, app_name):
    # Initialize the parallel runner with how many tests to run in parallel
    self.runner = RunParallel(self, self.options.jobs, self.options.load)

    ## Save executable-under-test name to self.executable
    self.executable = os.getcwd() + '/' + app_name + '-' + self.options.method

    # Check for built application
    if not os.path.exists(self.executable):
      print 'Application not found: ' + str(self.executable)
      sys.exit(1)

    # Emulate the standard Nose RegEx for consistency
    self.test_match = re.compile(r"(?:^|\b|[_-])[Tt]est")

    # Save the output dir since the current working directory changes during tests
    self.output_dir = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.options.output_dir)

    # Create the output dir if they ask for it. It is easier to ask for forgiveness than permission
    if self.options.output_dir:
      try:
        os.makedirs(self.output_dir)
      except OSError, ex:
        if ex.errno == errno.EEXIST: pass
        else: raise

    # Open the file to redirect output to and set the quiet option for file output
    if self.options.file:
      self.file = open(os.path.join(self.output_dir, self.options.file), 'w')
    if self.options.file or self.options.fail_files or self.options.sep_files:
      self.options.quiet = True
示例#2
0
class TestHarness:
    def __init__(self, argv, app_name, moose_dir):
        self.factory = Factory()

        self.test_table = []
        self.num_passed = 0
        self.num_failed = 0
        self.num_skipped = 0
        self.num_pending = 0
        self.host_name = gethostname()
        self.moose_dir = os.path.abspath(moose_dir) + '/'
        self.run_tests_dir = os.path.abspath('.')
        self.code = '2d2d6769726c2d6d6f6465'
        # Assume libmesh is a peer directory to MOOSE if not defined
        if os.environ.has_key("LIBMESH_DIR"):
            self.libmesh_dir = os.environ['LIBMESH_DIR']
        else:
            self.libmesh_dir = self.moose_dir + '../libmesh/installed'
        self.file = None

        # Parse arguments
        self.parseCLArgs(argv)

        self.checks = {}
        self.checks['platform'] = getPlatforms()
        self.checks['compiler'] = getCompilers(self.libmesh_dir)
        self.checks['petsc_version'] = getPetscVersion(self.libmesh_dir)
        self.checks['mesh_mode'] = getLibMeshConfigOption(
            self.libmesh_dir, 'mesh_mode')
        self.checks['dtk'] = getLibMeshConfigOption(self.libmesh_dir, 'dtk')
        self.checks['library_mode'] = getSharedOption(self.libmesh_dir)
        self.checks['unique_ids'] = getLibMeshConfigOption(
            self.libmesh_dir, 'unique_ids')
        self.checks['vtk'] = getLibMeshConfigOption(self.libmesh_dir, 'vtk')
        self.checks['tecplot'] = getLibMeshConfigOption(
            self.libmesh_dir, 'tecplot')

        # Override the MESH_MODE option if using '--parallel-mesh' option
        if self.options.parallel_mesh == True or \
              (self.options.cli_args != None and \
              self.options.cli_args.find('--parallel-mesh') != -1):

            option_set = set()
            option_set.add('ALL')
            option_set.add('PARALLEL')
            self.checks['mesh_mode'] = option_set

        method = set()
        method.add('ALL')
        method.add(self.options.method.upper())
        self.checks['method'] = method

        self.initialize(argv, app_name)

    def findAndRunTests(self):
        self.preRun()
        self.start_time = clock()

        # PBS STUFF
        if self.options.pbs and os.path.exists(self.options.pbs):
            self.options.processingPBS = True
            self.processPBSResults()
        else:
            self.options.processingPBS = False
            for dirpath, dirnames, filenames in os.walk(os.getcwd(),
                                                        followlinks=True):
                if (self.test_match.search(dirpath)
                        and "contrib" not in os.path.relpath(
                            dirpath, os.getcwd())):
                    for file in filenames:
                        # set cluster_handle to be None initially (happens for each test)
                        self.options.cluster_handle = None
                        # See if there were other arguments (test names) passed on the command line
                        if file == self.options.input_file_name:  #and self.test_match.search(file):
                            saved_cwd = os.getcwd()
                            sys.path.append(os.path.abspath(dirpath))
                            os.chdir(dirpath)
                            tests = self.parseGetPotTestFormat(file)

                            if self.options.enable_recover:
                                tests = self.appendRecoverableTests(tests)

                            # Go through the list of test specs and run them
                            for test in tests:
                                # Strip begining and ending spaces to input file name
                                test['input'] = test['input'].strip()

                                # Double the alloted time for tests when running with the valgrind option
                                if self.options.valgrind_mode == 'NORMAL':
                                    test['max_time'] = test['max_time'] * 2
                                elif self.options.valgrind_mode == 'HEAVY':
                                    test['max_time'] = test['max_time'] * 4

                                # Build the requested Tester object and run
                                tester = self.factory.create(
                                    test['type'], test)

                                # When running in valgrind mode, we end up with a ton of output for each failed
                                # test.  Therefore, we limit the number of fails...
                                if self.options.valgrind_mode and self.num_failed > self.options.valgrind_max_fails:
                                    (should_run,
                                     reason) = (False, 'Max Fails Exceeded')
                                else:
                                    (should_run,
                                     reason) = tester.checkRunnableBase(
                                         self.options, self.checks)

                                if should_run:
                                    # Create the cluster launcher input file
                                    if self.options.pbs and self.options.cluster_handle == None:
                                        self.options.cluster_handle = open(
                                            dirpath + '/tests.cluster', 'a')
                                        self.options.cluster_handle.write(
                                            '[Jobs]\n')

                                    command = tester.getCommand(self.options)
                                    # This method spawns another process and allows this loop to continue looking for tests
                                    # RunParallel will call self.testOutputAndFinish when the test has completed running
                                    # This method will block when the maximum allowed parallel processes are running
                                    self.runner.run(tester, command)
                                else:  # This job is skipped - notify the runner
                                    if (reason != ''):
                                        self.handleTestResult(test, '', reason)
                                    self.runner.jobSkipped(test['test_name'])

                                if self.options.cluster_handle != None:
                                    self.options.cluster_handle.write('[]\n')
                                    self.options.cluster_handle.close()
                                    self.options.cluster_handle = None

                            os.chdir(saved_cwd)
                            sys.path.pop()

        self.runner.join()
        # Wait for all tests to finish
        if self.options.pbs and self.options.processingPBS == False:
            print '\n< checking batch status >\n'
            self.options.processingPBS = True
            self.processPBSResults()
            self.cleanupAndExit()
        else:
            self.cleanupAndExit()

    def parseGetPotTestFormat(self, filename):
        tests = []
        test_dir = os.path.abspath(os.path.dirname(filename))
        relative_path = test_dir.replace(self.run_tests_dir, '')

        # Filter tests that we want to run
        # Under the new format, we will filter based on directory not filename since it is fixed
        will_run = False
        if len(self.tests) == 0:
            will_run = True
        else:
            for item in self.tests:
                if test_dir.find(item) > -1:
                    will_run = True
        if not will_run:
            return tests

        try:
            data = ParseGetPot.readInputFile(filename)
        except:  # ParseGetPot class
            print "Parse Error: " + test_dir + "/" + filename
            return tests

        # We expect our root node to be called "Tests"
        if 'Tests' in data.children:
            tests_node = data.children['Tests']

            for testname, test_node in tests_node.children.iteritems():
                # First retrieve the type so we can get the valid params
                if 'type' not in test_node.params:
                    print "Type missing in " + test_dir + filename
                    sys.exit(1)

                params = self.factory.getValidParams(test_node.params['type'])

                # Now update all the base level keys
                params_parsed = set()
                params_ignored = set()
                for key, value in test_node.params.iteritems():
                    params_parsed.add(key)
                    if key in params:
                        if params.type(key) == list:
                            params[key] = value.split(' ')
                        else:
                            if re.match('".*"', value):  # Strip quotes
                                params[key] = value[1:-1]
                            else:
                                # Prevent bool types from being stored as strings.  This can lead to the
                                # strange situation where string('False') evaluates to true...
                                if params.isValid(key) and (type(params[key])
                                                            == type(bool())):
                                    # We support using the case-insensitive strings {true, false} and the string '0', '1'.
                                    if (value.lower() == 'true') or (value
                                                                     == '1'):
                                        params[key] = True
                                    elif (value.lower()
                                          == 'false') or (value == '0'):
                                        params[key] = False
                                    else:
                                        print "Unrecognized (key,value) pair: (", key, ',', value, ")"
                                        sys.exit(1)

                                # Otherwise, just do normal assignment
                                else:
                                    params[key] = value
                    else:
                        params_ignored.add(key)

                # Make sure that all required parameters are supplied
                required_params_missing = params.required_keys(
                ) - params_parsed
                if len(required_params_missing):
                    print "Error detected during test specification parsing\n  File: " + os.path.join(
                        test_dir, filename)
                    print '       Required Missing Parameter(s): ', required_params_missing
                if len(params_ignored):
                    print "Warning detected during test specification parsing\n  File: " + os.path.join(
                        test_dir, filename)
                    print '       Ignored Parameter(s): ', params_ignored

                # We are going to do some formatting of the path that is printed
                # Case 1.  If the test directory (normally matches the input_file_name) comes first,
                #          we will simply remove it from the path
                # Case 2.  If the test directory is somewhere in the middle then we should preserve
                #          the leading part of the path
                relative_path = relative_path.replace(
                    '/' + self.options.input_file_name + '/', ':')
                relative_path = re.sub(
                    '^[/:]*', '', relative_path)  # Trim slashes and colons
                formatted_name = relative_path + '.' + testname

                params['test_name'] = formatted_name
                params['test_dir'] = test_dir
                params['relative_path'] = relative_path
                params['executable'] = self.executable
                params['hostname'] = self.host_name
                params['moose_dir'] = self.moose_dir
                if params.isValid('prereq'):
                    if type(params['prereq']) != list:
                        print "Option 'prereq' needs to be of type list in " + params[
                            'test_name']
                        sys.exit(1)
                    params['prereq'] = [
                        relative_path.replace('/tests/', '') + '.' + item
                        for item in params['prereq']
                    ]

                # Build a list of test specs (dicts) to return
                tests.append(params)
        return tests

    def augmentTestSpecs(self, test):
        test['executable'] = self.executable
        test['hostname'] = self.host_name

    # This method splits a lists of tests into two pieces each, the first piece will run the test for
    # approx. half the number of timesteps and will write out a restart file.  The second test will
    # then complete the run using the MOOSE recover option.
    def appendRecoverableTests(self, tests):
        new_tests = []

        for part1 in tests:
            if part1['recover'] == True:
                # Clone the test specs
                part2 = copy.deepcopy(part1)

                # Part 1:
                part1['test_name'] += '_part1'
                part1['cli_args'].append('--half-transient')
                part1['cli_args'].append('Outputs/auto_recovery_part1=true')
                part1['skip_checks'] = True

                # Part 2:
                part2['prereq'].append(part1['test_name'])
                part2['delete_output_before_running'] = False
                part2['cli_args'].append('Outputs/auto_recovery_part2=true')
                part2['cli_args'].append('--recover')
                part2.addParam('caveats', ['recover'], "")

                new_tests.append(part2)

        tests.extend(new_tests)
        return tests

    ## Finish the test by inspecting the raw output
    def testOutputAndFinish(self, tester, retcode, output, start=0, end=0):
        caveats = []
        test = tester.specs  # Need to refactor

        if test.isValid('caveats'):
            caveats = test['caveats']

        if self.options.pbs and self.options.processingPBS == False:
            (reason, output) = self.buildPBSBatch(output, tester)
        else:
            (reason, output) = tester.processResults(self.moose_dir, retcode,
                                                     self.options, output)

        if self.options.scaling and test['scale_refine']:
            caveats.append('scaled')

        did_pass = True
        if reason == '':
            # It ran OK but is this test set to be skipped on any platform, compiler, so other reason?
            if self.options.extra_info:
                checks = [
                    'platform', 'compiler', 'petsc_version', 'mesh_mode',
                    'method', 'library_mode', 'dtk', 'unique_ids'
                ]
                for check in checks:
                    if not 'ALL' in test[check]:
                        caveats.append(', '.join(test[check]))
            if len(caveats):
                result = '[' + ', '.join(caveats).upper() + '] OK'
            elif self.options.pbs and self.options.processingPBS == False:
                result = 'LAUNCHED'
            else:
                result = 'OK'
        else:
            result = 'FAILED (%s)' % reason
            did_pass = False
        self.handleTestResult(tester.specs, output, result, start, end)
        return did_pass

    def getTiming(self, output):
        time = ''
        m = re.search(r"Active time=(\S+)", output)
        if m != None:
            return m.group(1)

    def getSolveTime(self, output):
        time = ''
        m = re.search(r"solve().*", output)
        if m != None:
            return m.group().split()[5]

    def checkExpectError(self, output, expect_error):
        if re.search(expect_error, output, re.MULTILINE | re.DOTALL) == None:
            #print "%" * 100, "\nExpect Error Pattern not found:\n", expect_error, "\n", "%" * 100, "\n"
            return False
        else:
            return True

# PBS Defs

    def processPBSResults(self):
        # If batch file exists, check the contents for pending tests.
        if os.path.exists(self.options.pbs):
            # Build a list of launched jobs
            batch_file = open(self.options.pbs)
            batch_list = [
                y.split(':')
                for y in [x for x in batch_file.read().split('\n')]
            ]
            batch_file.close()
            del batch_list[-1:]

            # Loop through launched jobs and match the TEST_NAME to determin correct stdout (Output_Path)
            for job in batch_list:
                file = '/'.join(job[2].split('/')[:-2]) + '/' + job[3]
                tests = self.parseGetPotTestFormat(file)
                for test in tests:
                    # Build the requested Tester object
                    if job[1] == test['test_name']:
                        # Create Test Type
                        tester = self.factory.create(test['type'], test)

                        # Get job status via qstat
                        qstat = ['qstat', '-f', '-x', str(job[0])]
                        qstat_command = subprocess.Popen(
                            qstat,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
                        qstat_stdout = qstat_command.communicate()[0]
                        if qstat_stdout != None:
                            output_value = re.search(r'job_state = (\w+)',
                                                     qstat_stdout).group(1)
                        else:
                            return ('QSTAT NOT FOUND', '')

                        # Report the current status of JOB_ID
                        if output_value == 'F':
                            # F = Finished. Get the exit code reported by qstat
                            exit_code = int(
                                re.search(r'Exit_status = (-?\d+)',
                                          qstat_stdout).group(1))

                            # Read the stdout file
                            if os.path.exists(job[2]):
                                output_file = open(job[2], 'r')
                                # Not sure I am doing this right: I have to change the TEST_DIR to match the temporary cluster_launcher TEST_DIR location, thus violating the tester.specs...
                                test['test_dir'] = '/'.join(
                                    job[2].split('/')[:-1])
                                outfile = output_file.read()
                                output_file.close()
                            else:
                                # I ran into this scenario when the cluster went down, but launched/completed my job :)
                                self.handleTestResult(
                                    tester.specs, '',
                                    'FAILED (NO STDOUT FILE)', 0, 0, True)

                            self.testOutputAndFinish(tester, exit_code,
                                                     outfile)

                        elif output_value == 'R':
                            # Job is currently running
                            self.handleTestResult(tester.specs, '', 'RUNNING',
                                                  0, 0, True)
                        elif output_value == 'E':
                            # Job is exiting
                            self.handleTestResult(tester.specs, '', 'EXITING',
                                                  0, 0, True)
                        elif output_value == 'Q':
                            # Job is currently queued
                            self.handleTestResult(tester.specs, '', 'QUEUED',
                                                  0, 0, True)
        else:
            return ('BATCH FILE NOT FOUND', '')

    def buildPBSBatch(self, output, tester):
        # Create/Update the batch file
        if 'command not found' in output:
            return ('QSUB NOT FOUND', '')
        else:
            # Get the PBS Job ID using qstat
            # TODO: Build an error handler. If there was any issue launching the cluster launcher due to <any thing>, why die here.
            job_id = re.findall(r'.*JOB_ID: (\d+)', output)[0]
            qstat = ['qstat', '-f', '-x', str(job_id)]
            qstat_command = subprocess.Popen(qstat,
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.PIPE)
            qstat_stdout = qstat_command.communicate()[0]

            # Get the Output_Path from qstat stdout
            if qstat_stdout != None:
                output_value = re.search(r'Output_Path(.*?)(^ +)',
                                         qstat_stdout, re.S | re.M).group(1)
                output_value = output_value.split(':')[1].replace('\n',
                                                                  '').replace(
                                                                      '\t', '')
            else:
                return ('QSTAT NOT FOUND', '')

            # Write job_id, test['test_name'], and Ouput_Path to the batch file
            file_name = self.options.pbs
            job_list = open(
                os.path.abspath(
                    os.path.join(tester.specs['executable'], os.pardir)) +
                '/' + file_name, 'a')
            job_list.write(
                str(job_id) + ':' + tester.specs['test_name'] + ':' +
                output_value + ':' + self.options.input_file_name + '\n')
            job_list.close()

            # Return to TestHarness and inform we have launched the job
            return ('', 'LAUNCHED')

    def cleanPBSBatch(self):
        # Open the PBS batch file and assign it to a list
        if os.path.exists(self.options.pbs_cleanup):
            batch_file = open(self.options.pbs_cleanup, 'r')
            batch_list = [
                y.split(':')
                for y in [x for x in batch_file.read().split('\n')]
            ]
            batch_file.close()
            del batch_list[-1:]
        else:
            print 'PBS batch file not found:', self.options.pbs_cleanup
            sys.exit(1)

        # Loop through launched jobs and delete whats found.
        for job in batch_list:
            if os.path.exists(job[2]):
                batch_dir = os.path.abspath(os.path.join(job[2],
                                                         os.pardir)).split('/')
                if os.path.exists('/'.join(batch_dir)):
                    shutil.rmtree('/'.join(batch_dir))
                if os.path.exists('/'.join(batch_dir[:-1]) + '/' + job[3] +
                                  '.cluster'):
                    os.remove('/'.join(batch_dir[:-1]) + '/' + job[3] +
                              '.cluster')
        os.remove(self.options.pbs_cleanup)

# END PBS Defs

## Update global variables and print output based on the test result
# Containing OK means it passed, skipped means skipped, anything else means it failed

    def handleTestResult(self,
                         specs,
                         output,
                         result,
                         start=0,
                         end=0,
                         add_to_table=True):
        timing = ''

        if self.options.timing:
            timing = self.getTiming(output)
        elif self.options.store_time:
            timing = self.getSolveTime(output)

        # Only add to the test_table if told to. We now have enough cases where we wish to print to the screen, but not
        # in the 'Final Test Results' area.
        if add_to_table:
            self.test_table.append((specs, output, result, timing, start, end))
            if result.find('OK') != -1:
                self.num_passed += 1
            elif result.find('skipped') != -1:
                self.num_skipped += 1
            elif result.find('deleted') != -1:
                self.num_skipped += 1
            elif result.find('LAUNCHED') != -1 or result.find(
                    'RUNNING') != -1 or result.find(
                        'QUEUED') != -1 or result.find('EXITING') != -1:
                self.num_pending += 1
            else:
                self.num_failed += 1

        self.postRun(specs, timing)

        if self.options.show_directory:
            print printResult(
                specs['relative_path'] + '/' +
                specs['test_name'].split('/')[-1], result, timing, start, end,
                self.options)
        else:
            print printResult(specs['test_name'], result, timing, start, end,
                              self.options)

        if self.options.verbose or ('FAILED' in result
                                    and not self.options.quiet):
            output = output.replace(
                '\r', '\n')  # replace the carriage returns with newlines
            lines = output.split('\n')
            color = ''
            if 'EXODIFF' in result or 'CSVDIFF' in result:
                color = 'YELLOW'
            elif 'FAILED' in result:
                color = 'RED'
            else:
                color = 'GREEN'
            test_name = colorText(specs['test_name'] + ": ", self.options,
                                  color)
            output = ("\n" + test_name).join(lines)
            print output

            # Print result line again at the bottom of the output for failed tests
            if self.options.show_directory:
                print printResult(
                    specs['relative_path'] + '/' +
                    specs['test_name'].split('/')[-1], result, timing, start,
                    end, self.options), "(reprint)"
            else:
                print printResult(specs['test_name'], result, timing, start,
                                  end, self.options), "(reprint)"

        if not 'skipped' in result:
            if self.options.file:
                if self.options.show_directory:
                    self.file.write(
                        printResult(specs['relative_path'] + '/' +
                                    specs['test_name'].split('/')[-1],
                                    result,
                                    timing,
                                    start,
                                    end,
                                    self.options,
                                    color=False) + '\n')
                    self.file.write(output)
                else:
                    self.file.write(
                        printResult(specs['test_name'],
                                    result,
                                    timing,
                                    start,
                                    end,
                                    self.options,
                                    color=False) + '\n')
                    self.file.write(output)

            if self.options.sep_files or (self.options.fail_files
                                          and 'FAILED' in result) or (
                                              self.options.ok_files
                                              and result.find('OK') != -1):
                fname = os.path.join(
                    specs['test_dir'], specs['test_name'].split('/')[-1] +
                    '.' + result[:6] + '.txt')
                f = open(fname, 'w')
                f.write(
                    printResult(specs['test_name'],
                                result,
                                timing,
                                start,
                                end,
                                self.options,
                                color=False) + '\n')
                f.write(output)
                f.close()

    # Write the app_name to a file, if the tests passed
    def writeState(self, app_name):
        # If we encounter bitten_status_moose environment, build a line itemized list of applications which passed their tests
        if os.environ.has_key("BITTEN_STATUS_MOOSE"):
            result_file = open(
                os.path.join(self.moose_dir, 'test_results.log'), 'a')
            result_file.write(str(os.path.split(app_name)[1][:-4]) + '\n')
            result_file.close()

    # Print final results, close open files, and exit with the correct error code
    def cleanupAndExit(self):
        # Print the results table again if a bunch of output was spewed to the screen between
        # tests as they were running
        if self.options.verbose or (self.num_failed != 0
                                    and not self.options.quiet):
            print '\n\nFinal Test Results:\n' + ('-' * (TERM_COLS - 1))
            for (test, output, result, timing, start, end) in self.test_table:
                if self.options.show_directory:
                    print printResult(
                        test['relative_path'] + '/' +
                        specs['test_name'].split('/')[-1], result, timing,
                        start, end, self.options)
                else:
                    print printResult(test['test_name'], result, timing, start,
                                      end, self.options)

        time = clock() - self.start_time
        print '-' * (TERM_COLS - 1)
        print 'Ran %d tests in %.1f seconds' % (self.num_passed +
                                                self.num_failed, time)

        if self.num_passed:
            summary = '<g>%d passed</g>'
        else:
            summary = '<b>%d passed</b>'
        summary += ', <b>%d skipped</b>'
        if self.num_pending:
            summary += ', <c>%d pending</c>, '
        else:
            summary += ', <b>%d pending</b>, '
        if self.num_failed:
            summary += '<r>%d FAILED</r>'
        else:
            summary += '<b>%d failed</b>'

        print colorText(summary % (self.num_passed, self.num_skipped,
                                   self.num_pending, self.num_failed),
                        self.options,
                        "",
                        html=True)
        if self.options.pbs:
            print '\nYour PBS batch file:', self.options.pbs
        if self.file:
            self.file.close()

        if self.num_failed == 0:
            self.writeState(self.executable)
            sys.exit(0)
        else:
            sys.exit(1)

    def initialize(self, argv, app_name):
        # Initialize the parallel runner with how many tests to run in parallel
        self.runner = RunParallel(self, self.options.jobs, self.options.load)

        ## Save executable-under-test name to self.executable
        self.executable = os.getcwd(
        ) + '/' + app_name + '-' + self.options.method

        # Check for built application
        if not os.path.exists(self.executable):
            print 'Application not found: ' + str(self.executable)
            sys.exit(1)

        # Emulate the standard Nose RegEx for consistency
        self.test_match = re.compile(r"(?:^|\b|[_-])[Tt]est")

        # Save the output dir since the current working directory changes during tests
        self.output_dir = os.path.join(
            os.path.abspath(os.path.dirname(sys.argv[0])),
            self.options.output_dir)

        # Create the output dir if they ask for it. It is easier to ask for forgiveness than permission
        if self.options.output_dir:
            try:
                os.makedirs(self.output_dir)
            except OSError, ex:
                if ex.errno == errno.EEXIST: pass
                else: raise

        # Open the file to redirect output to and set the quiet option for file output
        if self.options.file:
            self.file = open(os.path.join(self.output_dir, self.options.file),
                             'w')
        if self.options.file or self.options.fail_files or self.options.sep_files:
            self.options.quiet = True
示例#3
0
class ClusterLauncher:
    def __init__(self):
        self.factory = Factory()

    def parseJobsFile(self, template_dir, job_file):
        jobs = []
        # We expect the job list to be named "job_list"
        filename = template_dir + job_file

        try:
            data = ParseGetPot.readInputFile(filename)
        except:  # ParseGetPot class
            print "Parse Error: " + filename
            return jobs

        # We expect our root node to be called "Jobs"
        if 'Jobs' in data.children:
            jobs_node = data.children['Jobs']

            # Get the active line
            active_jobs = None
            if 'active' in jobs_node.params:
                active_jobs = jobs_node.params['active'].split(' ')

            for jobname, job_node in jobs_node.children.iteritems():
                # Make sure this job is active
                if active_jobs != None and not jobname in active_jobs:
                    continue

                # First retrieve the type so we can get the valid params
                if 'type' not in job_node.params:
                    print "Type missing in " + filename
                    sys.exit(1)

                params = self.factory.getValidParams(job_node.params['type'])

                params['job_name'] = jobname

                # Now update all the base level keys
                params_parsed = set()
                params_ignored = set()
                for key, value in job_node.params.iteritems():
                    params_parsed.add(key)
                    if key in params:
                        if params.type(key) == list:
                            params[key] = value.split(' ')
                        else:
                            if re.match('".*"', value):  # Strip quotes
                                params[key] = value[1:-1]
                            else:
                                params[key] = value
                    else:
                        params_ignored.add(key)

                # Make sure that all required parameters are supplied
                required_params_missing = params.required_keys(
                ) - params_parsed
                if len(required_params_missing):
                    print 'Required Missing Parameter(s): ', required_params_missing
                    sys.exit(1)
                if len(params_ignored):
                    print 'Ignored Parameter(s): ', params_ignored

                jobs.append(params)
        return jobs

    def createAndLaunchJob(self, template_dir, job_file, specs, options):
        next_dir = getNextDirName(specs['job_name'], os.listdir('.'))
        os.mkdir(template_dir + next_dir)

        # Log it
        if options.message:
            f = open(template_dir + 'jobs.log', 'a')
            f.write(next_dir.ljust(20) + ': ' + options.message + '\n')
            f.close()

        saved_cwd = os.getcwd()
        os.chdir(template_dir + next_dir)

        # Turn the remaining work over to the Job instance
        # To keep everything consistent we'll also append our serial number to our job name
        specs['job_name'] = next_dir
        job_instance = self.factory.create(specs['type'], specs)

        # Copy files
        job_instance.copyFiles(job_file)

        # Prepare the Job Script
        job_instance.prepareJobScript()

        # Launch it!
        job_instance.launch()

        os.chdir(saved_cwd)

    def registerJobType(self, type, name):
        self.factory.register(type, name)

    ### Parameter Dump ###
    def printDump(self):
        self.factory.printDump("Jobs")
        sys.exit(0)

    def run(self, template_dir, job_file, options):
        jobs = self.parseJobsFile(template_dir, job_file)

        for job in jobs:
            self.createAndLaunchJob(template_dir, job_file, job, options)
示例#4
0
class ClusterLauncher:
  def __init__(self):
    self.factory = Factory()

  def parseJobsFile(self, template_dir, job_file):
    jobs = []
    # We expect the job list to be named "job_list"
    filename = template_dir + job_file

    try:
      data = ParseGetPot.readInputFile(filename)
    except:        # ParseGetPot class
      print "Parse Error: " + filename
      return jobs

    # We expect our root node to be called "Jobs"
    if 'Jobs' in data.children:
      jobs_node = data.children['Jobs']

      # Get the active line
      active_jobs = None
      if 'active' in jobs_node.params:
        active_jobs = jobs_node.params['active'].split(' ')

      for jobname, job_node in jobs_node.children.iteritems():
        # Make sure this job is active
        if active_jobs != None and not jobname in active_jobs:
          continue

        # First retrieve the type so we can get the valid params
        if 'type' not in job_node.params:
          print "Type missing in " + filename
          sys.exit(1)

        params = self.factory.getValidParams(job_node.params['type'])

        params['job_name'] = jobname

        # Now update all the base level keys
        params_parsed = set()
        params_ignored = set()
        for key, value in job_node.params.iteritems():
          params_parsed.add(key)
          if key in params:
            if params.type(key) == list:
              params[key] = value.split(' ')
            else:
              if re.match('".*"', value):  # Strip quotes
                params[key] = value[1:-1]
              else:
                params[key] = value
          else:
            params_ignored.add(key)

        # Make sure that all required parameters are supplied
        required_params_missing = params.required_keys() - params_parsed
        if len(required_params_missing):
          print 'Required Missing Parameter(s): ', required_params_missing
          sys.exit(1)
        if len(params_ignored):
          print 'Ignored Parameter(s): ', params_ignored

        jobs.append(params)
    return jobs

  def createAndLaunchJob(self, template_dir, job_file, specs, options):
    next_dir = getNextDirName(specs['job_name'], os.listdir('.'))
    os.mkdir(template_dir + next_dir)

    # Log it
    if options.message:
      f = open(template_dir + 'jobs.log', 'a')
      f.write(next_dir.ljust(20) + ': ' + options.message + '\n')
      f.close()

    saved_cwd = os.getcwd()
    os.chdir(template_dir + next_dir)

    # Turn the remaining work over to the Job instance
    # To keep everything consistent we'll also append our serial number to our job name
    specs['job_name'] = next_dir
    job_instance = self.factory.create(specs['type'], specs)

    # Copy files
    job_instance.copyFiles(job_file)

    # Prepare the Job Script
    job_instance.prepareJobScript()

    # Launch it!
    job_instance.launch()

    os.chdir(saved_cwd)

  def registerJobType(self, type, name):
    self.factory.register(type, name)

  ### Parameter Dump ###
  def printDump(self):
    self.factory.printDump("Jobs")
    sys.exit(0)

  def run(self, template_dir, job_file, options):
    jobs = self.parseJobsFile(template_dir, job_file)

    for job in jobs:
      self.createAndLaunchJob(template_dir, job_file, job, options)
示例#5
0
class TestHarness:
    @staticmethod
    def buildAndRun(argv, app_name, moose_dir):
        if '--store-timing' in argv:
            harness = TestTimer(argv, app_name, moose_dir)
        else:
            harness = TestHarness(argv, app_name, moose_dir)

        harness.findAndRunTests()

        sys.exit(harness.error_code)

    def __init__(self, argv, app_name, moose_dir):
        self.factory = Factory()

        # Build a Warehouse to hold the MooseObjects
        self.warehouse = Warehouse()

        # Get dependant applications and load dynamic tester plugins
        # If applications have new testers, we expect to find them in <app_dir>/scripts/TestHarness/testers
        dirs = [
            os.path.dirname(
                os.path.abspath(inspect.getfile(inspect.currentframe())))
        ]
        sys.path.append(os.path.join(moose_dir, 'framework',
                                     'scripts'))  # For find_dep_apps.py

        # Use the find_dep_apps script to get the dependant applications for an app
        import find_dep_apps
        depend_app_dirs = find_dep_apps.findDepApps(app_name)
        dirs.extend([
            os.path.join(my_dir, 'scripts', 'TestHarness')
            for my_dir in depend_app_dirs.split('\n')
        ])

        # Finally load the plugins!
        self.factory.loadPlugins(dirs, 'testers', Tester)

        self.test_table = []
        self.num_passed = 0
        self.num_failed = 0
        self.num_skipped = 0
        self.host_name = gethostname()
        self.moose_dir = moose_dir
        self.base_dir = os.getcwd()
        self.run_tests_dir = os.path.abspath('.')
        self.code = '2d2d6769726c2d6d6f6465'
        self.error_code = 0x0
        # Assume libmesh is a peer directory to MOOSE if not defined
        if os.environ.has_key("LIBMESH_DIR"):
            self.libmesh_dir = os.environ['LIBMESH_DIR']
        else:
            self.libmesh_dir = os.path.join(self.moose_dir, 'libmesh',
                                            'installed')
        self.file = None

        # Failed Tests file object
        self.writeFailedTest = None

        # Parse arguments
        self.parseCLArgs(argv)

        checks = {}
        checks['platform'] = util.getPlatforms()
        checks['submodules'] = util.getInitializedSubmodules(
            self.run_tests_dir)
        checks['exe_objects'] = None  # This gets calculated on demand

        # The TestHarness doesn't strictly require the existence of libMesh in order to run. Here we allow the user
        # to select whether they want to probe for libMesh configuration options.
        if self.options.skip_config_checks:
            checks['compiler'] = set(['ALL'])
            checks['petsc_version'] = 'N/A'
            checks['petsc_version_release'] = 'N/A'
            checks['slepc_version'] = 'N/A'
            checks['library_mode'] = set(['ALL'])
            checks['mesh_mode'] = set(['ALL'])
            checks['dtk'] = set(['ALL'])
            checks['unique_ids'] = set(['ALL'])
            checks['vtk'] = set(['ALL'])
            checks['tecplot'] = set(['ALL'])
            checks['dof_id_bytes'] = set(['ALL'])
            checks['petsc_debug'] = set(['ALL'])
            checks['curl'] = set(['ALL'])
            checks['tbb'] = set(['ALL'])
            checks['superlu'] = set(['ALL'])
            checks['slepc'] = set(['ALL'])
            checks['unique_id'] = set(['ALL'])
            checks['cxx11'] = set(['ALL'])
            checks['asio'] = set(['ALL'])
            checks['boost'] = set(['ALL'])
        else:
            checks['compiler'] = util.getCompilers(self.libmesh_dir)
            checks['petsc_version'] = util.getPetscVersion(self.libmesh_dir)
            checks['petsc_version_release'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'petsc_version_release')
            checks['slepc_version'] = util.getSlepcVersion(self.libmesh_dir)
            checks['library_mode'] = util.getSharedOption(self.libmesh_dir)
            checks['mesh_mode'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'mesh_mode')
            checks['dtk'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'dtk')
            checks['unique_ids'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'unique_ids')
            checks['vtk'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'vtk')
            checks['tecplot'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'tecplot')
            checks['dof_id_bytes'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'dof_id_bytes')
            checks['petsc_debug'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'petsc_debug')
            checks['curl'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'curl')
            checks['tbb'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'tbb')
            checks['superlu'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'superlu')
            checks['slepc'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'slepc')
            checks['unique_id'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'unique_id')
            checks['cxx11'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'cxx11')
            checks['asio'] = util.getIfAsioExists(self.moose_dir)
            checks['boost'] = util.getLibMeshConfigOption(
                self.libmesh_dir, 'boost')

        # Override the MESH_MODE option if using the '--distributed-mesh'
        # or (deprecated) '--parallel-mesh' option.
        if (self.options.parallel_mesh == True or self.options.distributed_mesh == True) or \
              (self.options.cli_args != None and \
               (self.options.cli_args.find('--parallel-mesh') != -1 or self.options.cli_args.find('--distributed-mesh') != -1)):

            option_set = set(['ALL', 'PARALLEL'])
            checks['mesh_mode'] = option_set

        method = set(['ALL', self.options.method.upper()])
        checks['method'] = method

        # This is so we can easily pass checks around to any scheduler plugin
        self.options._checks = checks

        self.initialize(argv, app_name)

    """
    Recursively walks the current tree looking for tests to run
    Error codes:
    0x0  - Success
    0x7F - Parser error (any flag in this range)
    0x80 - TestHarness error
    """

    def findAndRunTests(self, find_only=False):
        self.error_code = 0x0
        self.preRun()
        self.start_time = clock()
        launched_tests = []

        try:
            self.base_dir = os.getcwd()
            for dirpath, dirnames, filenames in os.walk(self.base_dir,
                                                        followlinks=True):
                # Prune submdule paths when searching for tests
                if self.base_dir != dirpath and os.path.exists(
                        os.path.join(dirpath, '.git')):
                    dirnames[:] = []

                # walk into directories that aren't contrib directories
                if "contrib" not in os.path.relpath(dirpath, os.getcwd()):
                    for file in filenames:
                        # See if there were other arguments (test names) passed on the command line
                        if file == self.options.input_file_name \
                               and os.path.abspath(os.path.join(dirpath, file)) not in launched_tests:

                            if self.prunePath(file):
                                continue

                            saved_cwd = os.getcwd()
                            sys.path.append(os.path.abspath(dirpath))
                            os.chdir(dirpath)

                            # Get the testers for this test
                            testers = self.createTesters(
                                dirpath, file, find_only)

                            # Some 'test' files generate empty tests. Such as an entirely commented out
                            # test block:
                            #
                            # [Tests]
                            #   # [./test]
                            #   # [../]
                            # []
                            #
                            # We do not want to send an empty list of testers to the scheduler.
                            if len(testers):
                                # Schedule the testers for immediate execution
                                self.scheduler.schedule(testers)

                            # record these launched test to prevent this test from launching again
                            # due to os.walk following symbolic links
                            launched_tests.append(os.path.join(dirpath, file))

                            os.chdir(saved_cwd)
                            sys.path.pop()

            # Wait for all the tests to complete
            self.scheduler.waitFinish()

            self.cleanup()

            # Flags for the parser start at the low bit, flags for the TestHarness start at the high bit
            if self.num_failed:
                self.error_code = self.error_code | 0x80

        except KeyboardInterrupt:
            # Attempt to kill jobs currently running
            self.scheduler.killRemaining()

            if self.writeFailedTest != None:
                self.writeFailedTest.close()
            print('\nExiting due to keyboard interrupt...')
            sys.exit(1)

        return

# Create and return list of tester objects. A tester is created by providing
# abspath to basename (dirpath), and the test file in queustion (file)

    def createTesters(self, dirpath, file, find_only):
        # Build a Parser to parse the objects
        parser = Parser(self.factory, self.warehouse)

        # Parse it
        self.error_code = self.error_code | parser.parse(file)

        # Retrieve the tests from the warehouse
        testers = self.warehouse.getActiveObjects()

        # Augment the Testers with additional information directly from the TestHarness
        for tester in testers:
            self.augmentParameters(file, tester)

        # Short circuit this loop if we've only been asked to parse Testers
        # Note: The warehouse will accumulate all testers in this mode
        if find_only:
            self.warehouse.markAllObjectsInactive()
            return []

        # Clear out the testers, we won't need them to stick around in the warehouse
        self.warehouse.clear()

        if self.options.enable_recover:
            testers = self.appendRecoverableTests(testers)

        return testers

    def prunePath(self, filename):
        test_dir = os.path.abspath(os.path.dirname(filename))

        # Filter tests that we want to run
        # Under the new format, we will filter based on directory not filename since it is fixed
        prune = True
        if len(self.tests) == 0:
            prune = False  # No filter
        else:
            for item in self.tests:
                if test_dir.find(item) > -1:
                    prune = False

        # Return the inverse of will_run to indicate that this path should be pruned
        return prune

    def augmentParameters(self, filename, tester):
        params = tester.parameters()

        # We are going to do some formatting of the path that is printed
        # Case 1.  If the test directory (normally matches the input_file_name) comes first,
        #          we will simply remove it from the path
        # Case 2.  If the test directory is somewhere in the middle then we should preserve
        #          the leading part of the path
        test_dir = os.path.abspath(os.path.dirname(filename))
        relative_path = test_dir.replace(self.run_tests_dir, '')
        first_directory = relative_path.split(
            os.path.sep)[1]  # Get first directory
        relative_path = relative_path.replace(
            '/' + self.options.input_file_name + '/', ':')
        relative_path = re.sub('^[/:]*', '',
                               relative_path)  # Trim slashes and colons
        formatted_name = relative_path + '.' + tester.name()

        params['test_name'] = formatted_name
        params['test_dir'] = test_dir
        params['relative_path'] = relative_path
        params['executable'] = self.executable
        params['hostname'] = self.host_name
        params['moose_dir'] = self.moose_dir
        params['base_dir'] = self.base_dir
        params['first_directory'] = first_directory

        if params.isValid('prereq'):
            if type(params['prereq']) != list:
                print("Option 'prereq' needs to be of type list in " +
                      params['test_name'])
                sys.exit(1)
            params['prereq'] = [
                relative_path.replace('/tests/', '') + '.' + item
                for item in params['prereq']
            ]

        # Double the alloted time for tests when running with the valgrind option
        tester.setValgrindMode(self.options.valgrind_mode)

        # When running in valgrind mode, we end up with a ton of output for each failed
        # test.  Therefore, we limit the number of fails...
        if self.options.valgrind_mode and self.num_failed > self.options.valgrind_max_fails:
            tester.setStatus('Max Fails Exceeded', tester.bucket_fail)
        elif self.num_failed > self.options.max_fails:
            tester.setStatus('Max Fails Exceeded', tester.bucket_fail)
        elif tester.parameters().isValid('error_code'):
            tester.setStatus('Parser Error', tester.bucket_skip)

    # This method splits a lists of tests into two pieces each, the first piece will run the test for
    # approx. half the number of timesteps and will write out a restart file.  The second test will
    # then complete the run using the MOOSE recover option.
    def appendRecoverableTests(self, testers):
        new_tests = []

        for part1 in testers:
            if part1.parameters()['recover'] == True:
                # Clone the test specs
                part2 = copy.deepcopy(part1)

                # Part 1:
                part1_params = part1.parameters()
                part1_params['test_name'] += '_part1'
                part1_params['cli_args'].append('--half-transient')
                if self.options.recoversuffix == 'cpr':
                    part1_params['cli_args'].append('Outputs/checkpoint=true')
                if self.options.recoversuffix == 'cpa':
                    part1_params['cli_args'].append(
                        'Outputs/out/type=Checkpoint')
                    part1_params['cli_args'].append('Outputs/out/binary=false')
                part1_params['skip_checks'] = True

                # Part 2:
                part2_params = part2.parameters()
                part2_params['prereq'].append(part1.parameters()['test_name'])
                part2_params['delete_output_before_running'] = False
                part2_params['cli_args'].append('--recover --recoversuffix ' +
                                                self.options.recoversuffix)
                part2_params.addParam('caveats', ['recover'], "")

                new_tests.append(part2)

        testers.extend(new_tests)
        return testers

    def checkExpectError(self, output, expect_error):
        if re.search(expect_error, output, re.MULTILINE | re.DOTALL) == None:
            return False
        else:
            return True

    # Format the status message to make any caveats easy to read when they are printed
    def formatCaveats(self, tester):
        caveats = []
        result = ''

        if tester.specs.isValid('caveats'):
            caveats = tester.specs['caveats']

        # PASS and DRY_RUN fall into this catagory
        if tester.didPass():
            if self.options.extra_info:
                checks = [
                    'platform', 'compiler', 'petsc_version', 'mesh_mode',
                    'method', 'library_mode', 'dtk', 'unique_ids'
                ]
                for check in checks:
                    if not 'ALL' in tester.specs[check]:
                        caveats.append(', '.join(tester.specs[check]))
            if len(caveats):
                result = '[' + ', '.join(
                    caveats).upper() + '] ' + tester.getSuccessMessage()
            else:
                result = tester.getSuccessMessage()

        # FAIL, DIFF and DELETED fall into this catagory
        elif tester.didFail() or tester.didDiff() or tester.isDeleted():
            result = 'FAILED (%s)' % tester.getStatusMessage()

        elif tester.isSkipped():
            # Include caveats in skipped messages? Usefull to know when a scaled long "RUNNING..." test completes
            # but Exodiff is instructed to 'SKIP' on scaled tests.
            if len(caveats):
                result = '[' + ', '.join(caveats).upper(
                ) + '] skipped (' + tester.getStatusMessage() + ')'
            else:
                result = 'skipped (' + tester.getStatusMessage() + ')'
        else:
            result = tester.getStatusMessage()
        return result

    # Print and return formatted current tester status output
    def printResult(self, tester_data):
        """ Method to print a testers status to the screen """
        tester = tester_data.getTester()

        # The test has no status to print
        if tester.isSilent() or (tester.isDeleted()
                                 and not self.options.extra_info):
            caveat_formatted_results = None
        # Print what ever status the tester has at the time
        else:
            if self.options.verbose or (tester.didFail()
                                        and not self.options.quiet):
                output = 'Working Directory: ' + tester.getTestDir(
                ) + '\nRunning command: ' + tester.getCommand(
                    self.options) + '\n'

                output += tester_data.getOutput()
                output = output.replace(
                    '\r', '\n')  # replace the carriage returns with newlines
                lines = output.split('\n')

                # Obtain color based on test status
                color = tester.getColor()

                if output != '':
                    test_name = util.colorText(tester.getTestName() + ": ",
                                               color,
                                               colored=self.options.colored,
                                               code=self.options.code)
                    output = test_name + ("\n" + test_name).join(lines)
                    print(output)

            caveat_formatted_results = self.formatCaveats(tester)
            print(
                util.formatResult(tester_data, caveat_formatted_results,
                                  self.options))
        return caveat_formatted_results

    def handleTestStatus(self, tester_data):
        """ Method to handle a testers status """
        tester = tester_data.getTester()

        # print and store those results
        result = self.printResult(tester_data)

        # Test is finished and had some results to print
        if result and tester.isFinished():
            timing = tester_data.getTiming()

            # Store these results to a table we will use when we print final results
            self.test_table.append((tester_data, result, timing))

            self.postRun(tester.specs, timing)
            # Tally the results of this test to be used in our Final Test Results footer
            if tester.isSkipped():
                self.num_skipped += 1
            elif tester.didPass():
                self.num_passed += 1
            else:
                self.num_failed += 1

            # Write results to a file if asked to do so
            if not tester.isSkipped():
                if not tester.didPass() and not self.options.failed_tests:
                    self.writeFailedTest.write(tester.getTestName() + '\n')

                if self.options.file:
                    self.file.write(
                        util.formatResult(
                            tester_data, result, self.options, color=False) +
                        '\n')

                if self.options.sep_files or (self.options.fail_files
                                              and not tester.didPass()) or (
                                                  self.options.ok_files
                                                  and tester.didPass()):
                    fname = os.path.join(
                        tester.getTestDir(),
                        tester.getTestName().split('/')[-1] + '.' +
                        result[:6] + '.txt')
                    f = open(fname, 'w')
                    f.write(
                        util.formatResult(tester_data,
                                          tester_data.getOutput(),
                                          self.options,
                                          color=False) + '\n')
                    f.close()

    # Print final results, close open files, and exit with the correct error code
    def cleanup(self):
        # Print the results table again if a bunch of output was spewed to the screen between
        # tests as they were running
        if (self.options.verbose or
            (self.num_failed != 0
             and not self.options.quiet)) and not self.options.dry_run:
            print('\n\nFinal Test Results:\n' + ('-' * (util.TERM_COLS - 1)))
            for (tester_data, result, timing) in sorted(self.test_table,
                                                        key=lambda x: x[1],
                                                        reverse=True):
                print(util.formatResult(tester_data, result, self.options))

        time = clock() - self.start_time

        print('-' * (util.TERM_COLS - 1))

        # Mask off TestHarness error codes to report parser errors
        fatal_error = ''
        if self.error_code & Parser.getErrorCodeMask():
            fatal_error += ', <r>FATAL PARSER ERROR</r>'
        if self.error_code & ~Parser.getErrorCodeMask():
            fatal_error += ', <r>FATAL TEST HARNESS ERROR</r>'

        # Print a different footer when performing a dry run
        if self.options.dry_run:
            print('Processed %d tests in %.1f seconds' %
                  (self.num_passed + self.num_skipped, time))
            summary = '<b>%d would run</b>'
            summary += ', <b>%d would be skipped</b>'
            summary += fatal_error
            print(util.colorText( summary % (self.num_passed, self.num_skipped),  "", html = True, \
                             colored=self.options.colored, code=self.options.code ))

        else:
            print('Ran %d tests in %.1f seconds' %
                  (self.num_passed + self.num_failed, time))

            if self.num_passed:
                summary = '<g>%d passed</g>'
            else:
                summary = '<b>%d passed</b>'
            summary += ', <b>%d skipped</b>'
            if self.num_failed:
                summary += ', <r>%d FAILED</r>'
            else:
                summary += ', <b>%d failed</b>'
            summary += fatal_error

            print(util.colorText( summary % (self.num_passed, self.num_skipped, self.num_failed),  "", html = True, \
                             colored=self.options.colored, code=self.options.code ))

        if self.file:
            self.file.close()

        # Close the failed_tests file
        if self.writeFailedTest != None:
            self.writeFailedTest.close()

    def initialize(self, argv, app_name):
        # Load the scheduler plugins
        self.factory.loadPlugins(
            [os.path.join(self.moose_dir, 'python', 'TestHarness')],
            'schedulers', Scheduler)

        # Add our scheduler plugins
        # Note: for now, we only have one: 'RunParallel'. In the future this will be an options.argument
        #       comparison
        scheduler_plugin = 'RunParallel'

        # Augment the Scheduler params with plugin params
        plugin_params = self.factory.validParams(scheduler_plugin)

        # Set Scheduler specific params based on some provided options.arguments
        plugin_params['max_processes'] = self.options.jobs
        plugin_params['average_load'] = self.options.load

        # Create the scheduler
        self.scheduler = self.factory.create(scheduler_plugin, self,
                                             plugin_params)

        ## Save executable-under-test name to self.executable
        self.executable = os.getcwd(
        ) + '/' + app_name + '-' + self.options.method

        # Save the output dir since the current working directory changes during tests
        self.output_dir = os.path.join(
            os.path.abspath(os.path.dirname(sys.argv[0])),
            self.options.output_dir)

        # Create the output dir if they ask for it. It is easier to ask for forgiveness than permission
        if self.options.output_dir:
            try:
                os.makedirs(self.output_dir)
            except OSError as ex:
                if ex.errno == errno.EEXIST: pass
                else: raise

        # Open the file to redirect output to and set the quiet option for file output
        if self.options.file:
            self.file = open(os.path.join(self.output_dir, self.options.file),
                             'w')
        if self.options.file or self.options.fail_files or self.options.sep_files:
            self.options.quiet = True

    ## Parse command line options and assign them to self.options
    def parseCLArgs(self, argv):
        parser = argparse.ArgumentParser(
            description='A tool used to test MOOSE based applications')
        parser.add_argument('test_name', nargs=argparse.REMAINDER)
        parser.add_argument('--opt',
                            action='store_const',
                            dest='method',
                            const='opt',
                            help='test the app_name-opt binary')
        parser.add_argument('--dbg',
                            action='store_const',
                            dest='method',
                            const='dbg',
                            help='test the app_name-dbg binary')
        parser.add_argument('--devel',
                            action='store_const',
                            dest='method',
                            const='devel',
                            help='test the app_name-devel binary')
        parser.add_argument('--oprof',
                            action='store_const',
                            dest='method',
                            const='oprof',
                            help='test the app_name-oprof binary')
        parser.add_argument('--pro',
                            action='store_const',
                            dest='method',
                            const='pro',
                            help='test the app_name-pro binary')
        parser.add_argument(
            '--ignore',
            nargs='?',
            action='store',
            metavar='caveat',
            dest='ignored_caveats',
            const='all',
            type=str,
            help=
            'ignore specified caveats when checking if a test should run: (--ignore "method compiler") Using --ignore with out a conditional will ignore all caveats'
        )
        parser.add_argument('-j',
                            '--jobs',
                            nargs='?',
                            metavar='int',
                            action='store',
                            type=int,
                            dest='jobs',
                            const=1,
                            help='run test binaries in parallel')
        parser.add_argument(
            '-e',
            action='store_true',
            dest='extra_info',
            help=
            'Display "extra" information including all caveats and deleted tests'
        )
        parser.add_argument('-c',
                            '--no-color',
                            action='store_false',
                            dest='colored',
                            help='Do not show colored output')
        parser.add_argument('--color-first-directory',
                            action='store_true',
                            dest='color_first_directory',
                            help='Color first directory')
        parser.add_argument('--heavy',
                            action='store_true',
                            dest='heavy_tests',
                            help='Run tests marked with HEAVY : True')
        parser.add_argument(
            '--all-tests',
            action='store_true',
            dest='all_tests',
            help='Run normal tests and tests marked with HEAVY : True')
        parser.add_argument('-g',
                            '--group',
                            action='store',
                            type=str,
                            dest='group',
                            default='ALL',
                            help='Run only tests in the named group')
        parser.add_argument('--not_group',
                            action='store',
                            type=str,
                            dest='not_group',
                            help='Run only tests NOT in the named group')
        parser.add_argument(
            '--dbfile',
            nargs='?',
            action='store',
            dest='dbFile',
            help=
            'Location to timings data base file. If not set, assumes $HOME/timingDB/timing.sqlite'
        )
        parser.add_argument(
            '-l',
            '--load-average',
            action='store',
            type=float,
            dest='load',
            help=
            'Do not run additional tests if the load average is at least LOAD')
        parser.add_argument('-t',
                            '--timing',
                            action='store_true',
                            dest='timing',
                            help='Report Timing information for passing tests')
        parser.add_argument('-s',
                            '--scale',
                            action='store_true',
                            dest='scaling',
                            help='Scale problems that have SCALE_REFINE set')
        parser.add_argument(
            '-i',
            nargs=1,
            action='store',
            type=str,
            dest='input_file_name',
            default='tests',
            help=
            'The default test specification file to look for (default="tests").'
        )
        parser.add_argument(
            '--libmesh_dir',
            nargs=1,
            action='store',
            type=str,
            dest='libmesh_dir',
            help='Currently only needed for bitten code coverage')
        parser.add_argument(
            '--skip-config-checks',
            action='store_true',
            dest='skip_config_checks',
            help=
            'Skip configuration checks (all tests will run regardless of restrictions)'
        )
        parser.add_argument(
            '--parallel',
            '-p',
            nargs='?',
            action='store',
            type=int,
            dest='parallel',
            const=1,
            help='Number of processors to use when running mpiexec')
        parser.add_argument(
            '--n-threads',
            nargs=1,
            action='store',
            type=int,
            dest='nthreads',
            default=1,
            help='Number of threads to use when running mpiexec')
        parser.add_argument('-d',
                            action='store_true',
                            dest='debug_harness',
                            help='Turn on Test Harness debugging')
        parser.add_argument('--recover',
                            action='store_true',
                            dest='enable_recover',
                            help='Run a test in recover mode')
        parser.add_argument('--recoversuffix',
                            action='store',
                            type=str,
                            default='cpr',
                            dest='recoversuffix',
                            help='Set the file suffix for recover mode')
        parser.add_argument('--valgrind',
                            action='store_const',
                            dest='valgrind_mode',
                            const='NORMAL',
                            help='Run normal valgrind tests')
        parser.add_argument('--valgrind-heavy',
                            action='store_const',
                            dest='valgrind_mode',
                            const='HEAVY',
                            help='Run heavy valgrind tests')
        parser.add_argument(
            '--valgrind-max-fails',
            nargs=1,
            type=int,
            dest='valgrind_max_fails',
            default=5,
            help=
            'The number of valgrind tests allowed to fail before any additional valgrind tests will run'
        )
        parser.add_argument(
            '--max-fails',
            nargs=1,
            type=int,
            dest='max_fails',
            default=50,
            help=
            'The number of tests allowed to fail before any additional tests will run'
        )
        parser.add_argument(
            '--re',
            action='store',
            type=str,
            dest='reg_exp',
            help='Run tests that match --re=regular_expression')
        parser.add_argument('--failed-tests',
                            action='store_true',
                            dest='failed_tests',
                            help='Run tests that previously failed')
        parser.add_argument('--check-input',
                            action='store_true',
                            dest='check_input',
                            help='Run check_input (syntax) tests only')

        # Options that pass straight through to the executable
        parser.add_argument('--parallel-mesh',
                            action='store_true',
                            dest='parallel_mesh',
                            help='Deprecated, use --distributed-mesh instead')
        parser.add_argument('--distributed-mesh',
                            action='store_true',
                            dest='distributed_mesh',
                            help='Pass "--distributed-mesh" to executable')
        parser.add_argument(
            '--error',
            action='store_true',
            help=
            'Run the tests with warnings as errors (Pass "--error" to executable)'
        )
        parser.add_argument(
            '--error-unused',
            action='store_true',
            help=
            'Run the tests with errors on unused parameters (Pass "--error-unused" to executable)'
        )

        # Option to use for passing unwrapped options to the executable
        parser.add_argument(
            '--cli-args',
            nargs='?',
            type=str,
            dest='cli_args',
            help=
            'Append the following list of arguments to the command line (Encapsulate the command in quotes)'
        )

        parser.add_argument(
            '--dry-run',
            action='store_true',
            dest='dry_run',
            help=
            "Pass --dry-run to print commands to run, but don't actually run them"
        )

        outputgroup = parser.add_argument_group(
            'Output Options',
            'These options control the output of the test harness. The sep-files options write output to files named test_name.TEST_RESULT.txt. All file output will overwrite old files'
        )
        outputgroup.add_argument('-v',
                                 '--verbose',
                                 action='store_true',
                                 dest='verbose',
                                 help='show the output of every test')
        outputgroup.add_argument(
            '-q',
            '--quiet',
            action='store_true',
            dest='quiet',
            help=
            'only show the result of every test, don\'t show test output even if it fails'
        )
        outputgroup.add_argument('--no-report',
                                 action='store_false',
                                 dest='report_skipped',
                                 help='do not report skipped tests')
        outputgroup.add_argument(
            '--show-directory',
            action='store_true',
            dest='show_directory',
            help='Print test directory path in out messages')
        outputgroup.add_argument(
            '-o',
            '--output-dir',
            nargs=1,
            metavar='directory',
            dest='output_dir',
            default='',
            help=
            'Save all output files in the directory, and create it if necessary'
        )
        outputgroup.add_argument(
            '-f',
            '--file',
            nargs=1,
            action='store',
            dest='file',
            help=
            'Write verbose output of each test to FILE and quiet output to terminal'
        )
        outputgroup.add_argument(
            '-x',
            '--sep-files',
            action='store_true',
            dest='sep_files',
            help=
            'Write the output of each test to a separate file. Only quiet output to terminal. This is equivalant to \'--sep-files-fail --sep-files-ok\''
        )
        outputgroup.add_argument(
            '--sep-files-ok',
            action='store_true',
            dest='ok_files',
            help='Write the output of each passed test to a separate file')
        outputgroup.add_argument(
            '-a',
            '--sep-files-fail',
            action='store_true',
            dest='fail_files',
            help=
            'Write the output of each FAILED test to a separate file. Only quiet output to terminal.'
        )
        outputgroup.add_argument(
            "--store-timing",
            action="store_true",
            dest="store_time",
            help=
            "Store timing in the SQL database: $HOME/timingDB/timing.sqlite A parent directory (timingDB) must exist."
        )
        outputgroup.add_argument(
            "--testharness-unittest",
            action="store_true",
            help="Run the TestHarness unittests that test the TestHarness.")
        outputgroup.add_argument(
            "--revision",
            nargs=1,
            action="store",
            type=str,
            dest="revision",
            help=
            "The current revision being tested. Required when using --store-timing."
        )
        outputgroup.add_argument(
            "--yaml",
            action="store_true",
            dest="yaml",
            help="Dump the parameters for the testers in Yaml Format")
        outputgroup.add_argument(
            "--dump",
            action="store_true",
            dest="dump",
            help="Dump the parameters for the testers in GetPot Format")

        code = True
        if self.code.decode('hex') in argv:
            del argv[argv.index(self.code.decode('hex'))]
            code = False
        self.options = parser.parse_args(argv[1:])
        self.tests = self.options.test_name
        self.options.code = code

        # Convert all list based options of length one to scalars
        for key, value in vars(self.options).items():
            if type(value) == list and len(value) == 1:
                setattr(self.options, key, value[0])

        # If attempting to test only failed_tests, open the .failed_tests file and create a list object
        # otherwise, open the failed_tests file object for writing (clobber).
        failed_tests_file = os.path.join(os.getcwd(), '.failed_tests')
        if self.options.failed_tests:
            with open(failed_tests_file, 'r') as tmp_failed_tests:
                self.options._test_list = tmp_failed_tests.read().split('\n')
        else:
            self.writeFailedTest = open(failed_tests_file, 'w')

        self.checkAndUpdateCLArgs()

    ## Called after options are parsed from the command line
    # Exit if options don't make any sense, print warnings if they are merely weird
    def checkAndUpdateCLArgs(self):
        opts = self.options
        if opts.output_dir and not (opts.file or opts.sep_files
                                    or opts.fail_files or opts.ok_files):
            print(
                'WARNING: --output-dir is specified but no output files will be saved, use -f or a --sep-files option'
            )
        if opts.group == opts.not_group:
            print(
                'ERROR: The group and not_group options cannot specify the same group'
            )
            sys.exit(1)
        if opts.store_time and not (opts.revision):
            print('ERROR: --store-timing is specified but no revision')
            sys.exit(1)
        if opts.store_time:
            # timing returns Active Time, while store_timing returns Solve Time.
            # Thus we need to turn off timing.
            opts.timing = False
            opts.scaling = True
        if opts.valgrind_mode and (opts.parallel > 1 or opts.nthreads > 1):
            print(
                'ERROR: --parallel and/or --threads can not be used with --valgrind'
            )
            sys.exit(1)

        # Update any keys from the environment as necessary
        if not self.options.method:
            if os.environ.has_key('METHOD'):
                self.options.method = os.environ['METHOD']
            else:
                self.options.method = 'opt'

        if not self.options.valgrind_mode:
            self.options.valgrind_mode = ''

        # Update libmesh_dir to reflect arguments
        if opts.libmesh_dir:
            self.libmesh_dir = opts.libmesh_dir

        # When running heavy tests, we'll make sure we use --no-report
        if opts.heavy_tests:
            self.options.report_skipped = False

    def postRun(self, specs, timing):
        return

    def preRun(self):
        if self.options.yaml:
            self.factory.printYaml("Tests")
            sys.exit(0)
        elif self.options.dump:
            self.factory.printDump("Tests")
            sys.exit(0)

    def getOptions(self):
        return self.options