Example #1
0
def parse_configuration(node, base_config=None):
    """ Parse input config to configuration information """
    test_config = base_config
    if not test_config:
        test_config = TestSetConfig()

    node = lowercase_keys(flatten_dictionaries(node))  # Make it usable

    if "testset" in node:
        test_config.testset_name = node['testset']

    # ahead process variable_binds, other key can use it in template
    if not test_config.variable_binds:
        test_config.variable_binds = dict()

    if 'variable_binds' in node:
        value = node['variable_binds']
        test_config.variable_binds.update(flatten_dictionaries(value))

    # set default_base_url to global variable_binds
    if 'default_base_url' in node:
        value = node['default_base_url']
        default_base_url = templated_var(value, test_config.variable_binds)
        test_config.variable_binds['default_base_url'] = default_base_url

    for key, value in node.items():
        if key == 'timeout':
            test_config.timeout = int(value)
        elif key == 'retries':
            test_config.retries = int(value)
        elif key == 'collect_import_result':
            if isinstance(value, str):
                value = True if value.lower() == 'true' else False
            test_config.collect_import_result = value
        elif key == 'variable_binds':
            pass
        elif key == 'request_client':
            test_config.request_client = str(value)
        elif key == 'generators':
            flat = flatten_dictionaries(value)
            gen_map = dict()
            for generator_name, generator_config in flat.items():
                gen = parse_generator(
                    configuration=generator_config,
                    variable_binds={
                        **test_config.variable_binds,
                        'working_directory': test_config.working_directory
                    })
                gen_map[str(generator_name)] = gen
            test_config.generators = gen_map

    if 'data_driven' in node:
        value = node['data_driven']
        generator_name = value.get('generator', None)
        generator_obj = test_config.generators[generator_name]
        test_config.data_driven_generator = generator_obj
        test_config.data_driven_generator_name = generator_name

    return test_config
Example #2
0
    def parse(config):
        """ Create a validator that does an extract from body and applies a comparator,
            Then does comparison vs expected value
            Syntax sample:
              { jsonpath_mini: 'node.child',
                operator: 'eq',
                expected: 'myValue'
              }
        """

        output = ComparatorValidator()
        config = parsing.lowercase_keys(parsing.flatten_dictionaries(config))
        output.config = config

        # Extract functions are called by using defined extractor names
        output.extractor = _get_extractor(config)

        if output.extractor is None:
            raise ValueError(
                "Extract function for comparison is not valid or not found!")

        if 'comparator' not in config:  # Equals comparator if unspecified
            output.comparator_name = 'eq'
        else:
            output.comparator_name = config['comparator'].lower()
        output.comparator = COMPARATORS[output.comparator_name]
        if not output.comparator:
            raise ValueError("Invalid comparator given!")

        try:
            expected = config['expected']
        except KeyError:
            raise ValueError(
                "No expected value found in comparator validator config, one must be!"
            )

        # Expected value can be another extractor query, or a single value, or
        # a templated value

        if isinstance(expected, str) or isinstance(
                expected, (int, long, float, complex)):
            output.expected = expected
        elif isinstance(expected, dict):
            expected = parsing.lowercase_keys(expected)
            template = expected.get('template')
            if template:  # Templated string
                if not isinstance(template, str):
                    raise ValueError(
                        "Can't template a comparator-validator unless template value is a string"
                    )
                output.expected = template
            else:  # Extractor to compare against
                output.expected = _get_extractor(expected)
                if not output.expected:
                    raise ValueError(
                        "Can't supply a non-template, non-extract dictionary to comparator-validator"
                    )

        return output
Example #3
0
    def parse(config):
        output = ExtractTestValidator()
        config = parsing.lowercase_keys(parsing.flatten_dictionaries(config))
        output.config = config
        extractor = _get_extractor(config)
        output.extractor = extractor

        test_name = config['test']
        output.test_name = test_name
        test_fn = VALIDATOR_TESTS[test_name]
        output.test_fn = test_fn
        return output
Example #4
0
def parse_generator(configuration, variable_binds=None):
    """ Parses a configuration built from yaml and returns a generator
        Configuration should be a map
    """

    configuration = lowercase_keys(flatten_dictionaries(configuration))
    gen_type = str(configuration.get('type')).lower()

    if gen_type not in GENERATOR_TYPES:
        raise ValueError(
            'Generator type given {0} is not valid '.format(gen_type))

    # Do the easy parsing, delegate more complex logic to parsing functions
    if gen_type == 'env_variable':
        return factory_env_variable(configuration['variable_name'])()
    elif gen_type == 'env_string':
        return factory_env_string(configuration['string'])()
    elif gen_type == 'number_sequence':
        start = configuration.get('start')
        increment = configuration.get('increment')
        if not start:
            start = 1
        else:
            start = int(start)
        if not increment:
            increment = 1
        else:
            increment = int(increment)
        return factory_generate_ids(start, increment)()
    elif gen_type == 'random_int':
        return generator_random_int32()
    elif gen_type == 'random_text':
        return parse_random_text_generator(configuration)
    elif gen_type in GENERATOR_TYPES:
        return GENERATOR_PARSING[gen_type](configuration, variable_binds)
    else:
        raise Exception("Unknown generator type: {0}".format('gen_type'))
Example #5
0
    def parse_from_dict(cls, node, input_test=None, test_path=None):
        """ Create or modify a test, input_test, using configuration in node, and base_url
        If no input_test is given, creates a new one

        Test_path gives path to test file, used for setting working directory in setting up input bodies

        Uses explicitly specified elements from the test input structure
        to make life *extra* fun, we need to handle list <-- > dict transformations.

        This is to say: list(dict(),dict()) or dict(key,value) -->  dict() for some elements

        Accepted structure must be a single dictionary of key-value pairs for test configuration """

        mytest = input_test
        if not mytest:
            mytest = HttpTest()

        # Clean up for easy parsing
        node = lowercase_keys(flatten_dictionaries(node))

        # Simple table of variable name, coerce function, and optionally special store function
        CONFIG_ELEMENTS = {
            # Simple variables
            u'auth_username': [coerce_string_to_ascii],
            u'auth_password': [coerce_string_to_ascii],
            u'method': [coerce_http_method],  # HTTP METHOD
            u'delay': [lambda x: int(x)],  # Delay before running
            u'group': [coerce_to_string],  # Test group name
            u'name': [coerce_to_string],  # Test name
            u'expected_status': [coerce_list_of_ints],
            u'stop_on_failure': [safe_to_bool],

            # Templated / special handling
            # u'body': [ContentHandler.parse_content]

            # COMPLEX PARSE OPTIONS
            # u'extract_binds':[],  # Context variable-to-extractor output binding
            # u'variable_binds': [],  # Context variable to value binding
            # u'generator_binds': [],  # Context variable to generator output binding
            # u'validators': [],  # Validation functions to run
        }

        def use_config_parser(configobject, configelement, configvalue):
            """ Try to use parser bindings to find an option for parsing and storing config element
                :configobject: Object to store configuration
                :configelement: Configuratione element name
                :configvalue: Value to use to set configuration
                :returns: True if found match for config element, False if didn't
            """

            myparsing = CONFIG_ELEMENTS.get(configelement)
            if myparsing:
                converted = myparsing[0](configvalue)
                setattr(configobject, configelement, converted)
                return True
            return False

        # Copy/convert input elements into appropriate form for a test object
        for configelement, configvalue in node.items():
            if use_config_parser(mytest, configelement, configvalue):
                continue

            # Configure test using configuration elements
            if configelement == 'url':
                if isinstance(configvalue, dict):
                    configvalue = configvalue.get("template")
                mytest.url = coerce_to_string(configvalue)
            elif configelement == 'body':
                if isinstance(configvalue, dict):
                    configvalue = configvalue.get("template")
                mytest.http_body = configvalue
            elif configelement == 'extract_binds':
                # Add a list of extractors, of format:
                # {variable_name: {extractor_type: extractor_config}, ... }
                binds = flatten_dictionaries(configvalue)
                if mytest.extract_binds is None:
                    mytest.extract_binds = dict()

                for variable_name, extractor in binds.items():
                    if not isinstance(extractor, dict) or len(
                            extractor) == 0:
                        raise TypeError(
                            "Extractors must be defined as maps of extractorType:{configs} with 1 entry")
                    if len(extractor) > 1:
                        raise ValueError(
                            "Cannot define multiple extractors for given variable name")

                    # Safe because length can only be 1
                    for extractor_type, extractor_config in extractor.items():
                        mytest.extract_binds[
                            variable_name] = validators.parse_extractor(
                            extractor_type, extractor_config)

            elif configelement == 'validators':
                # Add a list of validators
                if not isinstance(configvalue, list):
                    raise Exception(
                        'Misconfigured validator section, must be a list of validators')
                if mytest.validators is None:
                    mytest.validators = list()

                # create validator and add to list of validators
                for var in configvalue:
                    if not isinstance(var, dict):
                        raise TypeError(
                            "Validators must be defined as validatorType:{configs} ")
                    for validator_type, validator_config in var.items():
                        validator = validators.parse_validator(
                            validator_type, validator_config)
                        mytest.validators.append(validator)

            elif configelement == 'headers':  # HTTP headers to use, flattened to a single string-string dictionary
                configvalue = flatten_dictionaries(configvalue)
                if isinstance(configvalue, dict):
                    mytest.headers = configvalue
                elif isinstance(configvalue, str):
                    try:
                        mytest.headers = json.loads(configvalue)
                    except Exception as e:
                        logger.error(str(e))
                        raise Exception("header must be dict or json str")
                else:
                    raise TypeError(
                        "Illegal header type: headers must be a dictionary or list of dictionary keys")
            elif configelement == 'variable_binds':
                mytest.variable_binds = flatten_dictionaries(configvalue)
            elif configelement == 'generator_binds':
                output = flatten_dictionaries(configvalue)
                output2 = dict()
                for key, value in output.items():
                    output2[str(key)] = str(value)
                mytest.generator_binds = output2

        # For non-GET requests, accept additional response codes indicating success
        # (but only if not expected statuses are not explicitly specified)
        # this is per HTTP spec:
        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5
        if 'expected_status' not in node.keys():
            if mytest.method == 'POST':
                mytest.expected_status = [200, 201, 204]
            elif mytest.method == 'PUT':
                mytest.expected_status = [200, 201, 204]
            elif mytest.method == 'DELETE':
                mytest.expected_status = [200, 202, 204]
            # Fallthrough default is simply [200]
        return mytest
Example #6
0
def parse_testsets(test_structure, test_files=set(), working_directory=None):
    """ Convert a Python data structure read from validated YAML to a set of structured testsets
    The data structure is assumed to be a list of dictionaries, each of which describes:
        - a tests (test structure)
        - a simple test (just a URL, and a minimal test is created)
        - or overall test configuration for this testset
        - an import (load another set of tests into this one, from a separate file)
            - For imports, these are recursive, and will use the parent config if none is present

    Note: test_files is used to track tests that import other tests, to avoid recursive loops

    This returns a list of testsets, corresponding to imported testsets and in-line multi-document sets
    """

    tests_list = list()
    test_config = TestSetConfig()
    testsets = list()

    if working_directory is None:
        working_directory = os.path.abspath(os.getcwd())
    test_config.working_directory = working_directory

    # returns a testconfig and collection of testsets
    assert isinstance(test_structure, list)

    testset = TestSet()

    index = 0
    while index < len(test_structure):  # Iterate through lists of test and configuration elements
        node = test_structure[index]
        if isinstance(node, dict):  # Each config element is a miniature key-value dictionary
            node = lowercase_keys(node)
            for key in node:
                if key == 'include':
                    includefile = node[key]  # include another file as module/modules
                    if includefile[0] != "/":
                        includefile = os.path.join(working_directory,
                                                   includefile)
                    # load include module file and delete its config, add its test/operation to local flow
                    subnodes = read_test_file(includefile)
                    assert isinstance(subnodes, list)
                    i = index + 1
                    for sn in subnodes:
                        keys = sn.keys()
                        assert len(keys) == 1
                        if list(keys)[0] in ['config']:
                            continue
                        test_structure.insert(i, sn)
                        i += 1

                if key == 'import':   # import another testset file
                    if isinstance(node[key], dict):
                        importfile = node[key]['file']
                        input = node[key].get('input')
                        extract = node[key].get('extract')
                    elif isinstance(node[key], str):
                        importfile = node[key]
                        input = None
                        extract = None
                    else:
                        raise Exception("Wrong Import Format: {}".format(node[key]))

                    if importfile[0] != "/":
                        importfile = os.path.join(working_directory,
                                                  importfile)
                    if importfile not in test_files:
                        logger.info("Importing test sets: " + importfile)
                        test_files.add(importfile)
                        import_test_structure = read_test_file(importfile)
                        with CD(os.path.dirname(
                                os.path.realpath(importfile))):
                            try:
                                import_testsets = parse_testsets(
                                    import_test_structure, test_files)
                            except Exception as e:
                                error_info = "Import SubTestSet {} ERROR, msg: {}".format(importfile, str(e))
                                logger.error(error_info)
                                raise Exception(error_info)
                            assert len(import_testsets) == 1
                            import_testsets[0].config.extract = extract
                            testset.subtestsets[importfile] = import_testsets[0]

                            subtestset = TestSet()
                            subtestset.input = input
                            subtestset.extract = extract
                            subtestset.file_path = importfile
                            tests_list.append(subtestset)  # call sub testset is also a test step

                # elif key == 'url':  # Simple test, just a GET to a URL
                #     mytest = HttpTest()
                #     val = node[key]
                #     assert isinstance(val, str)
                #     mytest.url = val
                #     tests_list.append(mytest)
                elif key == 'test':  # Complex test with additional parameters
                    with CD(working_directory):
                        child = node[key]
                        test_type = child.get('test_type', 'http_test')
                        # mytest = HttpTest.parse_from_dict(child)
                        runner_parser_func = get_test_runner_parser(test_type)
                        mytest = runner_parser_func(child)
                        mytest.original_node = child
                        tests_list.append(mytest)
                elif key == 'operation':  # Complex test with additional parameters
                    operation = Operation()
                    operation.config = flatten_dictionaries(node[key])
                    tests_list.append(operation)
                elif key == 'config' or key == 'configuration':
                    test_config = parse_configuration(
                        node[key], base_config=test_config)

                index += 1

    testset.tests = tests_list
    testset.config = test_config
    testset.name = test_config.testset_name
    for t in tests_list:
        t.testset_config = test_config
    testsets.append(testset)
    return testsets