Added support for meta variable JSON files

Syntribos now allows the user to specify variables in their request
templates by reading from a meta.json file. This is part 1 of 3 of
the full effort, dealing primarily with the template parser itself.

Change-Id: Id41d331f595cd3bc32f085ef49cb5d1b16779a5c
This commit is contained in:
Michael Dong 2016-12-13 11:51:25 -06:00
parent b1d47899d9
commit c5a4dd083d
15 changed files with 523 additions and 225 deletions

View File

@ -865,6 +865,50 @@ follows:
The ID provided will remain static for every test.
Meta Variable File
~~~~~~~~~~~~~~~~~~
Syntribos allows for templates to read in variables from a user-specified
meta variable file. These files contain JSON objects that define variables
to be used in one or more request templates.
The file must be named `meta.json` and must be placed in the same
directory as the template files that reference it. Meta variable files take
the form:
::
{
"user_password": {
"val": 1234
},
"user_name": {
"type": config,
"val": "user.username"
},
"user_token": {
"type": "function",
"val": "syntribos.extensions.identity:get_scoped_token_v3",
"args": ["user"]
}
}
To reference a meta variable from a request template, reference the variable
name surrounded by `|` (pipe). An example request template with meta
variables is as follows:
::
POST /user HTTP/1.1
X-Auth-Token: |user_token|
Accept: */*
Content-type: application/json
{
"user": {
"username": "|user_name|",
"password": "|user_password|"
}
}
Running a specific test
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -126,11 +126,6 @@ making HTTP requests.
:undoc-members:
:show-inheritance:
.. automodule:: syntribos.clients.http.models
:members:
:undoc-members:
:show-inheritance:
.. automodule:: syntribos.clients.http.parser
:members:
:undoc-members:

View File

@ -145,6 +145,50 @@ follows:
The ID provided will remain static for every test.
Meta Variable File
~~~~~~~~~~~~~~~~~~
Syntribos allows for templates to read in variables from a user-specified
meta variable file. These files contain JSON objects that define variables
to be used in one or more request templates.
The file must be named `meta.json` and must be placed in the same
directory as the template files that reference it. Meta variable files take
the form:
::
{
"user_password": {
"val": 1234
},
"user_name": {
"type": config,
"val": "user.username"
},
"user_token": {
"type": "function",
"val": "syntribos.extensions.identity:get_scoped_token_v3",
"args": ["user"]
}
}
To reference a meta variable from a request template, reference the variable
name surrounded by `|` (pipe). An example request template with meta
variables is as follows:
::
POST /user HTTP/1.1
X-Auth-Token: |user_token|
Accept: */*
Content-type: application/json
{
"user": {
"username": "|user_name|",
"password": "|user_password|"
}
}
Running a specific test
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -13,4 +13,5 @@
# limitations under the License.
# flake8: noqa
from syntribos.clients.http.parser import RequestCreator as parser
from syntribos.clients.http.parser import VariableObject
from syntribos.clients.http.client import SynHTTPClient as client

View File

@ -60,7 +60,7 @@ class SynHTTPClient(HTTPClient):
template files, and passed to this method to send the request.
:param request_obj: A RequestObject generated by a parser
:type request_obj: :class:`syntribos.clients.http.models.RequestObject`
:type request_obj: :class:`syntribos.clients.http.parser.RequestObject`
:returns: tuple of (response, signals)
"""
response, signals = self.request(

View File

@ -1,177 +0,0 @@
# Copyright 2015 Rackspace
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: skip-file
import copy
import json
import re
import xml.etree.ElementTree as ElementTree
import six
from six.moves import html_parser
_iterators = {}
class RequestHelperMixin(object):
"""Class that helps with fuzzing requests."""
@classmethod
def _run_iters(cls, data, action_field):
"""Recursively fuzz variables in `data` and its children
:param data: The request data to be modified
:param action_field: The name of the field to be replaced
:returns: object or string with action_field fuzzed
:rtype: `dict` OR `str` OR :class:`ElementTree.Element`
"""
if isinstance(data, dict):
return cls._run_iters_dict(data, action_field)
elif isinstance(data, ElementTree.Element):
return cls._run_iters_xml(data, action_field)
elif isinstance(data, six.string_types):
data = data.replace(action_field, "")
return cls._replace_iter(data)
else:
return data
@classmethod
def _run_iters_dict(cls, dic, action_field=""):
"""Run fuzz iterators for a dict type."""
for key, val in dic.items():
dic[key] = val = cls._replace_iter(val)
if isinstance(key, six.string_types):
new_key = cls._replace_iter(key).replace(action_field, "")
if new_key != key:
del dic[key]
dic[new_key] = val
if isinstance(val, dict):
cls._run_iters_dict(val, action_field)
elif isinstance(val, list):
cls._run_iters_list(val, action_field)
return dic
@classmethod
def _run_iters_list(cls, val, action_field=""):
"""Run fuzz iterators for a list type."""
for i, v in enumerate(val):
if isinstance(v, six.string_types):
val[i] = v = cls._replace_iter(v).replace(action_field, "")
elif isinstance(v, dict):
val[i] = cls._run_iters_dict(v, action_field)
elif isinstance(v, list):
cls._run_iters_list(v, action_field)
@classmethod
def _run_iters_xml(cls, ele, action_field=""):
"""Run fuzz iterators for an XML element type."""
if isinstance(ele.text, six.string_types):
ele.text = cls._replace_iter(ele.text).replace(action_field, "")
cls._run_iters_dict(ele.attrib, action_field)
for i, v in enumerate(list(ele)):
ele[i] = cls._run_iters_xml(v, action_field)
return ele
@staticmethod
def _string_data(data):
"""Replace various objects types with string representations."""
if isinstance(data, dict):
return json.dumps(data)
elif isinstance(data, ElementTree.Element):
str_data = ElementTree.tostring(data)
# No way to stop tostring from HTML escaping even if we wanted
h = html_parser.HTMLParser()
return h.unescape(str_data.decode())
else:
return data
@staticmethod
def _replace_iter(string):
"""Fuzz a string."""
if not isinstance(string, six.string_types):
return string
for k, v in _iterators.items():
if k in string:
string = string.replace(k, six.next(v))
return string
@staticmethod
def _remove_braces(string):
"""Remove braces from strings (in request templates)."""
return re.sub(r"{([^}]*)}", "\\1", string)
@staticmethod
def _remove_attr_names(string):
"""removes identifiers from string substitution
If we are fuzzing example.com/{userid:123}, this method removes the
identifier name so that the client only sees example.com/{123} when
it sends the request
"""
return re.sub(r"{[\w]+:", "{", string)
def prepare_request(self):
"""Prepare a request for sending off
It should be noted this function does not make a request copy,
destroying iterators in request. A copy should be made if making
multiple requests.
"""
self.data = self._run_iters(self.data, self.action_field)
self.headers = self._run_iters(self.headers, self.action_field)
self.params = self._run_iters(self.params, self.action_field)
self.data = self._string_data(self.data)
self.url = self._run_iters(self.url, self.action_field)
self.url = self._remove_braces(self._remove_attr_names(self.url))
def get_prepared_copy(self):
"""Create a copy of `self`, and prepare it for use by a fuzzer
:returns: Copy of request object that has been prepared for sending
:rtype: :class:`RequestHelperMixin`
"""
local_copy = copy.deepcopy(self)
local_copy.prepare_request()
return local_copy
def get_copy(self):
return copy.deepcopy(self)
class RequestObject(RequestHelperMixin):
"""An object that holds information about an HTTP request.
:ivar str method: Request method
:ivar str url: URL to request
:ivar dict action_field: Action Fields
:ivar dict headers: Dictionary of headers in name:value format
:ivar dict params: Dictionary of params in name:value format
:ivar data: Data to send as part of request body
:ivar bool sanitize: Boolean variable used to filter secrets
"""
def __init__(self,
method,
url,
action_field=None,
headers=None,
params=None,
data=None,
sanitize=False):
self.method = method
self.url = url
self.action_field = action_field
self.headers = headers
self.params = params
self.data = data
self.sanitize = sanitize

View File

@ -11,36 +11,50 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ast
import copy
import importlib
import json
import logging
import re
import string
import sys
import types
import uuid
import xml.etree.ElementTree as ElementTree
from oslo_config import cfg
import six
from six.moves import html_parser
from six.moves.urllib import parse as urlparse
from syntribos.clients.http.models import _iterators
from syntribos.clients.http.models import RequestObject
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
_iterators = {}
_string_var_objs = {}
class RequestCreator(object):
ACTION_FIELD = "ACTION_FIELD:"
EXTERNAL = r"CALL_EXTERNAL\|([^:]+?):([^:]+?):([^|]+?)\|"
request_model_type = RequestObject
METAVAR = r"(\|[^\|]*\|)"
FUNC_WITH_ARGS = r"([^:]+):([^:]+):(\[.+\])"
FUNC_NO_ARGS = r"([^:]+):([^:]+)"
@classmethod
def create_request(cls, string, endpoint):
def create_request(cls, string, endpoint, meta_vars=None):
"""Parse the HTTP request template into its components
:param str string: HTTP request template
:param str endpoint: URL of the target to be tested
:param dict meta_vars: default None, dict parsed from meta.json
:rtype: :class:`syntribos.clients.http.models.RequestObject`
:rtype: :class:`syntribos.clients.http.parser.RequestObject`
:returns: RequestObject with method, url, params, etc. for use by
runner
"""
if meta_vars:
cls.meta_vars = meta_vars
string = cls.call_external_functions(string)
action_field = str(uuid.uuid4()).replace("-", "")
string = string.replace(cls.ACTION_FIELD, action_field)
@ -53,10 +67,129 @@ class RequestCreator(object):
method, url, params, _ = cls._parse_url_line(lines[0], endpoint)
headers = cls._parse_headers(lines[1:index])
data = cls._parse_data(lines[index + 1:])
return cls.request_model_type(
return RequestObject(
method=method, url=url, headers=headers, params=params, data=data,
action_field=action_field)
@classmethod
def _create_var_obj(cls, var):
"""Given the name of a variable, creates VariableObject
:param str var: name of the variable in meta.json
:rtype: :class:`syntribos.clients.http.parser.VariableObject`
:returns: VariableObject holding the attributes defined in the JSON
object read in from meta.json
"""
if var not in cls.meta_vars:
print("Expected to find {0} in meta.json, but didn't. "
"Check your templates".format(var))
return
var_dict = cls.meta_vars[var]
if "type" in var_dict:
var_dict["var_type"] = var_dict.pop("type")
var_obj = VariableObject(var, **var_dict)
return var_obj
@classmethod
def replace_one_variable(cls, var_obj):
"""Evaluate a VariableObject according to its type
A meta variable's type is optional. If a type is given, the parser will
interpret the variable in one of 3 ways according to its type, and
returns that value.
* Type config: The parser will attempt to read the config value
specified by the "val" attribute and returns that value.
* Type function: The parser will call the function named in the "val"
attribute with arguments given in the "args" attribute, and returns
the value from calling the function. This value is cached, and
will be returned on subsequent calls.
* Type generator: works the same way as the function type, but its
results are not cached and the function will be called every time.
Otherwise, the parser will interpret the variable as a static variable,
and will return whatever is in the "val" attribute.
:param var_obj: A :class:`syntribos.clients.http.parser.VariableObject`
:returns: The evaluated value according to its meta variable type
"""
if var_obj.var_type == 'config':
try:
return reduce(getattr, var_obj.val.split("."), CONF)
except AttributeError:
print("Meta json file contains reference to the config option "
"{0}, which does not appear to exist.".format(
var_obj.val))
elif var_obj.var_type == 'function':
if var_obj.function_return_value:
return var_obj.function_return_value
if not var_obj.val:
print("The type of variable {0} is function, but there is no "
"reference to the function.")
return
var_obj.function_return_value = cls.call_one_external_function(
var_obj.val, var_obj.args)
return var_obj.function_return_value
elif var_obj.var_type == 'generator':
if not var_obj.val:
print("The type of variable {0} is generator, but there is no "
"reference to the function.")
return
return cls.call_one_external_function(var_obj.val, var_obj.args)
else:
return var_obj.val
@classmethod
def _replace_dict_variables(cls, dic):
"""Recursively evaluates all meta variables in a given dict."""
for (key, value) in six.iteritems(dic):
# Keys dont get fuzzed, so can handle them here
if re.search(cls.METAVAR, key):
key = key.strip("|%s" % string.whitespace)
key_obj = cls._create_var_obj(key)
key = cls.replace_one_variable(key_obj)
# Vals are fuzzed so they need to be passed to datagen as an object
if not isinstance(value, dict):
if re.search(cls.METAVAR, value):
value = value.strip("|%s" % string.whitespace)
val_obj = cls._create_var_obj(value)
dic[key] = val_obj
else:
cls._replace_dict_variables(value)
return dic
@classmethod
def _replace_str_variables(cls, string):
"""Replaces all meta variable references in the string
For every meta variable reference found in the string, it generates
a VariableObject. It then associates each VariableObject with a uuid,
as a key value pair, which is storedin the global dict variable
`_str_var_obs`. It then replaces all meta variable references in the
string with the uuid key to the VariableObject
:param str string: String to be evaluated
:returns: string with all metavariable references replaced
"""
while True:
match = re.search(cls.METAVAR, string)
if not match:
break
obj_ref_uuid = str(uuid.uuid4()).replace("-", "")
var_name = match.group(1).strip("|")
var_obj = cls._create_var_obj(var_name)
_string_var_objs[obj_ref_uuid] = var_obj
string = re.sub(cls.METAVAR, obj_ref_uuid, string, count=1)
return string
@classmethod
def _parse_url_line(cls, line, endpoint):
"""Split first line of an HTTP request into its components
@ -69,7 +202,6 @@ class RequestCreator(object):
"""
valid_methods = ["GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE",
"TRACE", "CONNECT", "PATCH"]
valid_versions = ["HTTP/1.1", "HTTP/1.0", "HTTP/0.9"]
params = {}
method, url, version = line.split()
@ -87,10 +219,8 @@ class RequestCreator(object):
if method not in valid_methods:
raise ValueError("Invalid HTTP method: {0}".format(method))
if version not in valid_versions:
raise ValueError("Invalid HTTP version: {0}".format(version))
return method, url, params, version
return (method, cls._replace_str_variables(url),
cls._replace_dict_variables(params), version)
@classmethod
def _parse_headers(cls, lines):
@ -105,7 +235,7 @@ class RequestCreator(object):
for line in lines:
key, value = line.split(":", 1)
headers[key] = value.strip()
return headers
return cls._replace_dict_variables(headers)
@classmethod
def _parse_data(cls, lines):
@ -124,6 +254,11 @@ class RequestCreator(object):
# TODO(cneill): Make this less hacky
if isinstance(data, list):
data = json.dumps(data)
if isinstance(data, dict):
return cls._replace_dict_variables(data)
else:
return cls._replace_str_variables(data)
except Exception:
try:
data = ElementTree.fromstring(data)
@ -163,3 +298,241 @@ class RequestCreator(object):
else:
string = re.sub(cls.EXTERNAL, str(val), string, count=1)
return string
@classmethod
def call_one_external_function(cls, string, args):
"""Calls one function read in from templates and returns the result."""
if not isinstance(string, six.string_types):
return string
match = re.search(cls.FUNC_NO_ARGS, string)
func_string_has_args = False
if not match:
match = re.search(cls.FUNC_WITH_ARGS, string)
func_string_has_args = True
if not match:
print("The reference to the function {0} failed to parse "
"correctly".format(string))
return
dot_path = match.group(1)
func_name = match.group(2)
mod = importlib.import_module(dot_path)
func = getattr(mod, func_name)
if func_string_has_args and not args:
arg_list = match.group(3)
args = json.loads(arg_list)
else:
args = ast.literal_eval(args)
val = func(*args)
if isinstance(val, types.GeneratorType):
local_uuid = str(uuid.uuid4()).replace("-", "")
string = local_uuid
_iterators[local_uuid] = val
else:
string = str(val)
return string
class VariableObject(object):
VAR_TYPES = ["function", "generator", "config"]
FUZZ_TYPES = ["int", "str", "uuid"]
def __init__(self, name, var_type="", args=[], val="", fuzz=True,
fuzz_type="", min_length=0, max_length=sys.maxsize, **kwargs):
if var_type and var_type.lower() not in self.VAR_TYPES:
print("The variable {0} has a type of {1} which syntribos does not"
" recognize".format(name, var_type))
self.name = name
self.var_type = var_type.lower()
self.val = val
self.args = args
self.fuzz_type = fuzz_type
self.fuzz = fuzz
self.min_length = min_length
self.max_length = max_length
self.function_return_value = None
def __repr__(self):
return str(vars(self))
class RequestHelperMixin(object):
"""Class that helps with fuzzing requests."""
def __init__(self):
self.data = ""
self.headers = ""
self.params = ""
self.data = ""
self.url = ""
self.url = ""
@classmethod
def _run_iters(cls, data, action_field):
"""Recursively fuzz variables in `data` and its children
:param data: The request data to be modified
:param action_field: The name of the field to be replaced
:returns: object or string with action_field fuzzed
:rtype: `dict` OR `str` OR :class:`ElementTree.Element`
"""
if isinstance(data, dict):
return cls._run_iters_dict(data, action_field)
elif isinstance(data, ElementTree.Element):
return cls._run_iters_xml(data, action_field)
elif isinstance(data, VariableObject):
return RequestCreator.replace_one_variable(data)
elif isinstance(data, six.string_types):
data = data.replace(action_field, "")
return cls._replace_iter(data)
else:
return data
@classmethod
def _run_iters_dict(cls, dic, action_field=""):
"""Run fuzz iterators for a dict type."""
for key, val in six.iteritems(dic):
dic[key] = val = cls._replace_iter(val)
if isinstance(key, six.string_types):
new_key = cls._replace_iter(key).replace(action_field, "")
if new_key != key:
del dic[key]
dic[new_key] = val
if isinstance(val, VariableObject):
if key in dic:
dic[key] = RequestCreator.replace_one_variable(val)
elif new_key in dic:
dic[new_key] = RequestCreator.replace_one_variable(val)
if isinstance(val, dict):
cls._run_iters_dict(val, action_field)
elif isinstance(val, list):
cls._run_iters_list(val, action_field)
return dic
@classmethod
def _run_iters_list(cls, val, action_field=""):
"""Run fuzz iterators for a list type."""
for i, v in enumerate(val):
if isinstance(v, six.string_types):
val[i] = v = cls._replace_iter(v).replace(action_field, "")
if isinstance(v, VariableObject):
val[i] = v = RequestCreator.replace_one_variable(v)
elif isinstance(v, dict):
val[i] = cls._run_iters_dict(v, action_field)
elif isinstance(v, list):
cls._run_iters_list(v, action_field)
@classmethod
def _run_iters_xml(cls, ele, action_field=""):
"""Run fuzz iterators for an XML element type."""
if isinstance(ele.text, six.string_types):
ele.text = cls._replace_iter(ele.text).replace(action_field, "")
cls._run_iters_dict(ele.attrib, action_field)
for i, v in enumerate(list(ele)):
ele[i] = cls._run_iters_xml(v, action_field)
return ele
@staticmethod
def _string_data(data):
"""Replace various objects types with string representations."""
if isinstance(data, dict):
return json.dumps(data)
elif isinstance(data, ElementTree.Element):
str_data = ElementTree.tostring(data)
# No way to stop tostring from HTML escaping even if we wanted
h = html_parser.HTMLParser()
return h.unescape(str_data.decode())
else:
return data
@staticmethod
def _replace_iter(string):
"""Fuzz a string."""
if not isinstance(string, six.string_types):
return string
for k, v in list(six.iteritems(_iterators)):
if k in string:
string = string.replace(k, six.next(v))
for k, v in _string_var_objs.items():
if k in string:
str_val = str(RequestCreator.replace_one_variable(v))
string = string.replace(k, str_val)
return string
@staticmethod
def _remove_braces(string):
"""Remove braces from strings (in request templates)."""
return re.sub(r"{([^}]*)}", "\\1", string)
@staticmethod
def _remove_attr_names(string):
"""removes identifiers from string substitution
If we are fuzzing example.com/{userid:123}, this method removes the
identifier name so that the client only sees example.com/{123} when
it sends the request
"""
return re.sub(r"{[\w]+:", "{", string)
def prepare_request(self):
"""Prepare a request for sending off
It should be noted this function does not make a request copy,
destroying iterators in request. A copy should be made if making
multiple requests.
"""
self.data = self._run_iters(self.data, self.action_field)
self.headers = self._run_iters(self.headers, self.action_field)
self.params = self._run_iters(self.params, self.action_field)
self.data = self._string_data(self.data)
self.url = self._run_iters(self.url, self.action_field)
self.url = self._remove_braces(self._remove_attr_names(self.url))
def get_prepared_copy(self):
"""Create a copy of `self`, and prepare it for use by a fuzzer
:returns: Copy of request object that has been prepared for sending
:rtype: :class:`RequestHelperMixin`
"""
local_copy = copy.deepcopy(self)
local_copy.prepare_request()
return local_copy
def get_copy(self):
return copy.deepcopy(self)
class RequestObject(RequestHelperMixin):
"""An object that holds information about an HTTP request.
:ivar str method: Request method
:ivar str url: URL to request
:ivar dict action_field: Action Fields
:ivar dict headers: Dictionary of headers in name:value format
:ivar dict params: Dictionary of params in name:value format
:ivar data: Data to send as part of request body
:ivar bool sanitize: Boolean variable used to filter secrets
"""
def __init__(self,
method,
url,
action_field=None,
headers=None,
params=None,
data=None,
sanitize=False):
self.method = method
self.url = url
self.action_field = action_field
self.headers = headers
self.params = params
self.data = data
self.sanitize = sanitize

View File

@ -11,11 +11,13 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
import os
import pkgutil
import sys
import time
import traceback
import unittest
from oslo_config import cfg
@ -222,7 +224,21 @@ class Runner(object):
exit(1)
print("\nPress Ctrl-C to pause or exit...\n")
# TODO(mdong): Make this handle inheritence and all that. For now, just
# pass the first meta file it sees to the parser. Also, find a better
# way to pass meta_vars
meta_vars = None
templates_dir = list(templates_dir)
for file_path, req_str in templates_dir:
if "meta.json" not in file_path:
continue
else:
meta_vars = json.loads(req_str)
break
for file_path, req_str in templates_dir:
if "meta.json" in file_path:
continue
LOG = cls.get_logger(file_path)
CONF.log_opt_values(LOG, logging.DEBUG)
if not file_path.endswith(".template"):
@ -243,9 +259,11 @@ class Runner(object):
print(syntribos.SEP)
if CONF.sub_command.name == "run":
cls.run_given_tests(list_of_tests, file_path, req_str)
cls.run_given_tests(list_of_tests, file_path,
req_str, meta_vars)
elif CONF.sub_command.name == "dry_run":
cls.dry_run(list_of_tests, file_path, req_str, dry_run_output)
cls.dry_run(list_of_tests, file_path,
req_str, dry_run_output, meta_vars)
if CONF.sub_command.name == "run":
result.print_result(cls.start_time)
@ -254,7 +272,8 @@ class Runner(object):
cls.dry_run_report(dry_run_output)
@classmethod
def dry_run(cls, list_of_tests, file_path, req_str, output):
def dry_run(cls, list_of_tests, file_path, req_str, output,
meta_vars=None):
"""Runs debug test to check all steps leading up to executing a test
This method does not run any checks, but does parse the template files
@ -272,10 +291,10 @@ class Runner(object):
for _, test_class in list_of_tests:
try:
print("\nParsing template file...")
test_class.create_init_request(file_path, req_str)
test_class.create_init_request(file_path, req_str, meta_vars)
except Exception as e:
print("Error in parsing template:\n \t{0}: {1}\n".format(
type(e).__name__, e))
print("Error in parsing template:\n \t{0}\n".format(
traceback.format_exc()))
LOG.exception("Error in parsing template:")
output["failures"].append({
"file": file_path,
@ -304,7 +323,8 @@ class Runner(object):
print(syntribos.SEP)
@classmethod
def run_given_tests(cls, list_of_tests, file_path, req_str):
def run_given_tests(cls, list_of_tests, file_path, req_str,
meta_vars=None):
"""Loads all the templates and runs all the given tests
This method calls run_test method to run each of the tests one
@ -335,7 +355,13 @@ class Runner(object):
else:
result_string = result_string.ljust(60)
LOG.debug(log_string)
test_class.send_init_request(file_path, req_str)
try:
test_class.send_init_request(file_path, req_str, meta_vars)
except Exception:
print("Error in parsing template:\n \t{0}\n".format(
traceback.format_exc()))
LOG.exception("Error in parsing template:")
break
test_cases = list(
test_class.get_test_cases(file_path, req_str))
if len(test_cases) > 0:

View File

@ -46,8 +46,9 @@ class AuthTestCase(base.BaseTestCase):
data=cls.request.data)
@classmethod
def send_init_request(cls, filename, file_content):
super(AuthTestCase, cls).send_init_request(filename, file_content)
def send_init_request(cls, filename, file_content, meta_vars):
super(AuthTestCase, cls).send_init_request(filename,
file_content, meta_vars)
cls.request = cls.init_req.get_prepared_copy()
@classmethod

View File

@ -122,7 +122,7 @@ class BaseTestCase(unittest.TestCase):
yield cls
@classmethod
def create_init_request(cls, filename, file_content):
def create_init_request(cls, filename, file_content, meta_vars):
"""Parses template and creates init request object
This method does not send the initial request, instead, it only creates
@ -132,13 +132,13 @@ class BaseTestCase(unittest.TestCase):
:param str file_content: content of template file as string
"""
request_obj = parser.create_request(
file_content, CONF.syntribos.endpoint)
file_content, CONF.syntribos.endpoint, meta_vars)
cls.init_req = request_obj
cls.init_resp = None
cls.init_signals = None
@classmethod
def send_init_request(cls, filename, file_content):
def send_init_request(cls, filename, file_content, meta_vars):
"""Parses template, creates init request object, and sends init request
This method sends the initial request, which is the request created
@ -149,7 +149,7 @@ class BaseTestCase(unittest.TestCase):
:param str file_content: content of template file as string
"""
cls.init_req = parser.create_request(
file_content, CONF.syntribos.endpoint)
file_content, CONF.syntribos.endpoint, meta_vars)
prepared_copy = cls.init_req.get_prepared_copy()
cls.init_resp, cls.init_signals = cls.client.send_request(
@ -212,7 +212,7 @@ class BaseTestCase(unittest.TestCase):
self.test_case()
except Exception as e:
self.errors += e
raise e
raise
if self.failures:
raise AssertionError

View File

@ -57,10 +57,6 @@ class BaseFuzzTestCase(base.BaseTestCase):
"exiting...".format(cls.test_name))
exit(1)
@classmethod
def send_init_request(cls, filename, file_content):
super(BaseFuzzTestCase, cls).send_init_request(filename, file_content)
@classmethod
def setUpClass(cls):
"""being used as a setup test not."""

View File

@ -25,7 +25,7 @@ def fuzz_request(req, strings, fuzz_type, name_prefix):
creates a RequestObject from the parameters of the model.
:param req: The RequestObject to be fuzzed
:type req: :class:`syntribos.clients.http.models.RequestObject`
:type req: :class:`syntribos.clients.http.parser.RequestObject`
:param list strings: List of strings to fuzz with
:param str fuzz_type: What attribute of the RequestObject to fuzz
:param name_prefix: (Used for ImpactedParameter)

View File

@ -17,7 +17,7 @@ from xml.etree import ElementTree
import six
import testtools
from syntribos.clients.http.models import RequestObject
from syntribos.clients.http.parser import RequestObject
import syntribos.tests.fuzz.datagen as fuzz_datagen
action_field = "ACTION_FIELD:"

View File

@ -18,12 +18,12 @@ import xml.etree.ElementTree as ElementTree
import six
import testtools
import syntribos.clients.http.models as mod
from syntribos.clients.http.parser import _iterators
from syntribos.clients.http.parser import RequestHelperMixin as rhm
from syntribos.clients.http.parser import RequestObject as ro
endpoint = "http://test.com"
action_field = "ACTION_FIELD:"
rhm = mod.RequestHelperMixin
ro = mod.RequestObject
def get_fake_generator():
@ -139,7 +139,7 @@ class HTTPModelsUnittest(testtools.TestCase):
def test_run_iters_global_iterators(self):
"""Tests _replace_iter by modifying _iterators global object."""
u = str(uuid.uuid4()).replace("-", "")
mod._iterators[u] = get_fake_generator()
_iterators[u] = get_fake_generator()
_str = "/v1/{0}/test".format(u)
res = rhm._run_iters(_str, action_field)
self.assertEqual("/v1/{0}/test".format(0), res)

View File

@ -38,11 +38,6 @@ class HTTPParserUnittest(testtools.TestCase):
self.assertEqual({"var": "val", "var2": "val2"}, params)
self.assertEqual("HTTP/1.1", version)
def test_url_line_parser_invalid_version(self):
"""Tests parsing an invalid HTTP version."""
line = "GET /path?var=val&var2=val2 HTTP"
self.assertRaises(ValueError, parser._parse_url_line, line, endpoint)
def test_url_line_parser_invalid_method(self):
"""Tests parsing an invalid HTTP method."""
line = "DERP /path?var=val&var2=val2 HTTP/1.1"