Beispiel #1
0
    def add_constructor(
        self,
        test_case: tc.TestCase,
        constructor: gao.GenericConstructor,
        position: int = -1,
        recursion_depth: int = 0,
        allow_none: bool = True,
    ) -> vr.VariableReference:
        """Adds a constructor statement to a test case at a given position.

        If the position is not given, the constructor will be appended on the end of
        the test case.  A given recursion depth controls how far the factory searches
        for suitable parameter values.

        Args:
            test_case: The test case
            constructor: The constructor to add to the test case
            position: The position where to put the statement in the test case,
                defaults to the end of the test case
            recursion_depth: A recursion limit for the search of parameter values
            allow_none: Whether or not a variable can be an None value

        Returns:
            A variable reference to the constructor

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        self._logger.debug("Adding constructor %s", constructor)
        if recursion_depth > config.INSTANCE.max_recursion:
            self._logger.debug("Max recursion depth reached")
            raise ConstructionFailedException("Max recursion depth reached")

        if position < 0:
            position = test_case.size()

        signature = constructor.inferred_signature
        length = test_case.size()
        try:
            parameters: List[vr.VariableReference] = self.satisfy_parameters(
                test_case=test_case,
                signature=signature,
                position=position,
                recursion_depth=recursion_depth + 1,
                allow_none=allow_none,
            )
            new_length = test_case.size()
            position = position + new_length - length

            statement = par_stmt.ConstructorStatement(
                test_case=test_case,
                generic_callable=constructor,
                args=parameters,
            )
            return test_case.add_statement(statement, position)
        except BaseException as exception:
            raise ConstructionFailedException(
                f"Failed to add constructor for {constructor} "
                f"due to {exception}.")
Beispiel #2
0
    def _create_dict(
        self,
        test_case: tc.TestCase,
        parameter_type: Type,
        position: int,
        recursion_depth: int,
    ) -> vr.VariableReference:
        args = get_args(parameter_type)
        if len(args) != 2:
            raise ConstructionFailedException()
        size = randomness.next_int(0, config.configuration.collection_size)
        elements = []
        for _ in range(size):
            previous_length = test_case.size()
            key = self._create_or_reuse_variable(test_case, args[0], position,
                                                 recursion_depth + 1, True)
            position += test_case.size() - previous_length
            previous_length = test_case.size()
            value = self._create_or_reuse_variable(test_case, args[1],
                                                   position,
                                                   recursion_depth + 1, True)
            position += test_case.size() - previous_length
            if key is not None and value is not None:
                elements.append((key, value))

        ret = test_case.add_statement(
            coll_stmt.DictStatement(test_case, parameter_type, elements),
            position)
        ret.distance = recursion_depth
        return ret
Beispiel #3
0
    def _get_variable_fallback(
        self,
        test_case: tc.TestCase,
        parameter_type: Optional[Type],
        position: int,
        recursion_depth: int,
        allow_none: bool,
    ) -> Optional[vr.VariableReference]:
        """Best effort approach to return some kind of matching variable."""
        objects = test_case.get_objects(parameter_type, position)

        # No objects to choose from, so either create random type variable or use None.
        if not objects:
            if config.INSTANCE.guess_unknown_types and randomness.next_float(
            ) <= 0.85:
                return self._create_random_type_variable(
                    test_case, position, recursion_depth, allow_none)
            if allow_none:
                return self._create_none(test_case, parameter_type, position,
                                         recursion_depth)
            raise ConstructionFailedException(
                f"No objects for type {parameter_type}")

        # Could not create, so re-use an existing variable.
        self._logger.debug("Choosing from %d existing objects: %s",
                           len(objects), objects)
        reference = randomness.choice(objects)
        self._logger.debug("Use existing object of type %s: %s",
                           parameter_type, reference)
        return reference
Beispiel #4
0
    def add_method(
        self,
        test_case: tc.TestCase,
        method: gao.GenericMethod,
        position: int = -1,
        recursion_depth: int = 0,
        allow_none: bool = True,
        callee: Optional[vr.VariableReference] = None,
    ) -> vr.VariableReference:
        """Adds a method call to a test case at a given position.

        If the position is not given, the method call will be appended to the end of
        the test case.  A given recursion depth controls how far the factory searches
        for suitable parameter values.

        :param test_case: The test case
        :param method: The method call to add to the test case
        :param position: The position where to put the statement in the test case,
        defaults to the end of the test case
        :param recursion_depth: A recursion limit for the search of parameter values
        :param allow_none: Whether or not a variable can hold a None value
        :param callee: The callee, if it is already known.
        :return: A variable reference to the method call's result
        """
        self._logger.debug("Adding method %s", method)
        if recursion_depth > config.INSTANCE.max_recursion:
            self._logger.debug("Max recursion depth reached")
            raise ConstructionFailedException("Max recursion depth reached")

        if position < 0:
            position = test_case.size()

        signature = method.inferred_signature
        length = test_case.size()
        if callee is None:
            callee = self._create_or_reuse_variable(test_case,
                                                    method.owner,
                                                    position,
                                                    recursion_depth,
                                                    allow_none=True)
        assert callee, "The callee must not be None"
        parameters: List[vr.VariableReference] = self.satisfy_parameters(
            test_case=test_case,
            signature=signature,
            position=position,
            recursion_depth=recursion_depth + 1,
            allow_none=allow_none,
        )

        new_length = test_case.size()
        position = position + new_length - length

        statement = par_stmt.MethodStatement(
            test_case=test_case,
            generic_callable=method,
            callee=callee,
            args=parameters,
        )
        return test_case.add_statement(statement, position)
Beispiel #5
0
    def append_generic_statement(
        self,
        test_case: tc.TestCase,
        statement: gao.GenericAccessibleObject,
        position: int = -1,
        recursion_depth: int = 0,
        allow_none: bool = True,
    ) -> Optional[vr.VariableReference]:
        """Appends a generic accessible object to a test case.

        Args:
            test_case: The test case
            statement: The object to append
            position: The position to insert the statement, default is at the end
                of the test case
            recursion_depth: The recursion depth for search
            allow_none: Whether or not parameter variables can hold None values

        Returns:
            An optional variable reference to the added statement

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        new_position = test_case.size() if position == -1 else position
        if isinstance(statement, gao.GenericConstructor):
            return self.add_constructor(
                test_case,
                statement,
                position=new_position,
                allow_none=allow_none,
                recursion_depth=recursion_depth,
            )
        if isinstance(statement, gao.GenericMethod):
            return self.add_method(
                test_case,
                statement,
                position=new_position,
                allow_none=allow_none,
                recursion_depth=recursion_depth,
            )
        if isinstance(statement, gao.GenericFunction):
            return self.add_function(
                test_case,
                statement,
                position=new_position,
                allow_none=allow_none,
                recursion_depth=recursion_depth,
            )
        if isinstance(statement, gao.GenericField):
            return self.add_field(
                test_case,
                statement,
                position=new_position,
                recursion_depth=recursion_depth,
            )
        raise ConstructionFailedException(
            f"Unknown statement type: {statement}")
Beispiel #6
0
def test_insert_random_call_on_object_at_no_accessible(
        test_case_mock, variable_reference_mock):
    test_cluster = MagicMock(TestCluster)
    test_cluster.get_random_call_for.side_effect = ConstructionFailedException(
    )
    test_factory = tf.TestFactory(test_cluster)
    variable_reference_mock.variable_type = float
    assert not test_factory.insert_random_call_on_object_at(
        test_case_mock, variable_reference_mock, 0)
 def get_random_object(self, parameter_type: Type,
                       position: int) -> vr.VariableReference:
     """Get a random object of the given type up to the given position (exclusive)."""
     variables = self.get_objects(parameter_type, position)
     if len(variables) == 0:
         raise ConstructionFailedException(
             f"Found no variables of type {parameter_type} at position {position}"
         )
     return randomness.choice(variables)
Beispiel #8
0
    def add_function(
        self,
        test_case: tc.TestCase,
        function: gao.GenericFunction,
        position: int = -1,
        recursion_depth: int = 0,
        allow_none: bool = True,
    ) -> vr.VariableReference:
        """Adds a function call to a test case at a given position.

        If the position is not given, the function call will be appended to the end
        of the test case.  A given recursion depth controls how far the factory
        searches for suitable parameter values.

        Args:
            test_case: The test case
            function: The function call to add to the test case
            position: the position where to put the statement in the test case,
                defaults to the end of the test case
            recursion_depth: A recursion limit for the search of parameter values
            allow_none: Whether or not a variable can hold a None value

        Returns:
            A variable reference to the function call's result

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        self._logger.debug("Adding function %s", function)
        if recursion_depth > config.INSTANCE.max_recursion:
            self._logger.debug("Max recursion depth reached")
            raise ConstructionFailedException("Max recursion depth reached")

        if position < 0:
            position = test_case.size()

        signature = function.inferred_signature
        length = test_case.size()
        parameters: List[vr.VariableReference] = self.satisfy_parameters(
            test_case=test_case,
            signature=signature,
            position=position,
            recursion_depth=recursion_depth + 1,
            allow_none=allow_none,
        )
        new_length = test_case.size()
        position = position + new_length - length

        statement = par_stmt.FunctionStatement(
            test_case=test_case,
            generic_callable=function,
            args=parameters,
        )
        return test_case.add_statement(statement, position)
Beispiel #9
0
 def _get_random_non_none_object(test_case: tc.TestCase, type_: Type,
                                 position: int) -> vr.VariableReference:
     variables = test_case.get_objects(type_, position)
     variables = [
         var for var in variables if not isinstance(
             test_case.get_statement(var.get_statement_position()),
             prim.NoneStatement,
         )
     ]
     if len(variables) == 0:
         raise ConstructionFailedException(
             f"Found no variables of type {type_} at position {position}")
     return randomness.choice(variables)
Beispiel #10
0
    def append_statement(
        self,
        test_case: tc.TestCase,
        statement: stmt.Statement,
        allow_none: bool = True,
    ) -> None:
        """Appends a statement to a test case.

        Args:
            test_case: The test case
            statement: The statement to append
            allow_none: Whether or not parameter variables can hold None values

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        if isinstance(statement, par_stmt.ConstructorStatement):
            self.add_constructor(
                test_case,
                statement.accessible_object(),
                position=test_case.size(),
                allow_none=allow_none,
            )
        elif isinstance(statement, par_stmt.MethodStatement):
            self.add_method(
                test_case,
                statement.accessible_object(),
                position=test_case.size(),
                allow_none=allow_none,
            )
        elif isinstance(statement, par_stmt.FunctionStatement):
            self.add_function(
                test_case,
                statement.accessible_object(),
                position=test_case.size(),
                allow_none=allow_none,
            )
        elif isinstance(statement, f_stmt.FieldStatement):
            self.add_field(
                test_case,
                statement.field,
                position=test_case.size(),
            )
        elif isinstance(statement, prim.PrimitiveStatement):
            self.add_primitive(test_case, statement, position=test_case.size())
        else:
            raise ConstructionFailedException(
                f"Unknown statement type: {statement}")
Beispiel #11
0
    def get_random_call_for(self, type_: Type) -> GenericAccessibleObject:
        """Get a random modifier for the given type.

        Args:
            type_: The type

        Returns:
            A random modifier for that type

        Raises:
            ConstructionFailedException: if no modifiers for the type exist
        """
        accessible_objects = self.get_modifiers_for(type_)
        if len(accessible_objects) == 0:
            raise ConstructionFailedException("No modifiers for " + str(type_))
        return randomness.choice(list(accessible_objects))
Beispiel #12
0
    def add_field(
        self,
        test_case: tc.TestCase,
        field: gao.GenericField,
        position: int = -1,
        recursion_depth: int = 0,
        callee: Optional[vr.VariableReference] = None,
    ) -> vr.VariableReference:
        """Adds a field access to a test case at a given position.

        If the position is not given, the field access will be appended to the end of
        the test case.  A given recursion depth controls how far the factory searches
        for suitable parameter values.

        Args:
            test_case: The test case
            field: The field access to add to the test case
            position: The position where to put the statement in the test case,
                defaults to the end of the test case
            recursion_depth: A recursion limit for the search of values
            callee: The callee, if it is already known.

        Returns:
            A variable reference to the field value

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        self._logger.debug("Adding field %s", field)
        if recursion_depth > config.INSTANCE.max_recursion:
            self._logger.debug("Max recursion depth reached")
            raise ConstructionFailedException("Max recursion depth reached")

        if position < 0:
            position = test_case.size()

        length = test_case.size()
        if callee is None:
            callee = self._create_or_reuse_variable(test_case,
                                                    field.owner,
                                                    position,
                                                    recursion_depth,
                                                    allow_none=False)
        assert callee, "The callee must not be None"
        position = position + test_case.size() - length
        statement = f_stmt.FieldStatement(test_case, field, callee)
        return test_case.add_statement(statement, position)
Beispiel #13
0
    def _get_variable_fallback(
        self,
        test_case: tc.TestCase,
        parameter_type: Optional[Type],
        position: int,
        recursion_depth: int,
        allow_none: bool,
    ) -> Optional[vr.VariableReference]:
        """Best effort approach to return some kind of matching variable.

        Args:
            test_case: The test case to take the variable from
            parameter_type: the type of the variable that is needed
            position: the position to limit the search
            recursion_depth: the current recursion level
            allow_none: whether or not a None value is allowed

        Returns:
            A variable if found

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        objects = test_case.get_objects(parameter_type, position)

        # No objects to choose from, so either create random type variable or use None.
        if not objects:
            if config.INSTANCE.guess_unknown_types and randomness.next_float(
            ) <= 0.85:
                return self._create_random_type_variable(
                    test_case, position, recursion_depth, allow_none)
            if allow_none:
                return self._create_none(test_case, parameter_type, position,
                                         recursion_depth)
            raise ConstructionFailedException(
                f"No objects for type {parameter_type}")

        # Could not create, so re-use an existing variable.
        self._logger.debug("Choosing from %d existing objects: %s",
                           len(objects), objects)
        reference = randomness.choice(objects)
        self._logger.debug("Use existing object of type %s: %s",
                           parameter_type, reference)
        return reference
Beispiel #14
0
def test_change_random_call_failed(function_mock, method_mock,
                                   constructor_mock):
    test_case = dtc.DefaultTestCase()
    float_prim = prim.FloatPrimitiveStatement(test_case, 5.0)
    int0 = prim.IntPrimitiveStatement(test_case, 2)
    float_function1 = par_stmt.FunctionStatement(test_case, function_mock,
                                                 [float_prim.return_value])
    const = par_stmt.ConstructorStatement(test_case, constructor_mock)
    test_case.add_statement(float_prim)
    test_case.add_statement(int0)
    test_case.add_statement(const)
    test_case.add_statement(float_function1)

    test_cluster = MagicMock(TestCluster)
    test_cluster.get_generators_for.return_value = {function_mock, method_mock}
    test_factory = tf.TestFactory(test_cluster)
    with mock.patch.object(test_factory, "change_call") as change_mock:
        change_mock.side_effect = ConstructionFailedException()
        assert not test_factory.change_random_call(test_case, float_function1)
        change_mock.assert_called_with(test_case, float_function1, method_mock)
Beispiel #15
0
    def get_random_object(self, parameter_type: Optional[Type],
                          position: int) -> vr.VariableReference:
        """Get a random object of the given type up to the given position (exclusive).

        Args:
            parameter_type: the parameter type
            position: the position

        Returns:
            A random object of given type up to the given position

        Raises:
            ConstructionFailedException: if no object could be found
        """
        variables = self.get_objects(parameter_type, position)
        if len(variables) == 0:
            raise ConstructionFailedException(
                f"Found no variables of type {parameter_type} at position {position}"
            )
        return randomness.choice(variables)
Beispiel #16
0
 def side_effect(tc, f, p, callee=None):
     tc.add_statement(prim.IntPrimitiveStatement(tc, 5), position=p)
     tc.add_statement(prim.IntPrimitiveStatement(tc, 5), position=p)
     tc.add_statement(prim.IntPrimitiveStatement(tc, 5), position=p)
     raise ConstructionFailedException()
Beispiel #17
0
    def satisfy_parameters(
        self,
        test_case: tc.TestCase,
        signature: InferredSignature,
        callee: Optional[vr.VariableReference] = None,
        position: int = -1,
        recursion_depth: int = 0,
        allow_none: bool = True,
        can_reuse_existing_variables: bool = True,
    ) -> Dict[str, vr.VariableReference]:
        """Satisfy a list of parameters by reusing or creating variables.

        Args:
            test_case: The test case
            signature: The inferred signature of the method
            callee: The callee of the method
            position: The current position in the test case
            recursion_depth: The recursion depth
            allow_none: Whether or not a variable can be a None value
            can_reuse_existing_variables: Whether or not existing variables shall
                be reused.

        Returns:
            A dict of variable references for the parameters

        Raises:
            ConstructionFailedException: if construction of an object failed
        """
        if position < 0:
            position = test_case.size()

        parameters: Dict[str, vr.VariableReference] = {}
        self._logger.debug(
            "Trying to satisfy %d parameters at position %d",
            len(signature.parameters),
            position,
        )

        for parameter_name, parameter_type in signature.parameters.items():
            self._logger.debug("Current parameter type: %s", parameter_type)

            previous_length = test_case.size()

            if (is_optional_parameter(signature, parameter_name)
                    and randomness.next_float() <
                    config.configuration.skip_optional_parameter_probability):
                continue

            if can_reuse_existing_variables:
                self._logger.debug("Can re-use variables")
                var = self._create_or_reuse_variable(
                    test_case,
                    parameter_type,
                    position,
                    recursion_depth,
                    allow_none,
                    callee,
                )
            else:
                self._logger.debug(
                    "Cannot re-use variables: attempt to creating new one")
                var = self._create_variable(
                    test_case,
                    parameter_type,
                    position,
                    recursion_depth,
                    allow_none,
                    callee,
                )
            if not var:
                raise ConstructionFailedException(
                    (f"Failed to create variable for type {parameter_type} " +
                     f"at position {position}"), )

            parameters[parameter_name] = var
            current_length = test_case.size()
            position += current_length - previous_length

        self._logger.debug("Satisfied %d parameters", len(parameters))
        return parameters
Beispiel #18
0
def test__get_possible_calls_no_calls():
    cluster = MagicMock(TestCluster)
    cluster.get_generators_for = MagicMock(
        side_effect=ConstructionFailedException())
    assert tf.TestFactory(cluster)._get_possible_calls(int, []) == []
Beispiel #19
0
    def satisfy_parameters(
        self,
        test_case: tc.TestCase,
        signature: InferredSignature,
        callee: Optional[vr.VariableReference] = None,
        position: int = -1,
        recursion_depth: int = 0,
        allow_none: bool = True,
        can_reuse_existing_variables: bool = True,
    ) -> List[vr.VariableReference]:
        """Satisfy a list of parameters by reusing or creating variables.

        :param test_case: The test case
        :param signature: The inferred signature of the method
        :param callee: The callee of the method
        :param position: The current position in the test case
        :param recursion_depth: The recursion depth
        :param allow_none: Whether or not a variable can be a None value
        :param can_reuse_existing_variables: Whether or not existing variables shall
        be reused.
        :return: A list of variable references for the parameters
        """
        if position < 0:
            position = test_case.size()

        parameters: List[vr.VariableReference] = []
        self._logger.debug(
            "Trying to satisfy %d parameters at position %d",
            len(signature.parameters),
            position,
        )

        for parameter_name, parameter_type in signature.parameters.items():
            self._logger.debug("Current parameter type: %s", parameter_type)

            previous_length = test_case.size()

            if should_skip_parameter(signature, parameter_name):
                # TODO Implement generation for positional parameters of variable length
                # TODO Implement generation for keyword parameters of variable length
                self._logger.info("Skip parameter %s", parameter_name)
                continue

            if can_reuse_existing_variables:
                self._logger.debug("Can re-use variables")
                var = self._create_or_reuse_variable(
                    test_case,
                    parameter_type,
                    position,
                    recursion_depth,
                    allow_none,
                    callee,
                )
            else:
                self._logger.debug(
                    "Cannot re-use variables: attempt to creating new one")
                var = self._create_variable(
                    test_case,
                    parameter_type,
                    position,
                    recursion_depth,
                    allow_none,
                    callee,
                )
            if not var:
                raise ConstructionFailedException(
                    f"Failed to create variable for type {parameter_type} "
                    f"at position {position}", )

            parameters.append(var)
            current_length = test_case.size()
            position += current_length - previous_length

        self._logger.debug("Satisfied %d parameters", len(parameters))
        return parameters
Beispiel #20
0
def test_raise_construction_failed_exception():
    with pytest.raises(ConstructionFailedException):
        raise ConstructionFailedException()
Beispiel #21
0
def test_raise_construction_failed_exception_with_message():
    with pytest.raises(ConstructionFailedException) as exception:
        raise ConstructionFailedException("foo")
    assert exception.value.args[0] == "foo"