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
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
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
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