def mutate(self) -> None: """Apply mutation at test suite level.""" assert self._test_case_factory, "Can only mutate with test case factory." changed = False # Mutate existing test cases. for test in self._tests: if randomness.next_float() < 1.0 / self.size(): test.mutate() if test.has_changed(): changed = True # Randomly add new test cases. alpha = config.INSTANCE.test_insertion_probability exponent = 1 while (randomness.next_float() <= pow(alpha, exponent) and self.size() < config.INSTANCE.max_size): self.add_test(self._test_case_factory.get_test_case()) exponent += 1 changed = True # Remove any tests that have no more statements left. self._tests = [t for t in self._tests if t.size() > 0] if changed: self.set_changed(True)
def mutate(self) -> None: """Each statement is mutated with probability 1/l.""" changed = False if (config.INSTANCE.chop_max_length and self.size() >= config.INSTANCE.chromosome_length): last_mutatable_position = self._get_last_mutatable_statement() if last_mutatable_position is not None: self.chop(last_mutatable_position) changed = True if randomness.next_float() <= config.INSTANCE.test_delete_probability: if self._mutation_delete(): changed = True if randomness.next_float() <= config.INSTANCE.test_change_probability: if self._mutation_change(): changed = True if randomness.next_float() <= config.INSTANCE.test_insert_probability: if self._mutation_insert(): changed = True if changed: self.set_changed(True)
def mutate(self) -> None: """Apply mutation at test suite level.""" assert ( self._test_case_chromosome_factory is not None ), "Mutation is not possibly without test case chromosome factory" changed = False # Mutate existing test cases. for test in self._test_case_chromosomes: if randomness.next_float() < 1.0 / self.size(): test.mutate() if test.has_changed(): changed = True # Randomly add new test cases. alpha = config.configuration.test_insertion_probability exponent = 1 while (randomness.next_float() <= pow(alpha, exponent) and self.size() < config.configuration.max_size): self.add_test_case_chromosome( self._test_case_chromosome_factory.get_chromosome()) exponent += 1 changed = True # Remove any tests that have no more statements left. self._test_case_chromosomes = [ t for t in self._test_case_chromosomes if t.size() > 0 ] if changed: self.set_changed(True)
def mutate(self) -> None: changed = False if (config.configuration.chop_max_length and self.size() >= config.configuration.chromosome_length): last_mutatable_position = self.get_last_mutatable_statement() if last_mutatable_position is not None: self._test_case.chop(last_mutatable_position) changed = True # In case mutation removes all calls on the SUT. backup = self.test_case.clone() if randomness.next_float( ) <= config.configuration.test_delete_probability: if self._mutation_delete(): changed = True if randomness.next_float( ) <= config.configuration.test_change_probability: if self._mutation_change(): changed = True if randomness.next_float( ) <= config.configuration.test_insert_probability: if self._mutation_insert(): changed = True assert self._test_factory, "Required for mutation" if not self._test_factory.has_call_on_sut(self._test_case): self._test_case = backup self._mutation_insert() if changed: self.set_changed(True)
def _mutate_parameters(self, p_per_param: float) -> bool: """ Mutates args and kwargs with the given probability. :param p_per_param: The probability for one parameter to be mutated. """ changed = False for arg in range(len(self.args)): if randomness.next_float() < p_per_param: changed |= self._mutate_parameter(arg) for kwarg in self.kwargs.keys(): if randomness.next_float() < p_per_param: changed |= self._mutate_parameter(kwarg) return changed
def delta(self) -> None: assert self._value is not None working_on = list(self._value) p_perform_action = 1.0 / 3.0 if randomness.next_float() < p_perform_action and len(working_on) > 0: working_on = self._random_deletion(working_on) if randomness.next_float() < p_perform_action and len(working_on) > 0: working_on = self._random_replacement(working_on) if randomness.next_float() < p_perform_action: working_on = self._random_insertion(working_on) self._value = "".join(working_on)
def _random_replacement(working_on: List[str]) -> List[str]: p_per_char = 1.0 / len(working_on) return [ randomness.next_char() if randomness.next_float() < p_per_char else char for char in working_on ]
def insert_random_statement(self, test_case: tc.TestCase, last_position: int) -> int: """Insert a random statement up to the given position. If the insertion was successful, the position at which the statement was inserted is returned, otherwise -1. Args: test_case: The test case to add the statement to last_position: The last position before that the statement is inserted Returns: The index the statement was inserted to, otherwise -1 """ old_size = test_case.size() rand = randomness.next_float() position = randomness.next_int(0, last_position + 1) if rand <= config.INSTANCE.insertion_uut: success = self.insert_random_call(test_case, position) else: success = self.insert_random_call_on_object(test_case, position) if test_case.size() - old_size > 1: position += test_case.size() - old_size - 1 if success: return position return -1
def _mutation_insert(self) -> bool: """With exponentially decreasing probability, insert statements at a random position. Returns: Whether or not the test case was changed """ changed = False alpha = config.configuration.statement_insertion_probability exponent = 1 while (randomness.next_float() <= pow(alpha, exponent) and self.size() < config.configuration.chromosome_length): assert self._test_factory, "Mutation requires a test factory." max_position = self.get_last_mutatable_statement() if max_position is None: # No mutatable statement found, so start at the first position. max_position = 0 else: # Also include the position after the last mutatable statement. max_position += 1 position = self._test_factory.insert_random_statement( self._test_case, max_position) exponent += 1 if 0 <= position < self.size(): changed = True return changed
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
def _attempt_generation( self, test_case: tc.TestCase, parameter_type: Optional[Type], position: int, recursion_depth: int, allow_none: bool, exclude: Optional[vr.VariableReference] = None, ) -> Optional[vr.VariableReference]: # We only select a concrete type e.g. from a union, when we are forced to choose one. parameter_type = self._test_cluster.select_concrete_type( parameter_type) if not parameter_type: return None if allow_none and randomness.next_float( ) <= config.INSTANCE.none_probability: return self._create_none(test_case, parameter_type, position, recursion_depth) if is_primitive_type(parameter_type): return self._create_primitive( test_case, parameter_type, position, recursion_depth, ) if type_generators := self._test_cluster.get_generators_for( parameter_type): return self._attempt_generation_for_type(test_case, position, recursion_depth, allow_none, type_generators)
def _random_insertion(self) -> bool: changed = False pos = 0 if len(self._elements) > 0: pos = randomness.next_int(0, len(self._elements) + 1) # This is so ugly... key_type = (get_args(self.ret_val.variable_type)[0] if get_args(self.ret_val.variable_type) else None) val_type = (get_args(self.ret_val.variable_type)[1] if get_args(self.ret_val.variable_type) else None) possibles_keys = self.test_case.get_objects(key_type, self.get_position()) possibles_values = self.test_case.get_objects(val_type, self.get_position()) alpha = 0.5 exponent = 1 while randomness.next_float() <= pow(alpha, exponent): exponent += 1 if len(possibles_keys) > 0 and len(possibles_values) > 0: self._elements.insert( pos, ( randomness.choice(possibles_keys), randomness.choice(possibles_values), ), ) changed = True return changed
def mutate(self) -> bool: changed = False if (randomness.next_float() < config.Configuration.test_delete_probability and len(self._elements) > 0): changed |= self._random_deletion() if (randomness.next_float() < config.Configuration.test_change_probability and len(self._elements) > 0): changed |= self._random_replacement() if randomness.next_float( ) < config.Configuration.test_insert_probability: changed |= self._random_insertion() return changed
def randomize_value(self) -> None: if (config.INSTANCE.constant_seeding and StaticConstantSeeding().has_ints and randomness.next_float() <= 0.90): self._value = StaticConstantSeeding().random_int else: self._value = int(randomness.next_gaussian() * config.INSTANCE.max_int)
def _random_deletion(self) -> bool: p_per_element = 1.0 / len(self._elements) previous_length = len(self._elements) self._elements = [ element for element in self._elements if randomness.next_float() >= p_per_element ] return previous_length != len(self._elements)
def randomize_value(self) -> None: if (config.INSTANCE.constant_seeding and StaticConstantSeeding().has_strings and randomness.next_float() <= 0.90): self._value = StaticConstantSeeding().random_string else: length = randomness.next_int(0, config.INSTANCE.string_length + 1) self._value = randomness.next_string(length)
def randomize_value(self) -> None: if (config.INSTANCE.constant_seeding and StaticConstantSeeding().has_floats and randomness.next_float() <= 0.90): self._value = StaticConstantSeeding().random_float else: val = randomness.next_gaussian() * config.INSTANCE.max_int precision = randomness.next_int(0, 7) self._value = round(val, precision)
def delta(self) -> None: assert self._value is not None probability = randomness.next_float() if probability < 1.0 / 3.0: self._value += randomness.next_gaussian() * config.INSTANCE.max_delta elif probability < 2.0 / 3.0: self._value += randomness.next_gaussian() else: self._value = round(self._value, randomness.next_int(0, 7))
def get_test_case(self) -> tc.TestCase: if ( config.configuration.initial_population_seeding and initpopseeding.initialpopulationseeding.has_tests and randomness.next_float() <= config.configuration.seeded_testcases_reuse_probability ): return initpopseeding.initialpopulationseeding.seeded_testcase return self._delegate.get_test_case()
def cross_over(self, parent1: T, parent2: T): if parent1.size() < 2 or parent2.size() < 2: return split_point = randomness.next_float() position1 = floor((parent1.size() - 1) * split_point) + 1 position2 = floor((parent2.size() - 1) * split_point) + 1 clone1 = parent1.clone() clone2 = parent2.clone() parent1.cross_over(clone2, position1, position2) parent2.cross_over(clone1, position2, position1)
def get_index(self, population: List[T]) -> int: """Provides an index in the population that is chosen by rank selection. Make sure that the population is sorted. The fittest chromosomes have to come first.""" random_value = randomness.next_float() bias = config.INSTANCE.rank_bias return int( len(population) * ((bias - sqrt(bias**2 - (4.0 * (bias - 1.0) * random_value))) / 2.0 / (bias - 1.0)))
def _random_replacement(self) -> bool: p_per_element = 1.0 / len(self._elements) changed = False for i, elem in enumerate(self._elements): if randomness.next_float() < p_per_element: # TODO(fk) what if the current type is not correct? replace = randomness.choice( self.test_case.get_objects(elem.variable_type, self.get_position()) + [elem]) self._elements[i] = replace changed |= replace != elem return changed
def mutate(self) -> bool: if randomness.next_float( ) >= config.INSTANCE.change_parameter_probability: return False objects = self.test_case.get_objects(self.source.variable_type, self.get_position()) objects.remove(self.source) if len(objects) > 0: self.source = randomness.choice(objects) return True return False
def mutate(self) -> bool: if randomness.next_float( ) >= config.INSTANCE.change_parameter_probability: return False changed = False mutable_param_count = self._mutable_argument_count() if mutable_param_count > 0: p_per_param = 1.0 / mutable_param_count changed |= self._mutate_special_parameters(p_per_param) changed |= self._mutate_parameters(p_per_param) return changed
def _random_insertion(working_on: List[str]) -> List[str]: pos = 0 if len(working_on) > 0: pos = randomness.next_int(0, len(working_on) + 1) alpha = 0.5 exponent = 1 while (randomness.next_float() <= pow(alpha, exponent) and len(working_on) < config.INSTANCE.string_length): exponent += 1 working_on = working_on[:pos] + [randomness.next_char() ] + working_on[pos:] return working_on
def _mutate_special_parameters(self, p_per_param: float) -> bool: # We mutate the callee here, as the special parameter. if randomness.next_float() < p_per_param: callee = self.callee objects = self.test_case.get_objects(callee.variable_type, self.get_position()) objects.remove(callee) if len(objects) > 0: self.callee = randomness.choice(objects) return True return False
def _random_insertion(working_on: List[int]) -> List[int]: pos = 0 if len(working_on) > 0: pos = randomness.next_int(0, len(working_on) + 1) alpha = 0.5 exponent = 1 while ( randomness.next_float() <= pow(alpha, exponent) and len(working_on) < config.configuration.bytes_length ): exponent += 1 working_on = working_on[:pos] + [randomness.next_byte()] + working_on[pos:] return working_on
def _mutation_delete(self) -> bool: last_mutatable_statement = self._get_last_mutatable_statement() if last_mutatable_statement is None: return False changed = False p_per_statement = 1.0 / (last_mutatable_statement + 1) for idx in reversed(range(last_mutatable_statement + 1)): if idx >= self.size(): continue if randomness.next_float() <= p_per_statement: changed |= self._delete_statement(idx) return changed
def randomize_value(self) -> None: use_seed = ( randomness.next_float() <= config.configuration.seeded_primitives_reuse_probability ) if ( config.configuration.dynamic_constant_seeding and dynamic_constant_seeding.has_ints and use_seed and config.configuration.constant_seeding and randomness.next_float() <= config.configuration.seeded_dynamic_values_reuse_probability ): self._value = dynamic_constant_seeding.random_int elif ( config.configuration.constant_seeding and static_constant_seeding.has_ints and use_seed ): self._value = static_constant_seeding.random_int else: self._value = int(randomness.next_gaussian() * config.configuration.max_int)
def evolve(self) -> None: """Evolve the current population and replace it with a new one.""" new_generation = [] new_generation.extend(self.elitism()) while not self.is_next_population_full(new_generation): parent1 = self._selection_function.select(self._population, 1)[0] parent2 = self._selection_function.select(self._population, 1)[0] offspring1 = parent1.clone() offspring2 = parent2.clone() try: if randomness.next_float() <= config.INSTANCE.crossover_rate: self._crossover_function.cross_over(offspring1, offspring2) offspring1.mutate() offspring2.mutate() except ConstructionFailedException as ex: self._logger.info("Crossover/Mutation failed: %s", ex) continue fitness_parents = min(parent1.get_fitness(), parent2.get_fitness()) fitness_offspring = min(offspring1.get_fitness(), offspring2.get_fitness()) length_parents = ( parent1.total_length_of_test_cases + parent2.total_length_of_test_cases ) length_offspring = ( offspring1.total_length_of_test_cases + offspring2.total_length_of_test_cases ) best_individual = self._get_best_individual() if (fitness_offspring < fitness_parents) or ( fitness_offspring == fitness_parents and length_offspring <= length_parents ): for offspring in [offspring1, offspring2]: if ( offspring.total_length_of_test_cases <= 2 * best_individual.total_length_of_test_cases ): new_generation.append(offspring) else: new_generation.append(randomness.choice([parent1, parent2])) else: new_generation.append(parent1) new_generation.append(parent2) self._population = new_generation self._sort_population() StatisticsTracker().current_individual(self._get_best_individual())