Ejemplo n.º 1
0
def run_test(mytest, test_config=TestConfig(), context=None, curl_handle=None, *args, **kwargs):
    """ Put together test pieces: configure & run actual test, return results """

    # Initialize a context if not supplied
    my_context = context
    if my_context is None:
        my_context = Context()

    mytest.update_context_before(my_context)
    templated_test = mytest.realize(my_context)
    curl = templated_test.configure_curl(
        timeout=test_config.timeout, context=my_context, curl_handle=curl_handle)
    result = TestResponse()
    result.test = templated_test

    # reset the body, it holds values from previous runs otherwise
    headers = MyIO()
    body = MyIO()
    curl.setopt(pycurl.WRITEFUNCTION, body.write)
    curl.setopt(pycurl.HEADERFUNCTION, headers.write)
    if test_config.verbose:
        curl.setopt(pycurl.VERBOSE, True)
    if test_config.ssl_insecure:
        curl.setopt(pycurl.SSL_VERIFYPEER, 0)
        curl.setopt(pycurl.SSL_VERIFYHOST, 0)

    result.passed = None

    if test_config.interactive:
        print("===================================")
        print("%s" % mytest.name)
        print("-----------------------------------")
        print("REQUEST:")
        print("%s %s" % (templated_test.method, templated_test.url))
        print("HEADERS:")
        print("%s" % (templated_test.headers))
        if mytest.body is not None:
            print("\n%s" % templated_test.body)
        raw_input("Press ENTER when ready (%d): " % (mytest.delay))

    if mytest.delay > 0:
        print("Delaying for %ds" % mytest.delay)
        time.sleep(mytest.delay)

    try:
        curl.perform()  # Run the actual call
    except Exception as e:
        # Curl exception occurred (network error), do not pass go, do not
        # collect $200
        trace = traceback.format_exc()
        result.failures.append(Failure(message="Curl Exception: {0}".format(
            e), details=trace, failure_type=validators.FAILURE_CURL_EXCEPTION))
        result.passed = False
        curl.close()
        return result

    # Retrieve values
    result.body = body.getvalue()
    body.close()
    result.response_headers = text_type(headers.getvalue(), HEADER_ENCODING)  # Per RFC 2616
    headers.close()

    response_code = curl.getinfo(pycurl.RESPONSE_CODE)
    result.response_code = response_code

    logger.debug("Initial Test Result, based on expected response code: " +
                 str(response_code in mytest.expected_status))

    if response_code in mytest.expected_status:
        result.passed = True
    else:
        # Invalid response code
        result.passed = False
        failure_message = "Invalid HTTP response code: response code {0} not in expected codes [{1}]".format(
            response_code, mytest.expected_status)
        result.failures.append(Failure(
            message=failure_message, details=None, failure_type=validators.FAILURE_INVALID_RESPONSE))

    # Parse HTTP headers
    try:
        result.response_headers = parse_headers(result.response_headers)
    except Exception as e:
        trace = traceback.format_exc()
        result.failures.append(Failure(message="Header parsing exception: {0}".format(
            e), details=trace, failure_type=validators.FAILURE_TEST_EXCEPTION))
        result.passed = False
        curl.close()
        return result

    # print str(test_config.print_bodies) + ',' + str(not result.passed) + ' ,
    # ' + str(test_config.print_bodies or not result.passed)

    head = result.response_headers

    # execute validator on body
    if result.passed is True:
        body = result.body
        if mytest.validators is not None and isinstance(mytest.validators, list):
            logger.debug("executing this many validators: " +
                         str(len(mytest.validators)))
            failures = result.failures
            for validator in mytest.validators:
                validate_result = validator.validate(
                    body=body, headers=head, context=my_context)
                if not validate_result:
                    result.passed = False
                # Proxy for checking if it is a Failure object, because of
                # import issues with isinstance there
                if hasattr(validate_result, 'details'):
                    failures.append(validate_result)
                # TODO add printing of validation for interactive mode
        else:
            logger.debug("no validators found")

        # Only do context updates if test was successful
        mytest.update_context_after(result.body, head, my_context)

    # Print response body if override is set to print all *OR* if test failed
    # (to capture maybe a stack trace)
    if test_config.print_bodies or not result.passed:
        if test_config.interactive:
            print("RESPONSE:")
        print(result.body.decode(ESCAPE_DECODING))

    if test_config.print_headers or not result.passed:
        if test_config.interactive:
            print("RESPONSE HEADERS:")
        print(result.response_headers)

    # TODO add string escape on body output
    logger.debug(result)

    return result
Ejemplo n.º 2
0
def run_test(mytest,
             test_config=TestConfig(),
             context=None,
             curl_handle=None,
             *args,
             **kwargs):
    """ Put together test pieces: configure & run actual test, return results """
    # Initialize a context if not supplied
    my_context = context
    if my_context is None:
        my_context = Context()

    mytest.update_context_before(my_context)
    templated_test = mytest.realize(my_context)

    result = TestResponse()
    result.test = templated_test

    session = requests.Session()

    # generate and attach signature to header
    req = templated_test.configure_request(timeout=test_config.timeout,
                                           context=my_context,
                                           curl_handle=curl_handle)

    if test_config.verbose:
        session.verbose = True
    if test_config.ssl_insecure:
        session.verify = False

    headers = MyIO()
    body = MyIO()

    prepped = req.prepare()
    prepped = signer.request_signer(prepped, TestConfig.key)

    result.passed = None

    if test_config.interactive:
        LOGGER.debug("===================================")
        LOGGER.debug("%s" % mytest.name)
        LOGGER.debug("-----------------------------------")
        LOGGER.debug("REQUEST:")
        LOGGER.debug("%s %s" % (templated_test.method, templated_test.url))
        LOGGER.debug("HEADERS:")
        LOGGER.debug("%s" % prepped.headers)
        if mytest.body is not None:
            LOGGER.debug("\n%s" % templated_test.body)

        if sys.version_info >= (3, 0):
            input("Press ENTER when ready (%d): " % (mytest.delay))
        else:
            raw_input("Press ENTER when ready (%d): " % (mytest.delay))

    if mytest.delay > 0:
        LOGGER.info("Delaying for %ds" % mytest.delay)
        time.sleep(mytest.delay)

    try:
        response = session.send(prepped)
    except Exception as error:
        # exception occurred (network error), do not pass go, do not
        # collect $200
        trace = traceback.format_exc()
        result.failures.append(
            Failure(message="Request Exception: {0}".format(error),
                    details=trace,
                    failure_type=validators.FAILURE_CURL_EXCEPTION))
        result.passed = False
        session.close()
        return result

    # Retrieve values
    result.body = response.content
    body.close()

    result.response_headers = response.headers  # Per RFC 2616
    headers.close()

    response_code = response.status_code
    result.response_code = response_code

    LOGGER.debug("Initial Test Result, based on expected response code: " +
                 str(response_code in mytest.expected_status))

    if response_code in mytest.expected_status:
        result.passed = True
    else:
        # Invalid response code
        result.passed = False
        failure_message = \
            "Invalid HTTP response code: response code " \
            "{0} not in expected codes [{1}]".format(response_code,
                                                     mytest.expected_status)
        result.failures.append(
            Failure(message=failure_message,
                    details=None,
                    failure_type=validators.FAILURE_INVALID_RESPONSE))

    # Parse HTTP headers
    try:
        result.response_headers = parse_headers(result.response_headers)
    except Exception as error:
        trace = traceback.format_exc()
        result.failures.append(
            Failure(message="Header parsing exception: {0}".format(error),
                    details=trace,
                    failure_type=validators.FAILURE_TEST_EXCEPTION))
        result.passed = False
        session.close()
        return result

    head = result.response_headers

    # execute validator on body
    if result.passed is True:
        body = result.body
        if mytest.validators is not None and isinstance(
                mytest.validators, list):
            LOGGER.debug("executing this many validators: " +
                         str(len(mytest.validators)))
            failures = result.failures
            for validator in mytest.validators:
                validate_result = validator.validate(body=body,
                                                     headers=head,
                                                     context=my_context)
                if not validate_result:
                    result.passed = False
                # Proxy for checking if it is a Failure object, because of
                # import issues with isinstance there
                if hasattr(validate_result, 'details'):
                    failures.append(validate_result)
                # TODO add printing of validation for interactive mode
        else:
            LOGGER.debug("no validators found")

        # Only do context updates if test was successful
        mytest.update_context_after(result.body, head, my_context)

    # Print response body if override is set to print all *OR* if test failed
    # (to capture maybe a stack trace)
    if test_config.print_bodies or not result.passed:
        if test_config.interactive:
            LOGGER.info("RESPONSE:")
        LOGGER.info(result.body.decode(ESCAPE_DECODING))

    if test_config.print_headers or not result.passed:
        if test_config.interactive:
            LOGGER.info("RESPONSE HEADERS:")
        LOGGER.info(result.response_headers)

    # TODO add string escape on body output
    LOGGER.debug(result)

    return result
Ejemplo n.º 3
0
    def configure_curl(self,
                       timeout=DEFAULT_TIMEOUT,
                       context=None,
                       curl_handle=None):
        """ Create and mostly configure a curl object for test, reusing existing if possible """

        if curl_handle:
            curl = curl_handle
        else:
            curl = pycurl.Curl()

        # curl.setopt(pycurl.VERBOSE, 1)  # Debugging convenience
        curl.setopt(curl.URL, str(self.url))
        curl.setopt(curl.TIMEOUT, timeout)

        is_unicoded = False
        bod = self.body
        if isinstance(bod, text_type):  # Encode unicode
            bod = bod.encode('UTF-8')
            is_unicoded = True

        # Set read function for post/put bodies
        if bod and len(bod) > 0:
            curl.setopt(curl.READFUNCTION, MyIO(bod).read)

        if self.auth_username and self.auth_password:
            curl.setopt(pycurl.USERPWD,
                        '%s:%s' % (self.auth_username, self.auth_password))
            if self.auth_type:
                curl.setopt(pycurl.HTTPAUTH, self.auth_type)

        if self.method == u'POST':
            curl.setopt(HTTP_METHODS[u'POST'], 1)
            # Required for some servers
            if bod is not None:
                curl.setopt(pycurl.POSTFIELDSIZE, len(bod))
            else:
                curl.setopt(pycurl.POSTFIELDSIZE, 0)
        elif self.method == u'PUT':
            curl.setopt(HTTP_METHODS[u'PUT'], 1)
            # Required for some servers
            if bod is not None:
                curl.setopt(pycurl.INFILESIZE, len(bod))
            else:
                curl.setopt(pycurl.INFILESIZE, 0)
        elif self.method == u'PATCH':
            curl.setopt(curl.POSTFIELDS, bod)
            curl.setopt(curl.CUSTOMREQUEST, 'PATCH')
            # Required for some servers
            if bod is not None:
                curl.setopt(pycurl.INFILESIZE, len(bod))
            else:
                curl.setopt(pycurl.INFILESIZE, 0)
        elif self.method == u'DELETE':
            curl.setopt(curl.CUSTOMREQUEST, 'DELETE')
        elif self.method and self.method.upper() != 'GET':  # Support HEAD/ETC
            curl.setopt(curl.CUSTOMREQUEST, self.method.upper())

        # Template headers as needed and convert headers dictionary to list of header entries
        head = self.get_headers(context=context)
        head = copy.copy(head)  # We're going to mutate it, need to copy

        # Set charset if doing unicode conversion and not set explicitly
        # TESTME
        if is_unicoded and u'content-type' in head.keys():
            content = head[u'content-type']
            if u'charset' not in content:
                head[u'content-type'] = content + u' ; charset=UTF-8'

        if head:
            headers = [
                str(headername) + ':' + str(headervalue)
                for headername, headervalue in head.items()
            ]
        else:
            headers = list()
        # Fix for expecting 100-continue from server, which not all servers
        # will send!
        headers.append("Expect:")
        headers.append("Connection: close")
        curl.setopt(curl.HTTPHEADER, headers)

        # Set custom curl options, which are KEY:VALUE pairs matching the pycurl option names
        # And the key/value pairs are set
        if self.curl_options:
            filterfunc = lambda x: x[0] is not None and x[
                1] is not None  # Must have key and value
            for (key, value) in ifilter(filterfunc, self.curl_options.items()):
                # getattr to look up constant for variable name
                curl.setopt(getattr(curl, key), value)
        return curl
Ejemplo n.º 4
0
    def configure_curl(self,
                       timeout=DEFAULT_TIMEOUT,
                       cookiejar=None,
                       context=None,
                       curl_handle=None):
        """ Create and mostly configure a curl object for test, reusing existing if possible """

        if curl_handle:
            curl = curl_handle
            try:  # Check the curl handle isn't closed, and reuse it if possible
                curl.getinfo(curl.HTTP_CODE)
                curl.reset()
                if not cookiejar:
                    # Below clears the cookies & curl options for clean run
                    # But retains the DNS cache and connection pool
                    curl.setopt(curl.COOKIELIST, "ALL")
            except pycurl.error:
                curl = pycurl.Curl()

        else:
            curl = pycurl.Curl()

        if cookiejar:
            curl.setopt(curl.COOKIEJAR, cookiejar)
            curl.setopt(curl.COOKIEFILE, cookiejar)
            curl.setopt(curl.COOKIELIST, "RELOAD")

        # curl.setopt(pycurl.VERBOSE, 1)  # Debugging convenience
        curl.setopt(curl.URL, str(self.url))
        curl.setopt(curl.TIMEOUT, timeout)

        is_unicoded = False
        bod = self.body
        if isinstance(bod, text_type):  # Encode unicode
            bod = bod.encode('UTF-8')
            is_unicoded = True

        # Set read function for post/put bodies
        if bod and len(bod) > 0:
            curl.setopt(curl.READFUNCTION, MyIO(bod).read)

        if self.auth_username and self.auth_password:
            curl.setopt(
                pycurl.USERPWD,
                parsing.encode_unicode_bytes(self.auth_username) + b':' +
                parsing.encode_unicode_bytes(self.auth_password))
            if self.auth_type:
                curl.setopt(pycurl.HTTPAUTH, self.auth_type)
        if self.method == u'GET':
            curl.setopt(HTTP_METHODS[u'GET'], 1)
        elif self.method == u'POST':
            curl.setopt(HTTP_METHODS[u'POST'], 1)
            # Required for some servers
            if bod is not None:
                curl.setopt(pycurl.POSTFIELDSIZE, len(bod))
            else:
                curl.setopt(pycurl.POSTFIELDSIZE, 0)
        elif self.method == u'PUT':
            curl.setopt(HTTP_METHODS[u'PUT'], 1)
            # Required for some servers
            if bod is not None:
                curl.setopt(pycurl.INFILESIZE, len(bod))
            else:
                curl.setopt(pycurl.INFILESIZE, 0)
        elif self.method == u'PATCH':
            curl.setopt(curl.POSTFIELDS, bod)
            curl.setopt(curl.CUSTOMREQUEST, 'PATCH')
            # Required for some servers
            # I wonder: how compatible will this be?  It worked with Django but feels iffy.
            if bod is not None:
                curl.setopt(pycurl.INFILESIZE, len(bod))
            else:
                curl.setopt(pycurl.INFILESIZE, 0)
        elif self.method == u'DELETE':
            curl.setopt(curl.CUSTOMREQUEST, 'DELETE')
            if bod is not None:
                curl.setopt(pycurl.POSTFIELDS, bod)
                curl.setopt(pycurl.POSTFIELDSIZE, len(bod))
        elif self.method == u'HEAD':
            curl.setopt(curl.NOBODY, 1)
            curl.setopt(curl.CUSTOMREQUEST, 'HEAD')
        elif self.method and self.method.upper(
        ) != 'GET':  # Alternate HTTP methods
            curl.setopt(curl.CUSTOMREQUEST, self.method.upper())
            if bod is not None:
                curl.setopt(pycurl.POSTFIELDS, bod)
                curl.setopt(pycurl.POSTFIELDSIZE, len(bod))

        # Template headers as needed and convert headers dictionary to list of header entries
        head = self.get_headers(context=context)
        head = copy.copy(head)  # We're going to mutate it, need to copy

        # Set charset if doing unicode conversion and not set explicitly
        # TESTME
        if is_unicoded and u'content-type' in head.keys():
            content = head[u'content-type']
            if u'charset' not in content:
                head[u'content-type'] = content + u' ; charset=UTF-8'

        if head:
            headers = [
                str(headername) + ':' + str(headervalue)
                for headername, headervalue in head.items()
            ]
        else:
            headers = list()
        # Fix for expecting 100-continue from server, which not all servers
        # will send!
        headers.append("Expect:")
        headers.append("Connection: close")
        curl.setopt(curl.HTTPHEADER, headers)

        # Set custom curl options, which are KEY:VALUE pairs matching the pycurl option names
        # And the key/value pairs are set
        if self.curl_options:
            filterfunc = lambda x: x[0] is not None and x[
                1] is not None  # Must have key and value
            for (key, value) in ifilter(filterfunc, self.curl_options.items()):
                # getattr to look up constant for variable name
                curl.setopt(getattr(curl, key), value)
        return curl