def _convertTemplateOldFormat(self, file2read): # now conversion to the old format #----------------------------------------- strTemplate = '' # first we have a list of templates with 2 keys: request and response for i, templateLine in enumerate(self.yamlTemplate): dictTemplateLine = dict(templateLine) if not all(name in ('request', 'response') for name in dictTemplateLine.keys()): logger.error( 'Incorrect format - each level 0 list has to be a dictionary with the keys "request" and "response" while reading "%s"!' % (file2read)) raise MySyntaxError( 'Incorrect format - each level 0 list has to be a dictionary with the keys "request" and "response" while reading "%s"!' % (file2read)) strTemplate += self._convertOldFormat(i, 'request', dictTemplateLine) strTemplate += self._convertOldFormat(i, 'response', dictTemplateLine) logger.trace('_readTemplate[YAML]="%s"' % (strTemplate)) return strTemplate
def _convertOldFormat(self, index, typeStr, dic): ''' Factorization, for the Yaml conversion to the old format yaml format is : - request: uri: <an uri> body: <a body> headers: key1: value1 key2: value2 response: statuscode: 200 delay_ms: 50 - request: (...) :param index: this is the index sequence of the yaml, the sequence separator is the "-" character for a list in yaml :param typeStr: request or response key :param dic: the sub dictionary for the key request or the key response ''' #request={uri=/context/create, headers={Content-Type=application/json;charset=utf-8}, body={"contextKey" : "msisdn", "value" : 334, "host" : "10.10.153.126", "port" : 3000, "expirationtime": 1456789}}, response={statuscode=200, delay_ms=20} try: dataDict = dict(dic[typeStr]) except: logger.error( 'while loading "%s" - yaml key "%s" has to be at level0 a dictionary !' % (self.templateShortName, typeStr)) raise MySyntaxError( 'while loading "%s" - yaml key "%s" has to be at level0 a dictionary !' % (self.templateShortName, typeStr)) # Look if we have a json content isJson = False if 'headers' in dataDict: for name, value in dict(dataDict['headers']).iteritems(): if name.lower() == 'content-type': isJson = value.lower().find('application/json') >= 0 if logger.isDebugEnabled(): logger.debug('[name=%s][value=%s][isJson=%s]' % (name, value, isJson)) # Solve the problem of empty lines in a SOAP http request if typeStr == 'request': if 'body' in dataDict: dataDict['body'] = '\n'.join([ line for line in dataDict['body'].split('\n') if line.strip() ]) retStr = '' # Old format header separator (pipe) could be overloaded delimiter = dataDict.get('header_separator', '|') for name, value in dataDict.iteritems(): if logger.isTraceEnabled(): logger.trace( '[%s_convertOldFormat] - [name=%s][value=%s][type value=%s]' % (self.__class__.__name__, name, value, type(value))) # very unlikely # Avoid misunderstanding with the yaml format (if people use previous syntax request0.xxx keyword) m = self.oldFormatPattern.matcher(name) if m.find(): name = name.replace(m.group(1), '') if name in ('uri'): value = str(value) if isinstance(value, LinkedHashMap): # Yaml does a strange interpretation of string for Json if you don't have a single quote around your string if name == 'body': value = JSONSerializer.toJSON(value) if isJson else str( value) elif name in 'headers': s = '' for name1, value1 in dict(value).iteritems(): s += '%s:%s%s' % (name1, value1, delimiter) value = s retStr += '%s%d.%s=%s\n' % (typeStr, index, name, value) return retStr
class Command: memoryPattern = Pattern.compile("@.+@") # old format is : [request|response]<indice>.<keyword>=<value> oldFormatPattern = Pattern.compile("(\w+\.)\w+") def __init__(self, templateFilePath, templateShortName, templateType): self.templateFilePath = templateFilePath self.templateShortName = templateShortName self.templateType = templateType # Yaml template file self.isYamlTemplate = False # The template in Yaml format self.yamlTemplate = None if templateType in ['string', 'text']: self.templateAsString = templateShortName return # the final template file name file2read = '' if templateType == 'yaml_inline': self.yamlTemplate = templateShortName self.isYamlTemplate = True else: # template in file file2read = '%s/%s.%s' % (self.templateFilePath, self.templateShortName, 'template') self._readTemplate(file2read) #---------------------- # Here we should have a Yaml file loaded in the variable self.yamlTemplate #------------------ # Store the template under the old string format self.templateAsString = self._convertTemplateOldFormat(file2read) def _convertOldFormat(self, index, typeStr, dic): ''' Factorization, for the Yaml conversion to the old format yaml format is : - request: uri: <an uri> body: <a body> headers: key1: value1 key2: value2 response: statuscode: 200 delay_ms: 50 - request: (...) :param index: this is the index sequence of the yaml, the sequence separator is the "-" character for a list in yaml :param typeStr: request or response key :param dic: the sub dictionary for the key request or the key response ''' #request={uri=/context/create, headers={Content-Type=application/json;charset=utf-8}, body={"contextKey" : "msisdn", "value" : 334, "host" : "10.10.153.126", "port" : 3000, "expirationtime": 1456789}}, response={statuscode=200, delay_ms=20} try: dataDict = dict(dic[typeStr]) except: logger.error( 'while loading "%s" - yaml key "%s" has to be at level0 a dictionary !' % (self.templateShortName, typeStr)) raise MySyntaxError( 'while loading "%s" - yaml key "%s" has to be at level0 a dictionary !' % (self.templateShortName, typeStr)) # Look if we have a json content isJson = False if 'headers' in dataDict: for name, value in dict(dataDict['headers']).iteritems(): if name.lower() == 'content-type': isJson = value.lower().find('application/json') >= 0 if logger.isDebugEnabled(): logger.debug('[name=%s][value=%s][isJson=%s]' % (name, value, isJson)) # Solve the problem of empty lines in a SOAP http request if typeStr == 'request': if 'body' in dataDict: dataDict['body'] = '\n'.join([ line for line in dataDict['body'].split('\n') if line.strip() ]) retStr = '' # Old format header separator (pipe) could be overloaded delimiter = dataDict.get('header_separator', '|') for name, value in dataDict.iteritems(): if logger.isTraceEnabled(): logger.trace( '[%s_convertOldFormat] - [name=%s][value=%s][type value=%s]' % (self.__class__.__name__, name, value, type(value))) # very unlikely # Avoid misunderstanding with the yaml format (if people use previous syntax request0.xxx keyword) m = self.oldFormatPattern.matcher(name) if m.find(): name = name.replace(m.group(1), '') if name in ('uri'): value = str(value) if isinstance(value, LinkedHashMap): # Yaml does a strange interpretation of string for Json if you don't have a single quote around your string if name == 'body': value = JSONSerializer.toJSON(value) if isJson else str( value) elif name in 'headers': s = '' for name1, value1 in dict(value).iteritems(): s += '%s:%s%s' % (name1, value1, delimiter) value = s retStr += '%s%d.%s=%s\n' % (typeStr, index, name, value) return retStr def _convertTemplateOldFormat(self, file2read): # now conversion to the old format #----------------------------------------- strTemplate = '' # first we have a list of templates with 2 keys: request and response for i, templateLine in enumerate(self.yamlTemplate): dictTemplateLine = dict(templateLine) if not all(name in ('request', 'response') for name in dictTemplateLine.keys()): logger.error( 'Incorrect format - each level 0 list has to be a dictionary with the keys "request" and "response" while reading "%s"!' % (file2read)) raise MySyntaxError( 'Incorrect format - each level 0 list has to be a dictionary with the keys "request" and "response" while reading "%s"!' % (file2read)) strTemplate += self._convertOldFormat(i, 'request', dictTemplateLine) strTemplate += self._convertOldFormat(i, 'response', dictTemplateLine) logger.trace('_readTemplate[YAML]="%s"' % (strTemplate)) return strTemplate def _readTemplate(self, file2read): ''' evaluate if the template has a yaml format ''' if not self.isYamlTemplate: initialFile = file2read logger.debug('[%s._readTemplate] Looking for file: "%s"' % (self.__class__.__name__, initialFile)) if not os.path.exists(file2read): # check both .yaml & .yml extension file2read = '%s.yaml' % (file2read) logger.debug('[%s._readTemplate] Looking for file: "%s"' % (self.__class__.__name__, file2read)) if os.path.exists(file2read): self.isYamlTemplate = True else: file2read = '%s.yml' % (file2read) logger.debug('[%s._readTemplate] Looking for file: "%s"' % (self.__class__.__name__, file2read)) if os.path.exists(file2read): self.isYamlTemplate = True else: logger.error( '[%s._readTemplate] Template File "%s" doesn\'t exist (even with yaml extension)' % (self.__class__.__name__, initialFile)) raise MySyntaxError( '[%s._readTemplate] Template File "%s" doesn\'t exist (even with yaml extension)' % (self.__class__.__name__, initialFile)) # So the template file exists try: logger.debug('[%s._readTemplate] Reading template file "%s"' % (self.__class__.__name__, file2read)) lines = open(file2read, 'r').readlines() except: logger.error( '[%s._readTemplate] failure opening template file "%s"' % (self.__class__.__name__, file2read)) raise MySyntaxError( '[%s._readTemplate] failure opening template file "%s"' % (self.__class__.__name__, file2read)) # Shebang testing for Yaml format if not self.isYamlTemplate and (lines[0].find('#!yaml') >= 0 or lines[0].find('#!yml') >= 0): self.isYamlTemplate = True if not self.isYamlTemplate: logger.error( '[%s._readTemplate] compatibility issue ! template must be YAML data', (self.__class__.__name__)) raise SyntaxError( '[%s._readTemplate] compatibility issue ! template must be YAML data', (self.__class__.__name__)) # Yaml format: load the string to Yaml if we don't have already if not self.yamlTemplate: yaml = Yaml(Constructor(), Representer(), DumperOptions(), CustomResolver()) try: self.yamlTemplate = yaml.load(''.join(lines).strip()) except (MarkedYAMLException, YAMLException, ParserException, ReaderException, ScannerException), e: logger.error('Error while parsing YAML-file "%s":\n%s' % (file2read, e)) raise MySyntaxError('Error while parsing YAML-file "%s":\n%s' % (file2read, e)) logger.trace("_readTemplate - Loaded Yaml : '''%s'''" % (self.yamlTemplate)) # Templates are list object if not isinstance(list(self.yamlTemplate), list): logger.error( 'Yaml template must be a list of dictionaries while reading "%s"!' % file2read) raise MySyntaxError( 'Yaml template must be a list of dictionaries while reading "%s"!' % file2read)
def _readTemplate(self, file2read): ''' evaluate if the template has a yaml format ''' if not self.isYamlTemplate: initialFile = file2read logger.debug('[%s._readTemplate] Looking for file: "%s"' % (self.__class__.__name__, initialFile)) if not os.path.exists(file2read): # check both .yaml & .yml extension file2read = '%s.yaml' % (file2read) logger.debug('[%s._readTemplate] Looking for file: "%s"' % (self.__class__.__name__, file2read)) if os.path.exists(file2read): self.isYamlTemplate = True else: file2read = '%s.yml' % (file2read) logger.debug('[%s._readTemplate] Looking for file: "%s"' % (self.__class__.__name__, file2read)) if os.path.exists(file2read): self.isYamlTemplate = True else: logger.error( '[%s._readTemplate] Template File "%s" doesn\'t exist (even with yaml extension)' % (self.__class__.__name__, initialFile)) raise MySyntaxError( '[%s._readTemplate] Template File "%s" doesn\'t exist (even with yaml extension)' % (self.__class__.__name__, initialFile)) # So the template file exists try: logger.debug('[%s._readTemplate] Reading template file "%s"' % (self.__class__.__name__, file2read)) lines = open(file2read, 'r').readlines() except: logger.error( '[%s._readTemplate] failure opening template file "%s"' % (self.__class__.__name__, file2read)) raise MySyntaxError( '[%s._readTemplate] failure opening template file "%s"' % (self.__class__.__name__, file2read)) # Shebang testing for Yaml format if not self.isYamlTemplate and (lines[0].find('#!yaml') >= 0 or lines[0].find('#!yml') >= 0): self.isYamlTemplate = True if not self.isYamlTemplate: logger.error( '[%s._readTemplate] compatibility issue ! template must be YAML data', (self.__class__.__name__)) raise SyntaxError( '[%s._readTemplate] compatibility issue ! template must be YAML data', (self.__class__.__name__)) # Yaml format: load the string to Yaml if we don't have already if not self.yamlTemplate: yaml = Yaml(Constructor(), Representer(), DumperOptions(), CustomResolver()) try: self.yamlTemplate = yaml.load(''.join(lines).strip()) except (MarkedYAMLException, YAMLException, ParserException, ReaderException, ScannerException), e: logger.error('Error while parsing YAML-file "%s":\n%s' % (file2read, e)) raise MySyntaxError('Error while parsing YAML-file "%s":\n%s' % (file2read, e)) logger.trace("_readTemplate - Loaded Yaml : '''%s'''" % (self.yamlTemplate))
def checkRequiredProperty(key): if key not in properties: raise MySyntaxError( '**** required key "%s" **** is not defined !' % (key))
def __init__(self, rule, deferred=False): # all keys must be lower case self.ruleDefinition = dict( (k.lower(), v) for k, v in dict(rule).iteritems()) # Only one keyword is authorized intersect = set(self.KEYWORDS).intersection( set(self.ruleDefinition.keys())) if len(intersect) > 1: logger.error( 'We cannot have more than one keywords "%s" in the same rule among "%s"' % (intersect, self.KEYWORDS)) raise MySyntaxError( 'We cannot have than one keywords "%s" in the same rule among"%s"' % (intersect, self.KEYWORDS)) if len(intersect) == 0: logger.error( 'Invalid rule: missing mandatory comparison keywords. One of "%s"' % self.KEYWORDS) raise MySyntaxError( 'Invalid rule: missing mandatory comparison keywords. One of "%s"' % self.KEYWORDS) # the only one keyword self.ruleType = list(intersect)[0] # For Async assertion self.deferred = 'deferred' in self.ruleDefinition or deferred # ignoreCase for Xpath & Regexp self.ignoreCase = 'ignorecase' in self.ruleDefinition and str( self.ruleDefinition['ignorecase']).lower() in ('y', 'yes', 'true') # For "not" expression (notequals, notcontains ...) self.positiveCheck = True # if there is a ${VAR} template self.isPlaceholder = False # "equals" rule are special "contains" rule self.equalsRule = False # For Regexp rules self.isCompiledRule = False self.compiledRule = None self.regexpExpression = None # For Xpath rules self.hasXpath = False self.xpathExpression = None self.textQuery = False self.isXpathPlaceholder = False self.compiledXpathRule = None self.isXpathCompiledRule = False # duration rule if 'maxduration' in self.ruleDefinition: try: s_duration = self.ruleDefinition['maxduration'].strip() self.maxDuration = int(s_duration) # this means that we have a string # see if we have the except ValueError: try: if s_duration[-2:] == 'ms': self.maxDuration = int(s_duration[:-2]) elif s_duration[-1:] == 's': self.maxDuration = int(s_duration[:-1]) * 1000 except Exception, e: raise MySyntaxError( 'Invalid rule: maxDuration must be expressed in milliseconds (ms) or seconds (s), raised: %s' % str(e)) return
class CheckRule: """ a simple checker object for assertion management on response """ KEYWORDS = [ 'matches', 'notmatches', 'contains', 'notcontains', 'equals', 'notequals', 'exists', 'macro', 'eval', 'maxduration' ] def __init__(self, rule, deferred=False): # all keys must be lower case self.ruleDefinition = dict( (k.lower(), v) for k, v in dict(rule).iteritems()) # Only one keyword is authorized intersect = set(self.KEYWORDS).intersection( set(self.ruleDefinition.keys())) if len(intersect) > 1: logger.error( 'We cannot have more than one keywords "%s" in the same rule among "%s"' % (intersect, self.KEYWORDS)) raise MySyntaxError( 'We cannot have than one keywords "%s" in the same rule among"%s"' % (intersect, self.KEYWORDS)) if len(intersect) == 0: logger.error( 'Invalid rule: missing mandatory comparison keywords. One of "%s"' % self.KEYWORDS) raise MySyntaxError( 'Invalid rule: missing mandatory comparison keywords. One of "%s"' % self.KEYWORDS) # the only one keyword self.ruleType = list(intersect)[0] # For Async assertion self.deferred = 'deferred' in self.ruleDefinition or deferred # ignoreCase for Xpath & Regexp self.ignoreCase = 'ignorecase' in self.ruleDefinition and str( self.ruleDefinition['ignorecase']).lower() in ('y', 'yes', 'true') # For "not" expression (notequals, notcontains ...) self.positiveCheck = True # if there is a ${VAR} template self.isPlaceholder = False # "equals" rule are special "contains" rule self.equalsRule = False # For Regexp rules self.isCompiledRule = False self.compiledRule = None self.regexpExpression = None # For Xpath rules self.hasXpath = False self.xpathExpression = None self.textQuery = False self.isXpathPlaceholder = False self.compiledXpathRule = None self.isXpathCompiledRule = False # duration rule if 'maxduration' in self.ruleDefinition: try: s_duration = self.ruleDefinition['maxduration'].strip() self.maxDuration = int(s_duration) # this means that we have a string # see if we have the except ValueError: try: if s_duration[-2:] == 'ms': self.maxDuration = int(s_duration[:-2]) elif s_duration[-1:] == 's': self.maxDuration = int(s_duration[:-1]) * 1000 except Exception, e: raise MySyntaxError( 'Invalid rule: maxDuration must be expressed in milliseconds (ms) or seconds (s), raised: %s' % str(e)) return # # quoted string may be forced in both "equals" and ("regexp","contains") keywords # default behavior is: # "equals" : literal_usage=True # "regexp","contains" : literal_usage=False # default behavior is superseded by the usage of the keywords "literal","quote_string","regexp" # # Change: for **equals** keyword, "quoted" is the default # # We force the literal usage (quoted string) in the case of equals self.equalsRule = self.ruleType.lower().find('equals') >= 0 self.literal_usage = True if self.equalsRule else False if len( list( set(self.ruleDefinition) & set(['literal', 'quote_string', 'regex']))) > 1: logger.error( 'Only 1 of [literal, quote_string, regex] is accepted - please review test scenario - assertion %s' % self.ruleDefinition) raise MySyntaxError( 'Only 1 of [literal, quote_string, regex] is accepted - please review test scenario - assertion %s' % self.ruleDefinition) # # This is a special case where you have complex literal values to compare (for instance quoted Http response) # Default is False. *** Activated if "literal: True" or quote_string: True in the Assertion rule *** # if 'literal' in self.ruleDefinition: self.literal_usage = self.ruleDefinition.get('literal') if 'quote_string' in self.ruleDefinition: self.literal_usage = self.ruleDefinition.get('quote_string') if 'regex' in self.ruleDefinition: # # for not literal_usage to False if regex is explictly specified in rule def # for example: - { response_key: toto, equals: 't[a-z]+', regex: True } # self.literal_usage = not self.ruleDefinition.get('regex') # ------------- # macro keyword # ------------------------- self.isMacroRule = False if self.ruleType == 'macro': # self.isMacroRule = True self.macroExpression = self.ruleDefinition['macro'] # We check the macro format if not GlobalPattern.dynFieldPattern.matcher( self.macroExpression).find(): raise SyntaxError( 'The macro "%s" format is incorrect, it is not a macro of the form module.function(parameter)' % self.ruleDefinition['macro']) logger.trace('"macro" rule to evaluate: %s' % (self.ruleDefinition['macro'])) return # ------------- # eval keyword # ------------------------- self.isEvalRule = False self.stringToEvaluate = '' if self.ruleType == 'eval': self.isEvalRule = True self.stringToEvaluate = self.ruleDefinition['eval'] logger.trace('"eval" rule to evaluate: %s' % (self.ruleDefinition['eval'])) return # Identify the response key to compare to self.responseKey=self.ruleDefinition['response_key'] if 'response_key' in self.ruleDefinition else \ self.ruleDefinition['from'] if 'from' in self.ruleDefinition else 'responseText' # we remove any placeholder in the response key m = (GlobalPattern.dynPlaceholderPattern).matcher(self.responseKey) if m.find(): self.responseKey = m.group(1) # ------------- # exists keyword # allows to check that a rule.responseKey exists or not # ------------------------- if self.ruleType == 'exists': return # ----------------- # Xpath rule # ----------------- if 'xpath' in self.ruleDefinition: self.hasXpath = True self.xpathExpression = self.ruleDefinition['xpath'] self.textQuery = self.xpathExpression.find('/text()') >= 0 # To avoid NOT_FOUND in scenario checkResponse self.xpathExpression = self.xpathExpression.replace('/text()', '') self.isXpathPlaceholder = GlobalPattern.dynPlaceholderPattern.matcher( self.xpathExpression).find() if not self.isXpathPlaceholder: self.isXpathCompiledRule = True try: xpathFactory = XPathFactory.newInstance() self.compiledXpathRule = xpathFactory.newXPath().compile( self.xpathExpression) logger.trace('Compiled Xpath %s has id: %s' % (self.xpathExpression, hex(id(self.compiledXpathRule)))) except: logger.error('Unable to compile xpath rule %s' % (self.xpathExpression)) raise MySyntaxError('Unable to compile xpath rule %s' % (self.xpathExpression)) # positive or not ? self.positiveCheck = not self.ruleType.find('not') >= 0 # In case of "equals", we may have "space" characters ... so we don't strip self.regexpExpression = self.ruleDefinition[ self.ruleType] if self.equalsRule else self.ruleDefinition[ self.ruleType].strip() self.isPlaceholder = (GlobalPattern.dynPlaceholderPattern).matcher( self.ruleDefinition[self.ruleType]).find() # --------------- # JSON rule # ---------------- self.jsonRule = self.ruleDefinition.get('json', False) self.jsonStrict = self.ruleDefinition.get('strict', False) if self.jsonRule: logger.trace('JSON Rule declared') # no compilation # force literal usage self.literal_usage = True return # --------------- # regexp rule # optimization : compile rule if there is no placeholder # ---------------- if not self.isPlaceholder: self.isCompiledRule = True logger.trace( '[CheckRule][No placeholder=>compiling rule][equals=%s][literal=%s][value=%s][positiveCheck=%s]' % (self.equalsRule, self.literal_usage, self.regexpExpression, str(self.positiveCheck))) tmp_regexpExpression = Pattern.quote(str( self.regexpExpression)) if self.literal_usage else str( self.regexpExpression) if self.equalsRule: tmp_regexpExpression = '^%s$' % (tmp_regexpExpression) logger.trace( '[CheckRule][equals=%s][literal=%s][tmp_regexp=%s]' % (self.equalsRule, self.literal_usage, tmp_regexpExpression)) self.compiledRule = Pattern.compile( tmp_regexpExpression, Pattern.CASE_INSENSITIVE | Pattern.DOTALL) if self.ignoreCase else Pattern.compile( tmp_regexpExpression, Pattern.DOTALL)