def run_download_test(self):  #Runs test
        log = logger.attach_to_logger(__name__)
        log.info('Starting: Download Speed Test')
        bandwidthdown = "10000000"
        download_element = driver.find_element_by_xpath(
            '//*[@id="root"]/div/div[2]/div[2]/main/div[2]/div[2]/div[1]/div[2]/div/div/span'
        )  #Grabs download element via xpath
        downloadSpeed = download_element.text  #Grabs download speed from element

        #Adds data to log for debugging
        log.info('download Speed: ' + str(downloadSpeed))

        #Converts from mb to bits
        downloadSpeed = float(downloadSpeed) * 1000000

        downloadError = 0.93 - downloadSpeed / float(
            bandwidthdown
        )  #Killer automatically sets the max bandwidth to 93% of value set so the error formula reflects that

        #Adds data to log for debugging
        log.info('Download error: ' + str(downloadError))

        #Now the script determines if the test failed and returns info to Jenkins
        #Error < 1% = Perfect pass
        #Error < 3% = Pass with warning
        #Error > 3% = Fail
        if downloadError > 0.03 or downloadError < -0.03:
            raise Exception('Download error greater than 3%')
        elif downloadError > 0.01 or downloadError < -0.01:
            log.info("Download error greater than 1%")
        else:
            log.info("Download error within 1%")
    def get_ip_info():
        log = logger.attach_to_logger(__name__)
        #Grabs the ip adress and interface name
        #sets self.ip_adress, self.interface_name
        import socket
        #get's ip adress
        ip_address = socket.gethostbyname(socket.gethostname())

        log.info('Getting network interface name for ethernet adapter...')
        os.environ["COMSPEC"] = 'powershell'
        #cmd_str = R'powershell "$ip = "10.200.150.200";foreach($int in (gwmi Win32_NetworkAdapter)) {gwmi Win32_NetworkAdapterConfiguration -Filter """Index = $($int.index)""" | ? {$_.IPAddress -contains $ip} | % {$int.NetConnectionID} }"'
        cmd_str = f"$ip = '{ip_address}';" + R'foreach($int in (gwmi Win32_NetworkAdapter)) {gwmi Win32_NetworkAdapterConfiguration -Filter """Index = $($int.index)""" | ? {$_.IPAddress -contains $ip} | % {$int.NetConnectionID} }'
        log.info(cmd_str)
        try:
            output = subprocess.check_output(cmd_str,
                                             stderr=subprocess.STDOUT,
                                             shell=True)
        except Exception as e:
            log.warning(
                f'get interface name powershell command failed with error: {e}'
            )

        interface_name = output.decode("utf-8").rstrip()
        log.info(ip_address)
        log.info(interface_name)

        return ip_address, interface_name
    def start_capture(self):
        log = logger.attach_to_logger(__name__)
        prio = self.config['priority']

        cap = pyshark.LiveCapture(
            interface=self.interface_name,
            bpf_filter=f'src {self.ip_address} and port 21',
            output_file=f'pyshark_{prio}.pcap')
        log.info('Capture started')
        #Create a thread that uploads the file while the capture is sniffing packets
        file_upload_thread = Thread(target=self.upload_file)
        file_upload_thread.start()
        log.info('sniffing...')
        count = 0
        cap.sniff(packet_count=8)
        for packet in cap:
            if 'IP' in packet:
                packet_result = self.print_dscp_info(packet)
                if packet_result:
                    count += 1
                    log.debug(packet['ip'])
                    if count == 5:
                        log.info('Test PASSED')
                        passed = True
                        break
                else:
                    #log.debug('aint it')
                    pass
        cap.close()
        if count < 5:
            raise RobotFatalError(
                f'{count} packets were tagged properly. Failing test...')
    def parse_yaml_config_file(yaml_config_filename=None):
        r"""Parse the arguments from a yaml file.
		Example file contents:
			  priority:
			  - 5
			  DSCP:
			  - 0x00000098
			  - 0x00000028
		"""

        log = logger.attach_to_logger(__name__)

        if yaml_config_filename is None:
            yaml_config_filename = 'DSCP_verification.yaml'

        log.info(f'Parsing YAML config file: {yaml_config_filename}')

        try:
            yaml_config_file = yaml.safe_load(open(yaml_config_filename))
            return yaml_config_file['DSCP_verification']
        except yaml.YAMLError as ye:
            log.error(
                f'Error loading config file {yaml_config_filename}: {ye}')
        except Exception as e:
            log.warning(f'Error: {e}')
            log.warning(f'Stack trace: {sys.exc_info()}')
 def set_prio(self):
     log = logger.attach_to_logger(__name__)
     log.info('Setting priority....')
     BCMWrapper = cdll.LoadLibrary(self._BCMWRAPPER_LIBRARY_PATH)
     #program = bR"c:\Program Files (x86)\Java\jre1.8.0_144\bin\java.exe"
     #program = bR"c:\Windows\System32\svchost.exe"
     program = bR"C:\Users\Rivet\AppData\Local\Programs\Python\Python37\python.exe"
     BCMWrapper.set_priority(program, int(self.config['priority']))
 def print_dscp_info(self, packet):
     log = logger.attach_to_logger(__name__)
     #if the TCP destination port is 21, then it's FTP traffic
     if packet['ip'].dsfield in self.config['dscp']:
         log.info('Test passed: found a packet with correct dscp number')
         return True
     else:
         return False
def bandwidth_maximum_test(self):
    bandwidthup = "10000000"
    bandwidthdown = "10000000"
    log = logger.attach_to_logger(__name__)
    log.info('Starting: Bandwidth Maximum Test')
    start = timer()
    end = None

    call(["sc", "stop", "Killer Network Service x64"
          ])  #Stops killer network service via command line

    #Now that killer network service is stopped, user.xml can be updated
    tree = ET.parse(
        'C:/ProgramData/RivetNetworks/Killer/user.xml'
    )  #Loads file, should be consistent across all computers due to Killer's install process
    root = tree.getroot()  #Gets root out of the tree parse
    sh = root.find('NetworkInfos')  #Finds parent
    networkinfo = sh.find(
        'NetworkInfo'
    )  #Finds child which contains the attributes we are looking for

    networkinfo.set('BandwidthUp',
                    bandwidthup)  #Updates bandwidth upload max speed value
    networkinfo.set('BandwidthDown',
                    bandwidthdown)  #Updates bandwith download max speed value
    tree.write('C:/ProgramData/RivetNetworks/Killer/user.xml'
               )  #Overrides the old user.xml file with a new updated file.

    #Now we need to start Killer and see if change is made
    call(["sc", "start", "Killer Network Service x64"
          ])  #Starts killer network service via command line

    driver = webdriver.Chrome(executable_path='chromedriver.exe')
    driver.get('http://killernetworking.speedtestcustom.com/'
               )  #Opens up killer's speed test via chrome
    id_box = driver.find_element_by_xpath(
        '//*[@id="main-content"]/div[1]/div/button')  #Isolates the "go" button
    id_box.click()  #clicks go button
    time.sleep(38)  #waits for test to be completed
    download_element = driver.find_element_by_xpath(
        '//*[@id="root"]/div/div[2]/div[2]/main/div[2]/div[2]/div[1]/div[2]/div/div/span'
    )  #Grabs download element via xpath
    upload_element = driver.find_element_by_xpath(
        '//*[@id="root"]/div/div[2]/div[2]/main/div[2]/div[2]/div[2]/div[2]/div/div/span'
    )  #Grabs download element via xpath
    downloadSpeed = download_element.text  #Grabs download speed from element
    uploadSpeed = upload_element.text  #Grabs upload speed from element

    downloadSpeed = float(downloadSpeed) * 1000000
    uploadSpeed = float(uploadSpeed) * 1000000

    downloadError = 0.93 - downloadSpeed / float(bandwidthdown)
    uploadError = 0.93 - uploadSpeed / float(bandwidthup)

    log.info('Download Error: ' + downloadError)
    log.info('Upload Error: ' + uploadError)
    def __init__(self, filepath_to_bcmwrapper=None):
        log = logger.attach_to_logger(__name__)

        if filepath_to_bcmwrapper is None:
            self.filepath_to_bcmwrapper = self._BCMWRAPPER_LIBRARY_PATH
        else:
            self.filepath_to_bcmwrapper = filepath_to_bcmwrapper

        self.config = self.parse_yaml_config_file()
        self.ip_address, self.interface_name = self.get_ip_info()
    def receive(self):
        config = yaml.safe_load(open('snapshot_job.yaml'))  #Parse YAML File
        config = config['snapshot_job']  #Grab Data
        log = logger.attach_to_logger(__name__)
        vm_input = config['vmInputGlobal']  #Grab the vm name
        vm_job = config['vmJobGlobal']  #Grab the job to be performed
        log.info(vm_input + " will perform a " + vm_job + " task")

        vm_job_name = "create"
        if vm_job == ("Restore Snapshot"):
            vm_job_name = "revert"
        elif vm_job == ("Activate Nodes"):
            vm_job_name = "activate_node"

        vm_input_name = "ppal-win10-01"  #Shorten the name given to the operational name
        if vm_input == "10.200.100.102/ppal-win10-02":
            vm_input_name = "ppal-win10-02"
        if vm_input == "10.200.100.103/ppal-win10-03":
            vm_input_name = "ppal-win10-03"
        if vm_input == "10.200.100.104/ppal-win10-04":
            vm_input_name = "ppal-win10-04"
        if vm_input == "10.200.100.199/ppal-win10-99":
            vm_input_name = "ppal-win10-99"

        if vm_input == 'Select All':
            if vm_job_name == ("create"):
                self.main('ppal-win10-01', "remove_all",
                          'ppal-win10-01_automated_snapshot', True)
                self.main('ppal-win10-02', "remove_all",
                          'ppal-win10-02_automated_snapshot', True)
                self.main('ppal-win10-03', "remove_all",
                          'ppal-win10-03_automated_snapshot', True)
                self.main('ppal-win10-04', "remove_all",
                          'ppal-win10-04_automated_snapshot', True)
                self.main('ppal-win10-99', "remove_all",
                          'ppal-win10-99_automated_snapshot', True)
            self.main('ppal-win10-01', vm_job_name,
                      'ppal-win10-01_automated_snapshot', False)
            self.main('ppal-win10-02', vm_job_name,
                      'ppal-win10-02_automated_snapshot', False)
            self.main('ppal-win10-03', vm_job_name,
                      'ppal-win10-03_automated_snapshot', False)
            self.main('ppal-win10-04', vm_job_name,
                      'ppal-win10-04_automated_snapshot', False)
            self.main('ppal-win10-99', vm_job_name,
                      'ppal-win10-99_automated_snapshot', False)
        else:
            if vm_job_name == ("create"):
                self.main(vm_input_name, "remove_all",
                          str(vm_input_name + "_automated_snapshot"), True)
            self.main(vm_input_name, vm_job_name,
                      str(vm_input_name + "_automated_snapshot"), False)
 def upload_file(self):
     log = logger.attach_to_logger(__name__)
     ftps = ftplib.FTP_TLS()
     ftps.connect('ftp.rivetnetworks.com', 21)
     ftps.auth()
     ftps.login('rivettemp', 'killernetworks2015')
     ftps.prot_p()
     ftps.cwd('/Test')
     file = open(self._UPLOAD_FILE_PATH, 'rb')
     try:
         log.info('Starting ftps file upload')
         ftps.storbinary('STOR test_upload.exe', file)
     except EOFError as e:
         log.info(f'EOF Error: {e}')
     except Exception as e:
         log.info(f'Exception caught when uploading file. Error: {e}')
     ftps.quit()
     file.close()
    def run_upload_test(self):  #Runs test
        log = logger.attach_to_logger(__name__)
        log.info('Starting: Upload Maximum Test')
        bandwidthup = "10000000"  #10mbs, an easy value for testing the feature
        bandwidthdown = "10000000"

        call(["sc", "stop", "Killer Network Service x64"
              ])  #Stops killer network service via command line

        #Now that killer network service is stopped, user.xml can be updated
        tree = ET.parse(
            'C:/ProgramData/RivetNetworks/Killer/user.xml'
        )  #Loads file, the files location should be consistent across all computers due to Killer's install process
        root = tree.getroot()  #Gets root out of the tree parse
        sh = root.find('NetworkInfos')  #Finds parent
        networkinfo = sh.find(
            'NetworkInfo'
        )  #Finds child which contains the attributes we are looking for

        networkinfo.set('BandwidthUp',
                        bandwidthup)  #Updates bandwidth upload max speed value
        networkinfo.set('BandwidthDown',
                        bandwidthdown)  #Updates bandwidth download max speed
        tree.write('C:/ProgramData/RivetNetworks/Killer/user.xml'
                   )  #Overrides the old user.xml file with a new updated file.

        #Now we need to start Killer and see if change is made
        call(["sc", "start", "Killer Network Service x64"
              ])  #Starts killer network service via command line
        global driver
        driver = webdriver.Chrome(
            executable_path='chromedriver.exe')  #Logs in as a chrome user
        driver.get('http://killernetworking.speedtestcustom.com/'
                   )  #Opens up killer's speed test via chrome
        id_box = driver.find_element_by_xpath(
            '//*[@id="main-content"]/div[1]/div/button'
        )  #Isolates the "go" button
        id_box.click()  #clicks go button
        time.sleep(60)  #waits for test to be completed
        upload_element = driver.find_element_by_xpath(
            '//*[@id="root"]/div/div[2]/div[2]/main/div[2]/div[2]/div[2]/div[2]/div/div/span'
        )  #Grabs upload element via xpath
        uploadSpeed = upload_element.text  #Grabs upload speed from element

        #Adds data to log for debugging
        log.info('Upload Speed: ' + str(uploadSpeed))

        #Converts from mb to bits
        uploadSpeed = float(uploadSpeed) * 1000000

        uploadError = 0.93 - uploadSpeed / float(
            bandwidthup
        )  #Killer automatically sets the max bandwidth to 93% of value set so the error formula reflects that

        #Adds data to log for debugging
        log.info('Upload Error: ' + str(uploadError))

        #Now the script determines if the test failed and returns info to Jenkins
        #Error < 1% = Perfect pass
        #Error < 3% = Pass with warning
        #Error > 3% = Fail
        if uploadError > 0.03 or uploadError < -0.03:
            raise Exception('Upload error greater than 3%')
        elif uploadError > 0.01 or uploadError < -0.01:
            log.info("Upload error greater than 1%")
        else:
            log.info("Upload error within 1%")
 def __init__(self):  #Initializes log
     log = logger.attach_to_logger(__name__)
     log.info('Starting: Bandwidth Maximum Test')
    def main(self, vm_input, vm_job, vm_snapshot_name, precautionary):

        log = logger.attach_to_logger(__name__)
        log.info('Starting Operation: ' + vm_job + " " + vm_input)

        inputs = {
            'vcenter_ip': '10.200.100.1',
            'vcenter_password': '******',
            'vcenter_user': '******',
            'vm_name': str(vm_input),
            # operation in 'create/remove/revert/
            # list_all/list_current/remove_all'
            'operation': str(vm_job),
            'snapshot_name': str(vm_snapshot_name),
            'ignore_ssl': True
        }  #Holds the data needed to log in

        si = None  #holds login token

        log.info("Connecting to VMWare Server...")
        log.info("Trying to connect to VCENTER SERVER . . .")

        context = None  #Will hold ssl token
        if inputs['ignore_ssl'] and hasattr(
                ssl, "_create_unverified_context"
        ):  #If the connection needs to be unverified
            context = ssl._create_unverified_context(
            )  #Make the context unverified

        si = connect.Connect(inputs['vcenter_ip'],
                             443,
                             inputs['vcenter_user'],
                             inputs['vcenter_password'],
                             sslContext=context)  #Login token

        atexit.register(
            Disconnect, si
        )  #Checks if the status is still disconnected to the vmware server

        log.info("Connection Affirmed")
        log.info("Connected to VCENTER SERVER !")

        content = si.RetrieveContent(
        )  #now that si is connected, refresh the context

        operation = inputs[
            'operation']  #grabs what operation the software will be performing from inputs
        vm_name = inputs[
            'vm_name']  #grabs the name of the vm the operation will be performed on

        vm = self.get_obj(content, [vim.VirtualMachine],
                          vm_name)  #Creates a vm obj to perform tasks on

        if not vm:  #If that vm doesn't match a real vm, fail the test
            log.info("Virtual Machine %s doesn't exists" % vm_name)
            raise Exception("Virtual Machine %s doesn't exists" % vm_name)

        if operation != 'create' and vm.snapshot is None:  #If there is no snapshot and the operation isn't to create one, fail test
            log.info("Virtual Machine %s doesn't have any snapshots" % vm.name)
            if precautionary == False:
                raise Exception(
                    "Virtual Machine %s doesn't have any snapshots" % vm.name)

        if operation == 'create':  #If operation is create, take a snapshot
            snapshot_name = inputs['snapshot_name']
            description = "Test snapshot"
            dumpMemory = False
            quiesce = False

            log.info("Creating snapshot %s for virtual machine %s" %
                     (snapshot_name, vm.name))
            WaitForTask(
                vm.CreateSnapshot(snapshot_name, description, dumpMemory,
                                  quiesce))
            #Warning: VMware only supports one snapshot at a time

        elif operation in [
                'remove', 'revert'
        ]:  #If the plan is to remove a snapshot or revert, check if there is a snapshot with the given name
            snapshot_name = inputs['snapshot_name']
            snap_obj = self.get_snapshots_by_name_recursively(
                vm.snapshot.rootSnapshotList, snapshot_name)
            # if len(snap_obj) is 0; then no snapshots with specified name
            if len(snap_obj) == 1:
                snap_obj = snap_obj[0].snapshot
                if operation == 'remove':
                    log.info("Removing snapshot %s" % snapshot_name)
                    WaitForTask(snap_obj.RemoveSnapshot_Task(True))
                else:
                    log.info("Reverting to snapshot %s" % snapshot_name)
                    WaitForTask(snap_obj.RevertToSnapshot_Task())
                    vm.PowerOn()  #turns on vm after revert completed
            else:
                log.info("No snapshots found with name: %s on VM: %s" %
                         (snapshot_name, vm.name))
                if precautionary == False:
                    raise Exception(
                        "No snapshots found with name: %s on VM: %s" %
                        (snapshot_name, vm.name))

        elif operation == 'list_all':  #This task is not currently in use but is left for possible future use
            log.info("Display list of snapshots on virtual machine %s" %
                     vm.name)
            snapshot_paths = self.list_snapshots_recursively(
                vm.snapshot.rootSnapshotList)
            for snapshot in snapshot_paths:
                log.info(snapshot)
        elif operation == 'activate_node':
            WaitForTask(vm.PowerOn())
            self.logIn(vm.name)

        elif operation == 'list_current':  #This task is not currently in use but is left for possible future use
            current_snapref = vm.snapshot.currentSnapshot
            current_snap_obj = self.get_current_snap_obj(
                vm.snapshot.rootSnapshotList, current_snapref)
            current_snapshot = "Name: %s; Description: %s; " \
                            "CreateTime: %s; State: %s" % (
                                    current_snap_obj[0].name,
                                    current_snap_obj[0].description,
                                    current_snap_obj[0].createTime,
                                    current_snap_obj[0].state)
            log.info("Virtual machine %s current snapshot is:" % vm.name)
            log.info(current_snapshot)

        elif operation == 'remove_all':  #This task is not currently in use but is left for possible future use
            log.info("Removing all snapshots for virtual machine %s" % vm.name)
            WaitForTask(vm.RemoveAllSnapshots())

        else:
            log.info("Specify operation in "
                     "create/remove/revert/list_all/list_current/remove_all")
 def __init__(self):  #Initializes log
     log = logger.attach_to_logger(__name__)
     log.info('Starting: Snapshot Job')
示例#15
0
    def run_test(self):  #Runs test
        log = logger.attach_to_logger(__name__)
        bandwidthup = "100000000"  #100mbs, an easy value for testing the feature
        bandwidthdown = "100000000"

        log.info("Turning off Killer Networking Service")
        call(["sc", "stop", "Killer Network Service x64"
              ])  #Stops killer network service via command line
        call(["sc", "stop", "KfeCoSvc"])
        call([
            "reg", "add",
            R"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\KfeCoSvc\Parameters",
            "/v", "BypassLocalLan", "/t", "REG_DWORD", "/d", "0"
        ])

        log.info("Lan Exceptions registry key remade")

        #Now that killer network service is stopped, user.xml can be updated
        tree = ET.parse(
            'C:/ProgramData/RivetNetworks/Killer/user.xml'
        )  #Loads file, the files location should be consistent across all computers due to Killer's install process
        root = tree.getroot()  #Gets root out of the tree parse
        sh = root.find('NetworkInfos')  #Finds parent
        networkinfo = sh.find(
            'NetworkInfo'
        )  #Finds child which contains the attributes we are looking for

        networkinfo.set('BandwidthUp',
                        bandwidthup)  #Updates bandwidth upload max speed value
        networkinfo.set(
            'BandwidthDown',
            bandwidthdown)  #Updates bandwith download max speed value
        tree.write('C:/ProgramData/RivetNetworks/Killer/user.xml'
                   )  #Overrides the old user.xml file with a new updated file.

        #Now we need to start Killer and see if change is made
        call(["sc", "start", "Killer Network Service x64"
              ])  #Starts killer network service via command line
        call(["sc", "start", "KfeCoSvc"])

        log = logger.attach_to_logger(__name__)
        log.info('Attempting to log in:')
        call([
            "psexec", "\\\\10.200.100.199", "-u", "test", "-p", "test",
            "C:/Users/test/Desktop/iperf-3.0.11-win64/iperf3.exe", "-s"
        ])
        log.info('logged in')
        log.info('Server Client running')
        time.sleep(15)
        command = ["iperf3.exe", "-c", "10.200.100.199", "-t", "60"]
        result = subprocess.run(command, stdout=subprocess.PIPE)
        log.info(result)
        output = str(result)
        log.info("Iperf Test Output:" + output)
        index = output.rfind('bits')
        log.info(output[index - 1])
        if (output[index - 1] == "M"):
            log.info("In Megabits")
        log.info("Download Speed: " + output[index - 6:index])
        downloadSpeed = float(output[index - 6:index - 2])
        uploadSpeedString = output[1:index - 9]
        index = uploadSpeedString.rfind('bits')
        log.info(uploadSpeedString[index - 1])
        if (uploadSpeedString[index - 1] == "M"):
            log.info("In Megabits")
        log.info("Upload Speed: " + uploadSpeedString[index - 6:index])
        uploadSpeed = float(uploadSpeedString[index - 6:index - 2])

        log.info('Download Speed: ' +
                 str(downloadSpeed))  #Adds data to log for debugging
        log.info('Upload Speed: ' + str(uploadSpeed))

        downloadSpeed = downloadSpeed * 1000000  #Converts from mb to bits
        uploadSpeed = uploadSpeed * 1000000

        downloadError = 0.93 - downloadSpeed / float(
            bandwidthdown)  #Finds the % off the test was
        uploadError = 0.93 - uploadSpeed / float(
            bandwidthup
        )  #Killer automatically sets the max bandwidth to 93% of value set so the error formula reflects that

        log.info('Download Error: ' +
                 str(downloadError))  #Adds data to log for debugging
        log.info('Upload Error: ' + str(uploadError))

        #Now the script determines if the test failed and returns info to Jenkins
        #Error < 1% = Perfect pass
        #Error < 3% = Pass with warning
        #Error > 3% = Fail
        if downloadError > -0.01 and downloadError < 0.01:  #If download error passes perfectly
            if uploadError > -0.01 and uploadError < 0.01:  #If upload error also passes perfectly, pass test
                return 'PASS'
            elif uploadError > -0.03 and uploadError < 0.03:
                return 'WARN due to upload speed error being bigger than 1%'
            else:
                raise Exception(
                    "Test failed because of upload speed error being > 3%")

        elif downloadError > -0.03 and downloadError < 0.03:  #If within 3%, Warn
            if uploadError > 0.03 or uploadError < -0.03:
                raise Exception(
                    "Test failed because of upload speed error being > 3%")
            elif uploadError > -0.01 and uploadError < 0.01:
                return 'WARN due to download speed error being > 1%'
            else:
                return 'WARN due to both download and upload speed error being > 1%'

        else:  #If > 3% error fail the test
            if uploadError > -0.01 and uploadError < 0.01:
                raise Exception(
                    "Test failed because of download error being > 0.03 but upload error is less than 1%"
                )
            elif uploadError > -0.03 and uploadError < 0.03:
                raise Exception(
                    'Test failed because of download error being > 0.03 but upload error is less than 3%'
                )
            else:
                raise Exception(
                    'Test failed because both upload and download error are > 3%'
                )
示例#16
0
 def __init__(self):  #Initializes log
     log = logger.attach_to_logger(__name__)
     log.info('Starting: Iperf Test')