def setup():
    '''interactively edit the setup file. filepath must first be set'''
    checkForSetupFile()

    location = getLocation()
    if location:
        if prompt_bool('\nlocation has been configured as "%s", would you like to change this' % location):
            configLocation()
    else:
        configLocation()

    while True:
        if getAppliances() and not prompt_bool('\nDo you want to add/change/remove/view/check ' + \
                                'an appliance that is to be used when running local tests?'):
            break
        configAppliance()
        while True:
            if not prompt_bool('\nDo you want to add/change/remove/view/check another appliance?'):
                break
            configAppliance()
        break
        
    currentArgs = getArgs()
    if currentArgs:
        ansi.printc('\ncurrently sb test is adding the following args')
        ansi.writec(currentArgs, colors.PARAM_COLOR)
    if prompt_bool('\nsb test can be configured to always add arguments like -s ' '\n' \
                         'I have this configured to add' '\n' \
                         '-s --no-build-first --skip-compiled  --nologcapture' '\n' \
                         'to sb test' '\n' \
                         'for most people this is an unneccessary configuration' '\n'
                         'would you like to configure this'):
        configArgs()
Example #2
0
 def _snapshot(self, cmd):
     try:
         runCmd(cmd, printout=True)
     except VBoxManageError as err:
         if err.msg.startswith('\n0%...'): #ignoring VBoxManage bug
             pass
         else:
             ansi.printc(err.msg, colors.ERROR_COLOR)
             raise Exception('Reverting to the snapshot "%s" failed. Make sure you are using a VM that was auto created with this script' % snapshot)
def sbSetup():
    '''call this to interactively edit the setup file for the current sandbox'''
    global filepath
    
    if not os.path.exists( os.path.expanduser(os.path.join('~', 'test_setup.xml')) ):
        ansi.printc('Default setup file must be set up before a sandbox level setup file can be set up', colors.ERROR_COLOR)
        ansi.printc('Entering setup for default setup file', colors.WARNING_COLOR)
        filepath = os.path.expanduser(os.path.join('~', 'test_setup.xml'))
        setup()
        return
        
    filepath = os.path.join(testsupport.TESTROOT, 'test_setup.xml')
    setup()
Example #4
0
def _filterLog(path, listener=None):
    with open(path, 'rt') as f:
        lines = f.readlines()
    for i in range(len(lines)):
        lines[i] = str(i + 1).rjust(_LINENUM_WIDTH) + ' ' + lines[i].strip()
    itemNum = 1
    fname = os.path.basename(path)
    item = '%s (%d lines)' % (fname, len(lines))
    rest = DELIM_COLOR + '.' * (76 - len(item)) + NORMTXT
    item = CMD_COLOR + str(itemNum) + ' ' + NORMTXT + item
    m = LASTLOG_PAT.match(fname)
    if m:
        item = item.replace(m.group(1), CMD_COLOR + m.group(1) + NORMTXT)
    printc(item + rest)
    # Cut empty lines
    lines = [l for l in lines if l[_REST_OFFSET:]]
    # Cut lines that just have delimiters
    lines = [l for l in lines if _NONDELIM_LINES_PAT.search(l[_REST_OFFSET:])]
    # Cut lines that are known to be noise at particular phases.
    lines = [
        l for l in lines if not _GENERIC_ANT_LABEL_PAT.match(l[_REST_OFFSET:])
    ]
    tail = path.endswith('.tmp')
    # Now take the final 5 lines plus any lines with errors or warnings
    printCount = 0
    failed = False
    showNextLine = False
    lineCount = len(lines)
    i = 0
    for line in lines:
        if (showNextLine or (tail and i >= lineCount - 5)):
            interesting = line
        else:
            interesting = _getInteresting(line)
        if interesting:
            num = LINENUM_COLOR + line[0:_LINENUM_WIDTH]
            rest = line[_LINENUM_WIDTH:]
            if _isFailure(interesting):
                failed = True
                num += ERROR_COLOR
                rest += NORMTXT
            else:
                num += NORMTXT
            printc(num + rest)
            printCount += 1
        showNextLine = bool(_NEXT_LINE_PAT.search(line))
        i += 1
    if not printCount:
        print INDENT + '(nothing interesting)'
    return failed
Example #5
0
def _filterLog(path, listener = None):
    with open(path, 'rt') as f:
        lines = f.readlines()
    for i in range(len(lines)):
        lines[i] = str(i + 1).rjust(_LINENUM_WIDTH) + ' ' + lines[i].strip()
    itemNum = 1
    fname = os.path.basename(path)
    item = '%s (%d lines)' % (fname, len(lines))
    rest = DELIM_COLOR + '.'*(76-len(item)) + NORMTXT
    item = CMD_COLOR + str(itemNum) + ' ' + NORMTXT + item
    m = LASTLOG_PAT.match(fname)
    if m:
        item = item.replace(m.group(1), CMD_COLOR + m.group(1) + NORMTXT)
    printc(item + rest)
    # Cut empty lines
    lines = [l for l in lines if l[_REST_OFFSET:]]
    # Cut lines that just have delimiters
    lines = [l for l in lines if _NONDELIM_LINES_PAT.search(l[_REST_OFFSET:])]
    # Cut lines that are known to be noise at particular phases.
    lines = [l for l in lines if not _GENERIC_ANT_LABEL_PAT.match(l[_REST_OFFSET:])]
    tail = path.endswith('.tmp')
    # Now take the final 5 lines plus any lines with errors or warnings
    printCount = 0
    failed = False
    showNextLine = False
    lineCount = len(lines)
    i = 0
    for line in lines:
        if (showNextLine or (tail and i >= lineCount - 5)):
            interesting = line
        else:
            interesting = _getInteresting(line)
        if interesting:
            num = LINENUM_COLOR + line[0:_LINENUM_WIDTH]
            rest = line[_LINENUM_WIDTH:]
            if _isFailure(interesting):
                failed = True
                num += ERROR_COLOR
                rest += NORMTXT
            else:
                num += NORMTXT
            printc(num + rest)
            printCount += 1
        showNextLine = bool(_NEXT_LINE_PAT.search(line))
        i += 1
    if not printCount:
        print INDENT + '(nothing interesting)'
    return failed
def sbSetup():
    '''call this to interactively edit the setup file for the current sandbox'''
    global filepath

    if not os.path.exists(
            os.path.expanduser(os.path.join('~', 'test_setup.xml'))):
        ansi.printc(
            'Default setup file must be set up before a sandbox level setup file can be set up',
            colors.ERROR_COLOR)
        ansi.printc('Entering setup for default setup file',
                    colors.WARNING_COLOR)
        filepath = os.path.expanduser(os.path.join('~', 'test_setup.xml'))
        setup()
        return

    filepath = os.path.join(testsupport.TESTROOT, 'test_setup.xml')
    setup()
def checkCredentials(host, user, passwd):
    '''checks if a login is possible with the credentials entered for an appliance'''
    #print not Working
    try:
        import requests, socket
        session = requests.session(
            verify=False
        )  #the session will store the login cookies once we're logged in

        #check if the PSA is up
        try:
            socket.create_connection((host, '80')).close()
        except:
            raise Exception(
                'could not connect to the PSA at %s. Is the PSA is running?' %
                host)

        #logout if we are logged in
        try:
            session.get('https://%s/account/logout' % host)
        except:
            pass

        #try to login to the PSA
        loginResponse = session.get(
            'https://%s/account/dologin?login=%s&password=%s' %
            (host, user, passwd))
        whoami = session.get('https://%s/appliance/whoami' % host)

        assert loginResponse and whoami, 'bad or no status code(s) recieved.'
        if '<username>%s</username>' % user not in whoami.content:
            raise Exception(
                'After attempting to login, the user %s was not found in the whoami response.'
                % user)

        ansi.printc('successfully logged into %s' % host, colors.CMD_COLOR)
        return True

    except Exception, exc:
        errorMsg = 'Error logging into Existing Appliance object with the credentials\n' \
                   + 'host: %s  username: %s  password: %s\n' % (host, user, passwd) \
                   + str(exc)
        ansi.eprintc(errorMsg, colors.ERROR_COLOR)
        return False
Example #8
0
def show_logs(sb):
    overall_log = sb.get_log_file_path()
    print(overall_log)
    if os.path.isfile(overall_log):
        print('')
        _filterLog(overall_log)
        today = time.localtime(time.time())
        when = time.localtime(os.stat(overall_log).st_mtime)
        if (when.tm_mday == today.tm_mday) and (when.tm_mon == today.tm_mon) and (when.tm_year == today.tm_year):
            whentxt = 'today at ' + time.strftime('%I:%M %p', when)
        else:
            whentxt = time.strftime('%a, %d %b at %I:%M %p', when)
        lbl = '%s log last modified %s' % (sb.get_name(), whentxt)
        lbl = lbl.rjust(78)
        lbl = lbl.replace(sb.get_name(), PARAM_COLOR + sb.get_name() + NORMTXT)
        print('')
        printc(lbl)
    else:
        print('No logs available for %s.' % sb.get_name())
Example #9
0
def show_logs(sb):
    overall_log = sb.get_log_file_path()
    print(overall_log)
    if os.path.isfile(overall_log):
        print('')
        _filterLog(overall_log)
        today = time.localtime(time.time())
        when = time.localtime(os.stat(overall_log).st_mtime)
        if (when.tm_mday
                == today.tm_mday) and (when.tm_mon
                                       == today.tm_mon) and (when.tm_year
                                                             == today.tm_year):
            whentxt = 'today at ' + time.strftime('%I:%M %p', when)
        else:
            whentxt = time.strftime('%a, %d %b at %I:%M %p', when)
        lbl = '%s log last modified %s' % (sb.get_name(), whentxt)
        lbl = lbl.rjust(78)
        lbl = lbl.replace(sb.get_name(), PARAM_COLOR + sb.get_name() + NORMTXT)
        print('')
        printc(lbl)
    else:
        print('No logs available for %s.' % sb.get_name())
Example #10
0
def createPSA(name, branding=DEFAULT_BRANDING, release=DEFAULT_RELEASE_BRANCH, repo=None):
    '''creates a PSA with the specified name, branding, and release branch
    if repo is specified, the branding and release branch of the specified
    repo will be used instead'''
    
    copyVM()

    #create a new VM
    vm = VM(name, 'root', 'psa')    
    if vm.exists():
        if vm.getState() == 'running':
            print 'shutting down VM...'
            vm.forceStop()
        vm.revert()
    else:
        print '\nCreating new VM...'
        vm.importVM(LOCAL_VM_PATH)
        vm.modify(memory = '1024', cpus = '2', memoryBalloon = '512')
        vm.save()

    #convert newly created VM into a PSA by copying and installing RPMs
    vm.start()
    print '\nConverting VM to a PSA...'
    time.sleep(5)
    vm.copyPSAtoVM(psaBranding=branding, release=release, fullMirrorPath=repo)
    time.sleep(5)
    vm.installPSA(psaBranding=branding)
    vm.update()
    print '\n\n PSA sucessfully installed!'
    
    print 'Restarting VM after PSA install'
    vm.stop()
    vm.start()
    vm.save('psa-install')

    IPAddress = vm.getIPAddress()
    sys.stdout.write('\nIP Address of newly installed PSA is ')
    ansi.printc(str(IPAddress), colors.TITLE_COLOR)
    return IPAddress
Example #11
0
def setup():
    '''interactively edit the setup file. filepath must first be set'''
    checkForSetupFile()

    location = getLocation()
    if location:
        if prompt_bool(
                '\nlocation has been configured as "%s", would you like to change this'
                % location):
            configLocation()
    else:
        configLocation()

    while True:
        if getAppliances() and not prompt_bool('\nDo you want to add/change/remove/view/check ' + \
                                'an appliance that is to be used when running local tests?'):
            break
        configAppliance()
        while True:
            if not prompt_bool(
                    '\nDo you want to add/change/remove/view/check another appliance?'
            ):
                break
            configAppliance()
        break

    currentArgs = getArgs()
    if currentArgs:
        ansi.printc('\ncurrently sb test is adding the following args')
        ansi.writec(currentArgs, colors.PARAM_COLOR)
    if prompt_bool('\nsb test can be configured to always add arguments like -s ' '\n' \
                         'I have this configured to add' '\n' \
                         '-s --no-build-first --skip-compiled  --nologcapture' '\n' \
                         'to sb test' '\n' \
                         'for most people this is an unneccessary configuration' '\n'
                         'would you like to configure this'):
        configArgs()
def checkCredentials(host, user, passwd):
    '''checks if a login is possible with the credentials entered for an appliance'''
    #print not Working
    try:
        import requests, socket
        session = requests.session(verify=False) #the session will store the login cookies once we're logged in
        
        #check if the PSA is up
        try: 
            socket.create_connection((host, '80')).close()
        except: 
            raise Exception('could not connect to the PSA at %s. Is the PSA is running?' % host)

        #logout if we are logged in
        try: 
            session.get('https://%s/account/logout' % host)
        except: 
            pass
        
        #try to login to the PSA
        loginResponse = session.get('https://%s/account/dologin?login=%s&password=%s' % (host, user, passwd))
        whoami = session.get('https://%s/appliance/whoami' % host) 

        assert loginResponse and whoami, 'bad or no status code(s) recieved.'
        if '<username>%s</username>' % user not in whoami.content:
            raise Exception('After attempting to login, the user %s was not found in the whoami response.' % user)
        
        ansi.printc('successfully logged into %s'%host, colors.CMD_COLOR)
        return True
        
    except Exception, exc:
        errorMsg = 'Error logging into Existing Appliance object with the credentials\n' \
                   + 'host: %s  username: %s  password: %s\n' % (host, user, passwd) \
                   + str(exc)
        ansi.eprintc(errorMsg, colors.ERROR_COLOR)
        return False
Example #13
0
def configAppliance():
    '''interactively configure an appliance'''

    appName = prompt("\nEnter the name of an existing/new appliance: ",
                     default=None)
    assert appName

    oldHost = oldUser = oldPasswd = None

    if appName in getAppliances():
        oldHost, oldUser, oldPasswd = getAppInfo(appName)

        ansi.printc('\nwhat do you want to do with the appliance %s' % appName)
        ansi.writec('type ')
        ansi.writec('c', colors.PARAM_COLOR)
        ansi.printc(' to change the appliance configurations')
        ansi.writec('type ')
        ansi.writec('r', colors.PARAM_COLOR)
        ansi.printc(' to remove the appliance from the setup file')
        ansi.writec('type ')
        ansi.writec('v', colors.PARAM_COLOR)
        ansi.printc(' to view info about the appliance\'s configuration')
        ansi.writec('type ')
        ansi.writec('check', colors.PARAM_COLOR)
        ansi.printc(' to check the credentials of the appliance')
        response = prompt('\n', choices='options: c, r, v, check', default='v')

        if response == 'c':
            print 'Changing an appliance isn\'t working right now \n the setup file must be manually changed'
            pass
        elif response == 'v':
            displayApplianceInfo(appName)
            return
        elif response == 'r':
            delAppliance(appName)
            return
        elif response == 'check':
            checkCredentials(oldHost, oldUser, oldPasswd)
            return
        else:
            ansi.eprintc('not a valid option', colors.ERROR_COLOR)
            return

    host = prompt('\nwhat is the ip address for "%s"' % appName,
                  default=oldHost)
    name = prompt('\nwhat is the username for the appliance "%s"' % appName,
                  default=oldUser)
    passwd = prompt('\nwhat is the password for "%s"' % appName,
                    default=oldPasswd)

    if prompt_bool(
            '\nWould you like to check the entered credentials for "%s"? (%s must be running):'
            % (appName, appName)):
        if not checkCredentials(host, name, passwd):
            ansi.printc('failed to set up %s' % appName, colors.WARNING_COLOR)
            return

    print '\n%s successfully configured. You can now use the appliance %s ' \
          'to run tests by using the command' % (appName, appName)
    ansi.printc('sb test --psa %s\n' % appName, colors.TITLE_COLOR)

    setAppliance(appName, host, name, passwd)
    return
def configAppliance():
    '''interactively configure an appliance'''

    appName = prompt("\nEnter the name of an existing/new appliance: ", default=None)
    assert appName

    oldHost = oldUser = oldPasswd = None
    
    if appName in getAppliances():
        oldHost, oldUser, oldPasswd = getAppInfo(appName)

        ansi.printc('\nwhat do you want to do with the appliance %s' % appName)
        ansi.writec('type '); ansi.writec('c', colors.PARAM_COLOR);     ansi.printc(' to change the appliance configurations')
        ansi.writec('type '); ansi.writec('r', colors.PARAM_COLOR);     ansi.printc(' to remove the appliance from the setup file')
        ansi.writec('type '); ansi.writec('v', colors.PARAM_COLOR);     ansi.printc(' to view info about the appliance\'s configuration')
        ansi.writec('type '); ansi.writec('check', colors.PARAM_COLOR); ansi.printc(' to check the credentials of the appliance')
        response = prompt('\n', choices='options: c, r, v, check', default='v')
        
        if response == 'c':
            print 'Changing an appliance isn\'t working right now \n the setup file must be manually changed'
            pass
        elif response == 'v':
            displayApplianceInfo(appName)
            return
        elif response == 'r':
            delAppliance(appName)
            return
        elif response == 'check':
            checkCredentials(oldHost, oldUser, oldPasswd)
            return
        else:
            ansi.eprintc('not a valid option', colors.ERROR_COLOR)
            return

    host =   prompt('\nwhat is the ip address for "%s"'             % appName, default=oldHost)
    name =   prompt('\nwhat is the username for the appliance "%s"' % appName, default=oldUser)
    passwd = prompt('\nwhat is the password for "%s"'               % appName, default=oldPasswd)

    if prompt_bool('\nWould you like to check the entered credentials for "%s"? (%s must be running):' % (appName, appName)):
        if not checkCredentials(host, name, passwd):
            ansi.printc('failed to set up %s' % appName, colors.WARNING_COLOR)
            return
            
    print '\n%s successfully configured. You can now use the appliance %s ' \
          'to run tests by using the command' % (appName, appName)
    ansi.printc('sb test --psa %s\n' % appName, colors.TITLE_COLOR)

    setAppliance(appName, host, name, passwd)
    return
Example #15
0
def main(argv):
    try:
        if '-h' in argv or '--help' in argv:
            return run_nose(argv)

        sb = Sandbox(SBROOT)

        #Add different options for using an appliance to run a test
        #more info about this can be found at
        #https:// ... /working-with-code/concepts/existing-appliances/run-tests-using-an-appliance # TODO refer to correct doc site
        #TODO move all the info below into the above website.
        #
        #This can
        #  speed things up when creating test
        #  Allows one to debug a test with a full appliance
        #  can be used to run tests with an appliance setup from a variety of repos
        #
        #This does not
        #  Replace the current system of running tests off of a sandbox as an appliance
        #  Does not leave behind it's files in a way that is easy to inspect
        #  Is not as reproducable of an environment
        #
        #The different options available are
        #  --setup sets up appliances to run tests off of, as well as some
        #  other setup options related to tests
        #  --sb-setup same as --setup, but is specific to the current sandbox
        #  --ip ipaddress --ea applianceName --user username --passwd password 
        #  accomplishes the same thing as --setup, but without going through the interactive prompts 
        #  --no-adding-args using --setup sb test can be set up to automatically add
        #  arguments when you run sb test, this prevents that from happening
        #  --app someApplianceName the setup file created with --setup or --sb-setup
        #  maps a user defined name with the appliance credentials. You can specify the name
        #  of one of those appliances to run test off of, or you can use a new name and 
        #  a brand new appliance will be created and used. You can then reuse that appliance as many times as desired
        #  --recreate-psa this can only be used with an appliance that was previously created
        #  with the above command. This restores the VM to a snapshot before any PSA RPMs were
        #  copied over and installed, the VM then refetches all of the necessary RPMs and
        #  installs them
        #  --branding someBranding --release release_branch these can be used to specify the branding and release
        #  branch when creating a PSA
        #  --repo this can be used instead of the above command to specify a different repo to install off of
        #  this command overrides any branding or release branches that were specified with the above command
        
        if '--setup' in argv:
            argv.remove('--setup')
            isetup.defaultSetup()
            print '\n'
            sys.exit('setup is complete')
        if '--sb-setup' in argv:
            argv.remove('--sb-setup')
            isetup.sbSetup()
            print '\n'
            sys.exit('setup is complete')
        global addingArgs
        if '--no-adding-args' in argv:
            addingArgs = False
            argv.remove('--no-adding-args')
        if addingArgs:
            argsToAdd = getAdditionalArgs()
            if argsToAdd:
                added = ''
                for arg in argsToAdd.split():
                    if arg not in argv:
                        added += ' ' + arg
                        argv.append(arg)
                if added:
                    print 'added', added
        global_vars.ea = None
        ip = None
        user = None
        passwd = None

        def retreiveAndDelArg(arg):
            for i, tryArg in enumerate(argv):
                if tryArg == arg:
                    ret = argv[i + 1]
                    del argv[i]
                    del argv[i]
            return ret

        branding = None
        release = None
        repo = None
        
        for i, arg in enumerate(argv):
            if arg == '--ea' or arg == '--psa':
                global_vars.ea = argv[i + 1]
                del argv[i]
                del argv[i]
        for i, arg in enumerate(argv):                
            if arg == '--ip' or arg == '--host':
                ip = argv[i + 1]
                del argv[i]
                del argv[i]
        for i, arg in enumerate(argv):       
            if arg == '--user':
                user = argv[i + 1]
                del argv[i]
                del argv[i]
        for i, arg in enumerate(argv):                
            if arg == '--passwd' or arg == '--password':
                passwd = argv[i + 1]
                del argv[i]
                del argv[i]

        for i, arg in enumerate(argv):
            if arg == '--release':
                release = argv[i + 1]
                del argv[i]
                del argv[i]
        for i, arg in enumerate(argv):
            if arg == '--branding':
                branding = argv[i + 1]
                del argv[i]
                del argv[i]
        for i, arg in enumerate(argv):
            if arg == '--repo':
                repo = argv[i + 1]
                del argv[i]
                del argv[i]
                
        if global_vars.ea and ip and user and passwd:
            setup.setSetupFile()
            setup.configAppliance(global_vars.ea, ip, user, passwd)
      
        #make sure we're creating a new PSA if an appliance sandbox type is being used
        if 'appliance' in sb.get_sandboxtype().get_variant():
            global_vars.ea = 'sb-test-appliance'
            if '--recreate-psa' not in argv and global_vars.ea in isetup.getAppliances():
                argv.append('--recreate-psa')
        isetup.setSetupFile()
        
        #create the appliance if the appliance doesn't exist in the setup file
        if global_vars.ea and global_vars.ea not in isetup.getAppliances():
            ansi.printc('Appliance %s does not exist, will attempt to create it in 5 seconds, push ctrl+c to cancel' % global_vars.ea, colors.WARNING_COLOR)
            time.sleep(5)
            ip = controlVM.createPSA(global_vars.ea, branding, release, repo)
            isetup.setAppliance(global_vars.ea, ip, 'root', 'psa')
        if '--recreate-psa' in argv:
            assert global_vars.ea, 'A VM must be specified with the --ea option to use the --recreate-psa option'
            controlVM.createPSA(global_vars.ea, branding, release, repo)
            argv.remove('--recreate-psa')
            

        # Support "modes" of tests. This allows us to run a set of tests in
        # more than one way -- for example, with valgrind enabled or disabled,
        # or with SearchServer using hs5 versus hs3.
        mode_path = os.path.join(sb.get_root(), _MODE_FILE)
        argv, modes = get_modes(argv)
        if os.path.isfile(mode_path):
            os.remove(mode_path)
        if modes:
            if modes[0].lower() != 'none':
                with open(mode_path, 'w') as f:
                    f.write(','.join(modes))

        attr_arg = find_attr_arg_index(argv)
        if len(modes) == 0 and attr_arg is None:
            modeattrs = sb.get_sandboxtype().get_test_mode_attrs_combo()
            result = 0
            runcompiled = True
            runcodescan = True
            for modeattr in modeattrs.split(';'):
                runnable_assembly._modes = None
                if modeattr.find(':') == -1:
                    if len(modeattr) > 3:
                        print('Skipping mode+attr combo "%s" because no separator ":" was found' % modeattr)
                    continue
                print('Running mode+attr combo: %s' % modeattr)
                mode, attrs = modeattr.split(':')
                argnew = argv[:]#copy argv  into argnew so we can append things for this round of tests
                argnew.insert(1, '-A')
                argnew.insert(2, attrs.strip())
                argnew.insert(3, '--mode')
                argnew.insert(4, mode.strip())
                if not runcompiled:
                    argnew.append('--skip-compiled')
                if not runcodescan:
                    argnew.append('--skip-codescan')
                result = result + main(argnew);
                runcompiled = False
                runcodescan = False
                print("")
            if result == 0:
                print("All test combos passed.")
            else:
                print("%s test combos failed!" % result)
            return result
        elif attr_arg is None:
            argv.insert(1, '-A')
            attrs = sb.get_sandboxtype().get_test_attrs()
            argv.insert(2, attrs)
            print('Running tests that are: %s.' % attrs)
        else:
            # If we're running interactive tests, make sure we're also
            # running with the -s switch.
            print('Running tests that are: %s.' % argv[attr_arg + 1])
            x = argv[attr_arg + 1].replace('not ', '!')
            if 'interactive' in x and '!interactive' not in x:
                if '-s' not in argv:
                    argv.insert(1, '-s')

        if len(modes) > 0:
            if modes[0].lower() == 'none':
                modes = []

        print('Running modes: %s' % ','.join(modes))
        # Make sure any path args in the arg list are fully normalized so they
        # are not sensitive to working directory changes.
        normalize_path_args(argv)

        # If we're collecting tests, it's virtually guaranteed that we want to
        # display the list...
        if '--collect-only' in argv:
            if '-v' not in argv and '--verbose' not in argv:
                verbosity_found = False
                for a in argv:
                    if a.startswith('--verbosity'):
                        verbosity_found = True
                        break
                if not verbosity_found:
                    argv.append('--verbose')

        # The "full" flag tells us to test all components in the sandbox,
        # instead of just the ones that are present in code form. If our scope
        # is full, no special effort is required. However, if our scope is
        # normal (quick), we have to tell nose which subdirs to use during
        # discovery.
        if not SPECIFIC_TESTS_SELECTED:
            global quick
            if '--full' in argv:
                argv.remove('--full')
                quick = False
            if '--quick' in argv:
                quick = True
                argv.remove('--quick')
            if quick:
                subset = []
                cr = sb.get_code_root()
                tr = sb.get_test_root()
                deps = [l.name for l in sb.get_cached_components()]
                for c in deps:
                    if component.CODE_ASPECT_NAME in sb.get_component_aspects(c) or c == sb.get_top_component():
                        component_test_root = sb.get_component_path(c, component.TEST_ASPECT_NAME)
                        if os.path.isdir(component_test_root):
                            if could_have_tests(component_test_root):
                                # Nose treats the first --where arg as a specifier
                                # of the working directory, and subsequent --where
                                # args as folders to use for discovery. So if we're
                                # going to add --where, we have to guarantee working
                                # dir is the first --where.
                                if not subset:
                                    argv.append('--where')
                                    argv.append(sb.get_root())
                                argv.append('--where')
                                argv.append(component_test_root)
                                subset.append(c)
                if subset:
                    print('Targeting: ' + ', '.join(subset) + ' (--full to override).')

        # The default behavior of the "sb test" command is supposed to be that
        # all tests get run, no matter what part of the sandbox you're in when
        # you invoke the command. This is consistent with "sb build", "sb publish",
        # and other sandbox-level commands. By default, nose will discover tests
        # beginning in the current working directory. We could override nose's
        # behavior by adding an extra arg that specifies that the scope for
        # discovery is the test root, but this would interfere with other logic
        # that we have, that disables the running of compiled tests when any
        # sort of constraining file/directory scope is given. So the simplest
        # way to achieve our goal is to temporarily set the current working dir
        # to the test root whenever no explicit scope is provided.
        try:
            restore_dir = os.getcwd()
            os.chdir(sb.get_test_root())    # always run tests with the current directory being the test root

            if '--with-id' not in argv:
                argv.insert(1, '--with-id')
            if '--auto' in argv:
                argv.remove('--auto')
                auto_test(sb, argv)
            else:
                if '--build-first' in argv:
                    sb.set_auto_build(True)
                    argv.remove('--build-first')
                    err = build.main([])
                    if err:
                        return err
                elif '--no-build-first' in argv:
                    sb.set_auto_build(False)
                    argv.remove('--no-build-first')
                # Before running tests, automatically build sandbox if it's out of date.
                # This allows testers to get a built sandbox and immediately run tests. It
                # also allows developers to repeatedly code and test without explicitly
                # rebuilding in between.
                elif sb.needs_build() and sb.get_auto_build():
                    print('''
    Built artifacts are out of date and will be refreshed before running tests.
    To run tests without automatic build as needed, use "--no-build-first".
                ''')
                    err = build.main([])
                    if err:
                        return err

                return 0 if run_all(sb, argv) else 1

        finally:
            os.chdir(restore_dir)
    except:
        print(traceback.print_exc())
        return 1 # make sure bad things show up as an error