Skip to content

Commit

Permalink
Merge pull request #561 from HttpRunner/fix_match_func_var
Browse files Browse the repository at this point in the history
2.1.2
  • Loading branch information
debugtalk committed Apr 19, 2019
2 parents 9fd69d8 + 0f1925e commit 5e5e7b6
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 90 deletions.
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ python:
- 3.4
- 3.5
- 3.6
- 3.7-dev
matrix:
include:
- python: 3.7
dist: xenial
sudo: true
install:
- pip install pipenv --upgrade-strategy=only-if-needed
- pipenv install --dev --skip-lock
Expand Down
15 changes: 15 additions & 0 deletions HISTORY.md → CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Release History

## 2.1.2 (2019-04-17)

**Features**

- support new variable notation ${var}
- use \$\$ to escape \$ notation
- add Python 3.7 for travis CI

**Bugfixes**

- match duplicate variable/function in single raw string
- escape '{' and '}' notation in raw string
- print_info: TypeError when value is None
- display api name when running api as testcase

## 2.1.1 (2019-04-11)

**Features**
Expand Down
2 changes: 1 addition & 1 deletion httprunner/__about__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__title__ = 'HttpRunner'
__description__ = 'One-stop solution for HTTP(S) testing.'
__url__ = 'https://github.com/HttpRunner/HttpRunner'
__version__ = '2.1.1'
__version__ = '2.1.2'
__author__ = 'debugtalk'
__author_email__ = 'mail@debugtalk.com'
__license__ = 'Apache-2.0'
Expand Down
154 changes: 103 additions & 51 deletions httprunner/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from httprunner import exceptions, utils, validator
from httprunner.compat import basestring, builtin_str, numeric_types, str

# TODO: change variable notation from $var to {{var}}
# $var_1
variable_regex_compile = re.compile(r"\$(\w+)")
# ${func1($var_1, $var_3)}
# use $$ to escape $ notation
dolloar_regex_compile = re.compile(r"\$\$")
# variable notation, e.g. ${var} or $var
variable_regex_compile = re.compile(r"\$\{(\w+)\}|\$(\w+)")
# function notation, e.g. ${func1($var_1, $var_3)}
function_regex_compile = re.compile(r"\$\{(\w+)\(([\$\w\.\-/\s=,]*)\)\}")


Expand All @@ -30,18 +31,32 @@ def parse_string_value(str_value):
return str_value


def is_variable_exist(content):
def is_var_or_func_exist(content):
""" check if variable or function exist
"""
if not isinstance(content, basestring):
return False

return True if variable_regex_compile.search(content) else False
try:
match_start_position = content.index("$", 0)
except ValueError:
return False

while match_start_position < len(content):
dollar_match = dolloar_regex_compile.match(content, match_start_position)
if dollar_match:
match_start_position = dollar_match.end()
continue

def is_function_exist(content):
if not isinstance(content, basestring):
return False
func_match = function_regex_compile.match(content, match_start_position)
if func_match:
return True

var_match = variable_regex_compile.match(content, match_start_position)
if var_match:
return True

return True if function_regex_compile.search(content) else False
return False


def regex_findall_variables(content):
Expand All @@ -68,7 +83,12 @@ def regex_findall_variables(content):
"""
try:
return variable_regex_compile.findall(content)
vars_list = []
for var_tuple in variable_regex_compile.findall(content):
vars_list.append(
var_tuple[0] or var_tuple[1]
)
return vars_list
except TypeError:
return []

Expand Down Expand Up @@ -436,46 +456,73 @@ def __parse(self, raw_string):
args: ["${func2($a, $b)}", "$c"]
"""
self._string = raw_string
args_mapping = {}

# Notice: functions must be handled before variables
# search function like ${func($a, $b)}
func_match_list = regex_findall_functions(self._string)
match_start_position = 0
for func_match in func_match_list:
func_str = "${%s(%s)}" % (func_match[0], func_match[1])
match_start_position = raw_string.index(func_str, match_start_position)
self._string = self._string.replace(func_str, "{}", 1)
function_meta = parse_function_params(func_match[1])
function_meta = {
"func_name": func_match[0]
}
function_meta.update(parse_function_params(func_match[1]))
lazy_func = LazyFunction(
function_meta,
self.functions_mapping,
self.check_variables_set
)
args_mapping[match_start_position] = lazy_func

# search variable like $var
var_match_list = regex_findall_variables(self._string)
match_start_position = 0
for var_name in var_match_list:
# check if any variable undefined in check_variables_set
if var_name not in self.check_variables_set:
raise exceptions.VariableNotFound(var_name)
self._args = []

def escape_braces(origin_string):
return origin_string.replace("{", "{{").replace("}", "}}")

var = "${}".format(var_name)
match_start_position = raw_string.index(var, match_start_position)
# TODO: escape '{' and '}'
# self._string = self._string.replace("{", "{{")
# self._string = self._string.replace("}", "}}")
self._string = self._string.replace(var, "{}", 1)
args_mapping[match_start_position] = var_name
try:
match_start_position = raw_string.index("$", 0)
begin_string = raw_string[0:match_start_position]
self._string = escape_braces(begin_string)
except ValueError:
self._string = escape_braces(raw_string)
return

self._args = [args_mapping[key] for key in sorted(args_mapping.keys())]
while match_start_position < len(raw_string):

# Notice: notation priority
# $$ > ${func($a, $b)} > $var

# search $$
dollar_match = dolloar_regex_compile.match(raw_string, match_start_position)
if dollar_match:
match_start_position = dollar_match.end()
self._string += "$"
continue

# search function like ${func($a, $b)}
func_match = function_regex_compile.match(raw_string, match_start_position)
if func_match:
function_meta = parse_function_params(func_match.group(1))
function_meta = {
"func_name": func_match.group(1)
}
function_meta.update(parse_function_params(func_match.group(2)))
lazy_func = LazyFunction(
function_meta,
self.functions_mapping,
self.check_variables_set
)
self._args.append(lazy_func)
match_start_position = func_match.end()
self._string += "{}"
continue

# search variable like ${var} or $var
var_match = variable_regex_compile.match(raw_string, match_start_position)
if var_match:
var_name = var_match.group(1) or var_match.group(2)
# check if any variable undefined in check_variables_set
if var_name not in self.check_variables_set:
raise exceptions.VariableNotFound(var_name)

self._args.append(var_name)
match_start_position = var_match.end()
self._string += "{}"
continue

curr_position = match_start_position
try:
# find next $ location
match_start_position = raw_string.index("$", curr_position+1)
remain_string = raw_string[curr_position:match_start_position]
except ValueError:
remain_string = raw_string[curr_position:]
# break while loop
match_start_position = len(raw_string)

self._string += escape_braces(remain_string)

def __repr__(self):
return "LazyString({})".format(self.raw_string)
Expand Down Expand Up @@ -549,9 +596,11 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None,

elif isinstance(content, basestring):
# content is in string format here
if not (is_variable_exist(content) or is_function_exist(content)):
if not is_var_or_func_exist(content):
# content is neither variable nor function
return content
# replace $$ notation with $ and consider it as normal char.
# e.g. abc => abc, abc$$def => abc$def, abc$$$$def$$h => abc$$def$h
return content.replace("$$", "$")

functions_mapping = functions_mapping or {}
check_variables_set = check_variables_set or set()
Expand Down Expand Up @@ -1205,6 +1254,9 @@ def parse_tests(tests_mapping):
# encapsulate api as a testcase
for api_content in tests_mapping["apis"]:
testcase = {
"config": {
"name": api_content.get("name")
},
"teststeps": [api_content]
}
parsed_testcase = _parse_testcase(testcase, project_mapping)
Expand Down
2 changes: 2 additions & 0 deletions httprunner/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ def print_info(info_mapping):
continue
elif isinstance(value, (dict, list)):
value = json.dumps(value)
elif value is None:
value = "None"

if is_py2:
if isinstance(key, unicode):
Expand Down
6 changes: 4 additions & 2 deletions tests/httpbin/api/get_headers.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

name: headers
name: get headers
base_url: http://httpbin.org
variables:
expected_status_code: 200
request:
url: /headers
method: GET
validate:
- eq: ["status_code", 200]
- eq: ["status_code", $expected_status_code]
- eq: [content.headers.Host, "httpbin.org"]

0 comments on commit 5e5e7b6

Please sign in to comment.