提交 1608046f 编写于 作者: J Jan Richter

CIT Varianter with constraints

The new version of CIT varianter which support constraints for combinations.
Signed-off-by: NJan Richter <jarichte@redhat.com>
上级 2be2eab2
PARAMETERS
color [black, gold, red, green ]
shape[square, triangle, circle]
state[liquid, solid, gas]
material[leather, plastic, aluminum]
coating[anodic, cathodic]
p7[1,3,5,6,7]
p8[1,2,5,3,4]
p9[1,2,3,4,5]
p10[1,2,3,4,5]
CONSTRAINTS
color != black || shape != square
color != black || shape != triangle
color != gold || coating != cathodic
material != aluminum || color != gold
[parameters]
color: black, gold, red, green
shape: square, triangle, circle
state: liquid, solid, gas
material: leather, plastic, aluminum
coating: anodic, cathodic
PARAMETERS
color [black, gold, red, green ]
shape[square, triangle, circle]
state[liquid, solid, gas]
material[leather, plastic, aluminum]
coating[anodic, cathodic]
CONSTRAINTS
color != black
color != gold
color != red
import random
import logging
from avocado.core.output import LOG_UI
from avocado.core.output import Throbber
from .Solver import Solver
from .CombinationMatrix import CombinationMatrix
ITERATIONS_SIZE = 600
LOG = LOG_UI.getChild("Cit")
LOG.setLevel(logging.INFO)
class Cit:
def __init__(self, input_data, t_value, constraints):
"""
Creation of CombinationMatrix from user input
:param input_data: parameters from user
:param t_value: size of one combination
:param constraints: constraints of combinations
"""
self.data = input_data
self.t_value = t_value
# CombinationMatrix creation
self.combination_matrix = CombinationMatrix(input_data, t_value)
# Creation of solver and simplification of constraints
self.solver = Solver(input_data, constraints)
# Combinations which do not match to the constraints are disabled
self.solver.clean_hash_table(self.combination_matrix, t_value)
self.final_matrix = []
self.__throbber = Throbber()
def final_matrix_init(self):
"""
Creation of the first solution. This solution is the start of searching
for the best solution
:return: solution matrix (list(list))
"""
self.final_matrix = [self.create_random_row_with_constraints()]
self.combination_matrix.cover_solution_row(self.final_matrix[0])
while self.combination_matrix.total_uncovered != 0:
if self.combination_matrix.total_uncovered < self.combination_matrix.total_covered_more_than_ones:
new_row = self.compute_row()
else:
new_row = self.compute_row_using_hamming_distance()
self.combination_matrix.cover_solution_row(new_row)
self.final_matrix.append(new_row)
return self.final_matrix
def compute(self):
"""
Searching for the best solution. It creates one solution and from that,
it tries to create smaller solution. This searching process is limited
by ITERATIONS_SIZE. When ITERATIONS_SIZE is 0 the last found solution is
the best solution.
:return: The best solution
"""
self.final_matrix = self.final_matrix_init()
matrix = [x[:] for x in self.final_matrix]
iterations = ITERATIONS_SIZE
step_size = 1
deleted_rows = []
while step_size != 0:
for i in range(step_size):
delete_row = matrix.pop(random.randint(0, len(matrix) - 1))
self.combination_matrix.uncover_solution_row(delete_row)
deleted_rows.append(delete_row)
LOG.debug("I'm trying solution with size " + str(len(matrix)) + " and " + str(iterations) + " iterations")
matrix, is_better_solution = self.find_better_solution(iterations, matrix)
if is_better_solution:
self.final_matrix = matrix[:]
deleted_rows = []
step_size *= 2
LOG.debug("-----solution with size " + str(len(matrix)) + " was found-----\n")
iterations = ITERATIONS_SIZE
else:
LOG.debug("-----solution with size " + str(len(matrix)) + " was not found-----\n")
for i in range(step_size):
self.combination_matrix.cover_solution_row(deleted_rows[i])
matrix.append(deleted_rows[i])
if step_size > 1:
step_size = 1
else:
step_size = 0
return self.final_matrix
def find_better_solution(self, counter, matrix):
"""
Changing the matrix to cover all combinations
:param counter: maximum number of changes in the matrix
:param matrix: matrix to be changed
:return: new matrix and is changes have been successful?
"""
while self.combination_matrix.total_uncovered != 0:
LOG.debug(self.__throbber.render(), extra={"skip_newline": True})
solution, row_index, _ = self.use_random_algorithm(matrix)
self.combination_matrix.uncover_solution_row(matrix[row_index])
self.combination_matrix.cover_solution_row(solution)
matrix[row_index] = solution
if counter == 0:
return matrix, False
counter -= 1
return matrix, True
def use_random_algorithm(self, matrix):
"""
Applies one of these algorithms to the matrix.
It chooses algorithm by random in proportion 1:1:8
:param matrix: matrix to be changed
:return: new row of matrix, index of row inside matrix and parameters which has been changed
"""
switch = random.randint(0, 9)
if switch == 0:
solution, row_index, parameters = self.change_one_value(matrix)
elif switch == 1:
solution, row_index, parameters = self.change_one_column(matrix)
else:
solution, row_index, parameters = self.cover_missing_combination(matrix)
return solution, row_index, parameters
def compute_row(self):
"""
Computation of one row which covers most of combinations
:return: new solution row
"""
is_valid_row = False
while not is_valid_row:
possible_parameters = list(self.combination_matrix.uncovered_rows)
row = [-1] * len(self.data)
while len(possible_parameters) != 0:
# finding uncovered combination
combination_parameters_index = random.randint(0, len(possible_parameters) - 1)
combination_parameters = possible_parameters[combination_parameters_index]
del possible_parameters[combination_parameters_index]
combination_row = self.combination_matrix.get_row(combination_parameters)
possible_combinations = list(combination_row.get_all_uncovered_combinations())
combination_index = random.randint(0, len(possible_combinations) - 1)
combination = possible_combinations[combination_index]
is_parameter_used = False
# Are parameters already used in row?
for i in combination_parameters:
if row[i] != -1:
is_parameter_used = True
break
if is_parameter_used:
continue
row_copy = row.copy()
# Is combination matches the constraints?
for index, parameter in enumerate(combination_parameters):
row_copy[parameter] = combination[index]
if self.combination_matrix.is_valid_solution(row_copy):
row = row_copy
# Filling in of free spaces inside the row
for index, r in enumerate(row):
if r == -1:
is_valid = False
while not is_valid:
row[index] = random.randint(0, self.data[index] - 1)
is_valid = self.combination_matrix.is_valid_solution(row)
is_valid_row = self.combination_matrix.is_valid_solution(row)
return row
def cover_missing_combination(self, matrix):
"""
Randomly finds one missing combination. This combination puts into each
row of the matrix. The row with the best coverage is the solution
:param matrix: matrix to be changed
:return: solution, index of solution inside matrix and parameters which has been changed
"""
parameters, combination = self.get_missing_combination_random()
best_uncover = float("inf")
best_solution = []
best_row_index = 0
for row_index in range(len(matrix)):
solution = [x for x in matrix[row_index]]
for index, item in enumerate(parameters):
solution[item] = combination[index]
if self.combination_matrix.is_valid_combination(solution, parameters):
self.combination_matrix.uncover_combination(matrix[row_index], parameters)
self.combination_matrix.cover_combination(solution, parameters)
if self.combination_matrix.total_uncovered < best_uncover:
best_uncover = self.combination_matrix.total_uncovered
best_solution = solution
best_row_index = row_index
self.combination_matrix.uncover_combination(solution, parameters)
self.combination_matrix.cover_combination(matrix[row_index], parameters)
if best_uncover == 0:
break
if len(best_solution) == 0:
return self.change_one_column(matrix)
return best_solution, best_row_index, parameters
def get_missing_combination_random(self):
"""
Randomly finds one missing combination.
:return: parameter of combination and values of combination
"""
possible_parameters = list(self.combination_matrix.uncovered_rows)
combination_parameters_index = random.randint(0, len(possible_parameters) - 1)
combination_parameters = possible_parameters[combination_parameters_index]
combination_row = self.combination_matrix.get_row(combination_parameters)
possible_combinations = list(combination_row.get_all_uncovered_combinations())
combination_index = random.randint(0, len(possible_combinations) - 1)
combination = possible_combinations[combination_index]
return combination_parameters, combination
def change_one_column(self, matrix):
"""
Randomly choose one column of the matrix. In each cell of this column
changes value. The row with the best coverage is the solution.
:param matrix: matrix to be changed
:return: solution, index of solution inside matrix and parameters which has been changed
"""
column_index = random.randint(0, len(self.data) - 1)
best_uncover = float("inf")
best_solution = []
best_row_index = 0
for row_index in range(len(matrix)):
solution, row_index, parameters = self.change_one_value(matrix, row_index, column_index)
self.combination_matrix.uncover_combination(matrix[row_index], parameters)
self.combination_matrix.cover_combination(solution, parameters)
if self.combination_matrix.total_uncovered < best_uncover:
best_uncover = self.combination_matrix.total_uncovered
best_solution = solution
best_row_index = row_index
self.combination_matrix.uncover_combination(solution, parameters)
self.combination_matrix.cover_combination(matrix[row_index], parameters)
if best_uncover == 0:
break
return best_solution, best_row_index, [column_index]
def change_one_value(self, matrix, row_index=None, column_index=None):
"""
Change one cell inside the matrix
:param matrix: matrix to be changed
:param row_index: row inside matrix. If it's None it is chosen randomly
:param column_index: column inside matrix. If it's None it is chosen randomly
:return: solution, index of solution inside matrix and parameters which has been changed
"""
if row_index is None:
row_index = random.randint(0, len(matrix) - 1)
row = [x for x in matrix[row_index]]
if column_index is None:
column_index = random.randint(0, len(row) - 1)
possible_numbers = list(range(0, row[column_index])) + list(
range(row[column_index] + 1, self.data[column_index]))
row[column_index] = random.choice(possible_numbers)
while not self.combination_matrix.is_valid_combination(row, [column_index]):
possible_numbers.remove(row[column_index])
if len(possible_numbers) == 0:
column_index = random.randint(0, len(row) - 1)
row_index = random.randint(0, len(matrix) - 1)
row = [x for x in matrix[row_index]]
possible_numbers = list(range(0, row[column_index])) + list(
range(row[column_index] + 1, self.data[column_index]))
row[column_index] = random.choice(possible_numbers)
return row, row_index, [column_index]
def compute_row_using_hamming_distance(self):
"""
:return: row with the biggest hamming distance from final matrix
"""
row_1 = self.create_random_row_with_constraints()
row_2 = self.create_random_row_with_constraints()
if self.compute_hamming_distance(row_1) >= self.compute_hamming_distance(row_2):
return row_1
else:
return row_2
def compute_hamming_distance(self, row):
"""
:return: hamming distance of row from final matrix
"""
distance = 0
for final_row in self.final_matrix:
for index, cell in enumerate(final_row):
if row[index] != cell:
distance += 1
return distance
def create_random_row_with_constraints(self):
row = []
data_matrix = []
for parameter in self.data:
data_matrix.append(list(range(0, parameter)))
# delete the forbidden values ​by constraints
self.solver.clean_data_matrix(data_matrix)
for parameter, possible_values in enumerate(data_matrix):
value_choice = random.choice(possible_values)
self.solver.clean_data_matrix(data_matrix, {"name": parameter, "value": value_choice})
row.append(value_choice)
return row
import itertools
from .CombinationRow import CombinationRow as Row
class CombinationMatrix:
"""
CombinationMatrix object stores Rows of combinations into dictionary.
And also stores which rows are not covered. Keys in dictionary are parameters
of combinations and values are CombinationRow objects. CombinationMatrix object
has information about how many combinations are uncovered and how many of them
are covered more than ones.
"""
def __init__(self, input_data, t_value):
"""
:param input_data: list of data from user
:param t_value: t number from user
"""
self.hash_table = {}
self.uncovered_rows = {}
self.total_uncovered = 0
self.total_covered_more_than_ones = 0
# Creation of rows
for c in itertools.combinations(range(len(input_data)), t_value):
row = Row(input_data, t_value, c)
self.total_uncovered += row.uncovered
self.hash_table[c] = row
self.uncovered_rows[c] = c
def cover_solution_row(self, row):
"""
Cover all combination by one row from possible solution
:param row: one row from solution
:return: number of still uncovered combinations
"""
for key, value in self.hash_table.items():
val = []
# Getting combination from solution
for i in key:
val.append(row[i])
uncovered_difference, covered_more_than_ones_difference = value.cover_cell(val)
# Deleting covered row from uncovered rows
if value.uncovered == 0:
self.uncovered_rows.pop(key, None)
self.total_uncovered += uncovered_difference
self.total_covered_more_than_ones += covered_more_than_ones_difference
return self.total_uncovered
def cover_combination(self, row, parameters):
"""
Cover combination of specific parameters by one row from possible solution
:param row: one row from solution
:param parameters: parameters which has to be covered
:return: number of still uncovered combinations
"""
for key, value in self.hash_table.items():
for parameter in parameters:
if parameter in key:
val = []
# Getting combination from solution
for i in key:
val.append(row[i])
uncovered_difference, covered_more_than_ones_difference = value.cover_cell(val)
# Deleting covered row from uncovered rows
if value.uncovered == 0:
self.uncovered_rows.pop(key, None)
self.total_uncovered += uncovered_difference
self.total_covered_more_than_ones += covered_more_than_ones_difference
break
return self.total_uncovered
def uncover_solution_row(self, row):
"""
Uncover all combination by one row from possible solution
:param row: one row from solution
:return: number of uncovered combinations
"""
for key, value in self.hash_table.items():
val = []
# Getting combination from solution
for i in key:
val.append(row[i])
uncovered_difference, covered_more_than_ones_difference = value.uncover_cell(val)
# Adding uncovered row to uncovered rows
if value.uncovered != 0:
self.uncovered_rows[key] = key
self.total_uncovered += uncovered_difference
self.total_covered_more_than_ones += covered_more_than_ones_difference
return self.total_uncovered
def uncover_combination(self, row, parameters):
"""
Uncover combination of specific parameters by one row from possible solution
:param row: one row from solution
:param parameters: parameters which has to be covered
:return: number of uncovered combinations
"""
for key, value in self.hash_table.items():
for parameter in parameters:
if parameter in key:
val = []
# Getting combination from solution
for i in key:
val.append(row[i])
uncovered_difference, covered_more_than_ones_difference = value.uncover_cell(val)
# Adding uncovered row to uncovered rows
if value.uncovered != 0:
self.uncovered_rows[key] = key
self.total_uncovered += uncovered_difference
self.total_covered_more_than_ones += covered_more_than_ones_difference
break
return self.total_uncovered
def uncover(self):
"""
Uncover all combinations
"""
self.total_covered_more_than_ones = 0
self.total_uncovered = 0
for _, value in self.hash_table.items():
value.completely_uncover()
self.total_uncovered += value.uncovered
def is_valid_solution(self, row):
"""
Is the solution row match the constraints.
:param row: one row from solution
"""
for key, value in self.hash_table.items():
val = []
for i in key:
val.append(row[i])
if not value.is_valid(val):
return False
return True
def is_valid_combination(self, row, parameters):
"""
Is the specific parameters from solution row match the constraints.
:param row: one row from solution
:param parameters: parameters from row
"""
for key, value in self.hash_table.items():
for parameter in parameters:
if parameter in key:
val = []
for i in key:
val.append(row[i])
if not value.is_valid(val):
return False
break
return True
def del_cell(self, parameters, combination):
"""
Disable one combination. If combination is disabled it means that
the combination does not match the constraints
:param parameters: parameters whose combination is disabled
:param combination: combination to be disabled
"""
row = self.hash_table[tuple(parameters)]
uncovered_difference = row.del_cell(combination)
if row.uncovered == 0:
self.uncovered_rows.pop(tuple(parameters), None)
self.total_uncovered += uncovered_difference
def get_row(self, key):
"""
:param key: identifier of row
:return: CombinationRow
"""
return self.hash_table[tuple(key)]
def __eq__(self, other):
return (self.total_uncovered == other.total_uncovered and
self.total_covered_more_than_ones == other.total_covered_more_than_ones and
self.hash_table == other.hash_table)
import itertools
class CombinationRow:
"""
Row object store all combinations between two parameters into dictionary.
Keys in dictionary are values of combinations and values in dictionary are
information about coverage. Row object has information how many combinations
are ucovered and how many of them are covered more than ones.
"""
def __init__(self, input_data, t_value, parameters):
"""
:param input_data: list of data from user
:param t_value: t number from user
:param parameters: the tuple of parameters whose combinations Row object represents
"""
self.hash_table = {}
self.covered_more_than_ones = 0
self.uncovered = 0
array = []
"Creation of combinations"
for i in range(t_value):
array.append(range(input_data[parameters[i]]))
for i in itertools.product(*array):
self.uncovered += 1
self.hash_table[i] = 0
def cover_cell(self, key):
"""
Cover one combination inside Row
:param key: combination to be covered
:return: number of new covered combinations and number of new covered combinations more than ones
"""
old_uncovered = self.uncovered
old_covered_more_than_ones = self.covered_more_than_ones
value = self.hash_table[tuple(key)]
if value is not None:
if value == 0:
self.uncovered -= 1
elif value == 1:
self.covered_more_than_ones += 1
self.hash_table[tuple(key)] += 1
return self.uncovered - old_uncovered, self.covered_more_than_ones - old_covered_more_than_ones
def uncover_cell(self, key):
"""
Uncover one combination inside Row
:param key: combination to be uncovered
:return: number of new covered combinations and number of new covered combinations more than ones
"""
old_uncovered = self.uncovered
old_covered_more_than_ones = self.covered_more_than_ones
value = self.hash_table[tuple(key)]
if value is not None and value > 0:
if value == 1:
self.uncovered += 1
elif value == 2:
self.covered_more_than_ones -= 1
self.hash_table[tuple(key)] -= 1
return self.uncovered - old_uncovered, self.covered_more_than_ones - old_covered_more_than_ones
def completely_uncover(self):
"""
Uncover all combinations inside Row
"""
self.uncovered = 0
self.covered_more_than_ones = 0
for key in self.hash_table:
if self.hash_table[key] is not None:
self.hash_table[key] = 0
self.uncovered += 1
def del_cell(self, key):
"""
Disable one combination. If combination is disabled it means that
the combination does not match the constraints
:param key: combination to be disabled
:return: number of new covered combinations
"""
key = tuple(key)
if self.hash_table[key] is not None:
self.hash_table[key] = None
self.uncovered -= 1
return -1
else:
return 0
def is_valid(self, key):
"""
Is the combination match the constraints.
:param key: combination to valid
"""
key = tuple(key)
if self.hash_table.get(key, 0) is None:
return False
else:
return True
def get_all_uncovered_combinations(self):
"""
:return: list of all uncovered combination
"""
combinations = []
for key, value in self.hash_table.items():
if value == 0:
combinations.append(key)
return combinations
def __eq__(self, other):
return (self.covered_more_than_ones == other.covered_more_than_ones and self.uncovered == other.uncovered and
self.hash_table == other.hash_table)
class Pair:
def __init__(self, name, value):
self.name = name
self.value = value
def __str__(self):
return str(self.name) + " != " + str(self.value)
def __eq__(self, other):
return self.name == other.name and self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.__str__())
class Parameter:
def __init__(self, name, parameter_id, values):
self.is_full = False
self.name = name
self.id = parameter_id
self.values = values
self.constrained_values = set()
self.constraints = {}
for i in range(len(values)):
self.constraints[i] = []
def add_constraint(self, constraint):
array = []
value = None
for pair in constraint:
if pair.name == self.name:
self.constrained_values.add(pair.value)
value = pair.value
else:
array.append(pair)
if len(self.constrained_values) == len(self.values):
self.is_full = True
if len(array) != 0:
self.constraints[value].append(array)
def get_constraints(self):
array = []
for key in self.constraints:
array.append(self.constraints[key])
return array
def get_value_index(self, value):
return self.values.index(value)
def get_size(self):
return len(self.values)
from .Parameter import Parameter
from .Parameter import Pair
import re
class Parser:
@staticmethod
def parse(file_object):
"""
Parsing of input file with parameters and constraints
:param file_object: input file for parsing
:return: array of parameters and set of constraints
"""
rx_parameter_name = re.compile(r"(.+?)\[")
rx_parameter_value = re.compile(r"\[(.+?)\]")
rx_constraint = re.compile(r".+?!=.+?")
parameters = []
constraints = set()
is_parameters = None
for line in file_object:
line = line.strip()
if not line:
continue
if line == "PARAMETERS":
is_parameters = True
continue
if line == "CONSTRAINTS":
is_parameters = False
continue
if is_parameters is None:
raise ValueError("Invalid file")
if is_parameters:
# Searching for parameter name
match = rx_parameter_name.search(line)
if match is None:
raise ValueError('Parameter name is missing')
parameter_name = match.group(1).strip()
# Searching for parameter_values
match = rx_parameter_value.search(line)
if match is None:
raise ValueError('Parameter values are missing')
parameter_values = [x.strip() for x in match.group(1).split(',')]
if len(parameter_values) != len(set(parameter_values)):
raise ValueError('Parameter values has duplicities')
parameters.append(Parameter(parameter_name, len(parameters), parameter_values))
else:
constraints_data = line.split("||")
array = []
for constraint in constraints_data:
value_index = 1
if not rx_constraint.match(constraint):
raise ValueError("Invalid format of constraint")
constraint_data = [x.strip() for x in constraint.split("!=")]
# Searching for parameter name in constraint
constraint_name = next((p.id for p in parameters if p.name == constraint_data[0]), None)
if constraint_name is None:
# If first value inside constraint wasn't parameter name try second value
try:
constraint_name = next((p.id for p in parameters if p.name == constraint_data[1]))
value_index = 0
except StopIteration:
raise ValueError('Name in constraint not match with names in parameters')
try:
# Checking if value in constraint is matching with parameter values
constraint_value = parameters[constraint_name].get_value_index(constraint_data[value_index])
except ValueError:
raise ValueError('Value in constraint not match with values in parameters')
array.append(Pair(constraint_name, constraint_value))
array.sort(key=lambda x: int(x.name))
constraints.add(tuple(array))
return parameters, constraints
import itertools
from .Parameter import Parameter
class Solver:
OR = "||"
EQUALS = "="
PARAMETER = 0
VALUE = 2
def __init__(self, data, constraints):
self.data = data
self.constraints = constraints
self.parameters = []
self.simplify_constraints()
constraint_size = len(self.constraints)
self.read_constraints()
self.compute_constraints()
self.simplify_constraints()
while constraint_size != len(self.constraints):
constraint_size = len(self.constraints)
self.parameters = []
self.read_constraints()
self.compute_constraints()
self.simplify_constraints()
def read_constraints(self):
# creates new parameters with their names
for i, values_size in enumerate(self.data):
self.parameters.append(Parameter(i, i, range(values_size)))
for constraint in self.constraints:
for pair in constraint:
self.parameters[pair.name].add_constraint(constraint)
def compute_constraints(self):
for p in self.parameters:
if p.is_full:
array = p.get_constraints()
con = list(itertools.product(*array))
if len(con) == 0:
raise ValueError("Constraints are not satisfiable")
for constraint in con:
constraint_array = set()
for c in range(len(constraint)):
for pair in range(len(constraint[c])):
constraint_array.add(constraint[c][pair])
constraint_array = sorted(constraint_array, key=lambda x: int(x.name))
has_subset = False
remove = set()
for c in self.constraints:
if len(c) < len(constraint_array):
if set(c) < set(constraint_array):
has_subset = True
break
if len(c) > len(constraint_array):
if set(c) > set(constraint_array):
remove.add(c)
if not has_subset:
self.constraints.add(tuple(constraint_array))
for r in remove:
self.constraints.remove(r)
def simplify_constraints(self):
items_to_remove = set()
copy = list(self.constraints.copy())
for i in range(len(copy)):
is_brake = False
for j in range(len(copy[i])):
for k in range(j + 1, len(copy[i])):
if copy[i][j].name == copy[i][k].name:
items_to_remove.add(copy[i])
is_brake = True
break
if is_brake:
break
if is_brake:
continue
for j in range(len(copy)):
if j != i:
if len(copy[i]) < len(copy[j]):
if set(copy[i]).issubset(set(copy[j])):
items_to_remove.add(copy[j])
for item in items_to_remove:
self.constraints.remove(item)
def clean_hash_table(self, combination_matrix, t_value):
for constraint in self.constraints:
if len(constraint) > t_value:
continue
parameters_in_constraint = []
for pair in constraint:
parameters_in_constraint.append(pair.name)
for c in itertools.combinations(range(len(self.data)), t_value):
if set(parameters_in_constraint).issubset(c):
value_array = []
counter = 0
for value in c:
if value == constraint[counter].name:
value_array.append([constraint[counter].value])
if (counter + 1) != len(constraint):
counter += 1
else:
value_array.append(list(range(0, self.data[value])))
for key in itertools.product(*value_array):
combination_matrix.del_cell(c, key)
def clean_data_matrix(self, data_matrix, parameter=None):
if parameter is None:
for constraint in self.constraints:
if len(constraint) == 1:
constraint = constraint[0]
data_matrix[constraint.name].remove(constraint.value)
else:
parameter_constraint = self.parameters[parameter["name"]].constraints[parameter["value"]]
for constraint in parameter_constraint:
constraint = constraint[0]
try:
data_matrix[constraint.name].remove(constraint.value)
except ValueError:
# this value was already deleted
pass
......@@ -12,12 +12,9 @@
# Bestoun S. Ahmed <bestoon82@gmail.com>
# Cleber Rosa <crosa@redhat.com>
import configparser
import copy
import itertools
import os
import random
import sys
import logging
from avocado.core import exit_codes
from avocado.core import varianter
......@@ -25,6 +22,8 @@ from avocado.core.output import LOG_UI
from avocado.core.plugin_interfaces import CLI
from avocado.core.plugin_interfaces import Varianter
from avocado.core.tree import TreeNode
from avocado_varianter_cit.Cit import Cit, LOG
from avocado_varianter_cit.Parser import Parser
class VarianterCitCLI(CLI):
......@@ -48,11 +47,11 @@ class VarianterCitCLI(CLI):
cit.add_argument('--cit-order-of-combinations',
metavar='ORDER', type=int, default=2,
help=("Order of combinations. Defaults to "
"%(default)s, maximum number is specific "
"to parameter file content"))
"%(default)s, maximum number is 6"))
def run(self, args):
pass
if getattr(args, "varianter_debug", False):
LOG.setLevel(logging.DEBUG)
class VarianterCit(Varianter):
......@@ -66,6 +65,10 @@ class VarianterCit(Varianter):
def initialize(self, args):
self.variants = None
order = args.cit_order_of_combinations
if order > 6:
LOG_UI.error("The order of combinations is bigger then 6")
self.error_exit(args)
cit_parameter_file = getattr(args, "cit_parameter_file", None)
if cit_parameter_file is None:
......@@ -77,18 +80,22 @@ class VarianterCit(Varianter):
"is not readable", cit_parameter_file)
self.error_exit(args)
config = configparser.ConfigParser()
try:
config.read(cit_parameter_file)
parameters, constraints = Parser.parse(open(cit_parameter_file))
except Exception as details:
LOG_UI.error("Cannot parse parameter file: %s", details)
self.error_exit(args)
parameters = [(key, value.split(', '))
for key, value in config.items('parameters')]
order = args.cit_order_of_combinations
cit = Cit(parameters, order)
self.headers, self.variants = cit.combine()
input_data = [parameter.get_size() for parameter in parameters]
cit = Cit(input_data, order, constraints)
final_list = cit.compute()
self.headers = [parameter.name for parameter in parameters]
results = [[parameters[j].values[final_list[i][j]] for j in range(len(final_list[i]))]
for i in range(len(final_list))]
self.variants = []
for combination in results:
self.variants.append(dict(zip(self.headers, combination)))
@staticmethod
def error_exit(args):
......@@ -140,163 +147,3 @@ class VarianterCit(Varianter):
out.extend(varianter.variant_to_str(variant, variants - 1,
kwargs, False))
return "\n".join(out)
class Cit:
MATRIX_ROW_SIZE = 20
MAX_ITERATIONS = 15
def __init__(self, parameters, order):
# Parameters come as ('key', ['value1', 'value2', 'value3'])
self.parameters = parameters
# Length (number of values) for each parameter
self.parameters_length = [len(param[1]) for param in self.parameters]
# Order of combinations
self.order = min(order, len(parameters))
self.hash_table = {}
def combine(self):
"""
Computes the combination of parameters
:returns: headers (list of parameters keys) and combinations (list of
dictionaries. Each dictionary represents a combination of
parameters.
:rtype: tuple
"""
self.create_interaction_hash_table()
final_list = self.create_final_list()
headers = [item[0] for item in self.parameters]
result = [[self.parameters[i][1][combination[i]]
for i in range(len(combination))]
for combination in final_list]
combinations = []
for combination in result:
combinations.append(dict(zip(headers, combination)))
return headers, combinations
def create_interaction_hash_table(self):
for c in itertools.combinations(range(len(self.parameters_length)), self.order):
self.hash_table[c] = self.get_iteration(c)
def create_final_list(self):
final_list = []
while len(self.hash_table) != 0:
iterations = 0
previous_test_case = []
previous_remove_list = {}
previous_weight = 0
while iterations < self.MAX_ITERATIONS:
max_width = len(self.hash_table)
matrix = self.create_random_matrix()
remove_list = {}
for i in matrix:
width = self.get_weight(i, remove_list)
if width == 0 or width <= previous_weight:
remove_list.clear()
continue
elif width == max_width:
final_list.append(i)
self.remove_from_hash_table(remove_list)
previous_test_case = []
previous_remove_list = {}
continue
elif width > previous_weight:
previous_weight = width
previous_test_case = i
previous_remove_list = dict(remove_list)
remove_list.clear()
iterations += 1
if len(previous_test_case) != 0:
previous_remove_list = self.neighborhood_search(
previous_test_case, previous_weight,
max_width, previous_remove_list)
final_list.append(previous_test_case)
self.remove_from_hash_table(previous_remove_list)
return final_list
def get_iteration(self, parameter_combination):
parameters_array = []
for c in parameter_combination:
array = range(self.parameters_length[c])
parameters_array.append(array)
iterations = {}
for i in itertools.product(*parameters_array):
iterations[i] = tuple(i)
return iterations
def get_weight(self, test_case, remove_list):
weight = 0
for i in self.hash_table:
iteration = tuple(test_case[j] for j in i)
try:
value = self.hash_table[i][iteration]
weight += 1
remove_list[i] = value
except KeyError:
continue
return weight
def remove_from_hash_table(self, remove_list):
for i in remove_list:
del self.hash_table[i][remove_list[i]]
if len(self.hash_table[i]) == 0:
self.hash_table.pop(i)
remove_list.clear()
def create_random_matrix(self):
matrix = []
for _ in range(self.MATRIX_ROW_SIZE):
row = []
for j in self.parameters_length:
row.append(random.randint(0, j-1))
matrix.append(row)
return matrix
def neighborhood_search(self, test_case, width, max_width, remove_list):
neighborhood = list(test_case)
neighborhood_remove_list = {}
for i in range(len(test_case)):
# neighborhood +1
if (neighborhood[i] + 1) == self.parameters_length[i]:
neighborhood[i] = 0
else:
neighborhood[i] += 1
neighborhood_width = self.get_weight(neighborhood, neighborhood_remove_list)
if neighborhood_width > width:
width = neighborhood_width
remove_list = copy.deepcopy(neighborhood_remove_list)
del test_case[:]
for j in neighborhood:
test_case.append(j)
if neighborhood_width == max_width:
return remove_list
if neighborhood[i] == 0:
neighborhood[i] = self.parameters_length[i] - 1
else:
neighborhood[i] -= 1
# neighborhood -1
if neighborhood[i] == 0:
neighborhood[i] = self.parameters_length[i] - 1
else:
neighborhood[i] -= 1
neighborhood_width = self.get_weight(neighborhood, neighborhood_remove_list)
if neighborhood_width > width:
width = neighborhood_width
remove_list = copy.deepcopy(neighborhood_remove_list)
del test_case[:]
for j in neighborhood:
test_case.append(j)
if neighborhood_width == max_width:
return remove_list
if (neighborhood[i] + 1) == self.parameters_length[i]:
neighborhood[i] = 0
else:
neighborhood[i] += 1
return remove_list
......@@ -8,18 +8,23 @@ from avocado.utils import process
from selftests import AVOCADO, BASEDIR, temp_dir_prefix
class Variants(unittest.TestCase):
class Basic(unittest.TestCase):
def test_max_variants(self):
os.chdir(BASEDIR)
params_path = os.path.join(BASEDIR, 'examples',
'varianter_cit', 'test_params.cit')
cmd_line = (
'{0} variants --cit-order-of-combinations=5 '
'--cit-parameter-file examples/varianter_cit/params.ini'
).format(AVOCADO)
'{0} variants --cit-order-of-combinations=2 '
'--cit-parameter-file {1}'
).format(AVOCADO, params_path)
result = process.run(cmd_line)
lines = result.stdout.splitlines()
self.assertEqual(b'CIT Variants (216):', lines[0])
self.assertEqual(217, len(lines))
self.assertEqual(b'CIT Variants (9):', lines[0])
self.assertEqual(10, len(lines))
for i in range(1, len(lines)):
with self.subTest(combination=lines[i]):
self.assertIn(b"green", lines[i])
class Run(unittest.TestCase):
......@@ -31,7 +36,7 @@ class Run(unittest.TestCase):
def test(self):
os.chdir(BASEDIR)
params_path = os.path.join(BASEDIR, 'examples',
'varianter_cit', 'params.ini')
'varianter_cit', 'test_params.cit')
test_path = os.path.join(BASEDIR, 'examples',
'tests', 'cit_parameters.py')
cmd_line = (
......@@ -41,13 +46,7 @@ class Run(unittest.TestCase):
'-- {3}'
).format(AVOCADO, self.tmpdir, params_path, test_path)
result = process.run(cmd_line)
# all values of colors should be looked for at least once
self.assertIn(b"PARAMS (key=color, path=*, default=None) => 'black'",
result.stdout)
self.assertIn(b"PARAMS (key=color, path=*, default=None) => 'gold'",
result.stdout)
self.assertIn(b"PARAMS (key=color, path=*, default=None) => 'red'",
result.stdout)
# all values should be looked for at least once
self.assertIn(b"PARAMS (key=color, path=*, default=None) => 'green'",
result.stdout)
# all values of shape should be looked for at least once
......
import random
import unittest
from copy import copy
from avocado_varianter_cit.Cit import Cit
from avocado_varianter_cit.CombinationMatrix import CombinationMatrix
from avocado_varianter_cit.Parameter import Pair
from avocado_varianter_cit.Solver import Solver
class CitInitialization(unittest.TestCase):
def test_initialization(self):
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1), Pair(2, 0)), (Pair(0, 2), Pair(3, 2))}
t_value = 2
solver = Solver(parameters, constraints)
combination_matrix = CombinationMatrix(parameters, t_value)
solver.clean_hash_table(combination_matrix, t_value)
cit = Cit(parameters, t_value, constraints)
self.assertEqual(combination_matrix, cit.combination_matrix, "The initialization of cit algorithm is wrong")
class CitTests(unittest.TestCase):
def setUp(self):
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1), Pair(2, 0)), (Pair(0, 2), Pair(3, 2))}
t_value = 2
self.cit = Cit(parameters, t_value, constraints)
def test_create_random_row_with_constraints(self):
for _ in range(0, 10):
row = self.cit.create_random_row_with_constraints()
with self.subTest(random_row=row):
self.assertTrue(self.cit.combination_matrix.is_valid_solution(row), "New random row is not valid")
def test_compute_hamming_distance(self):
self.cit.final_matrix.append([1, 0, 1, 2])
self.cit.final_matrix.append([2, 1, 1, 0])
row = [2, 0, 3, 2]
expected_distance = 5
self.assertEqual(expected_distance, self.cit.compute_hamming_distance(row), "Wrong hamming distance")
def test_final_matrix_init(self):
combination_matrix = copy(self.cit.combination_matrix)
final_matrix = self.cit.final_matrix_init()
expected_total_uncovered = 0
expected_uncovered_rows = {}
self.assertEqual(expected_total_uncovered, self.cit.combination_matrix.total_uncovered,
"Final matrix don't cover all combinations")
self.assertEqual(expected_uncovered_rows, self.cit.combination_matrix.uncovered_rows,
"Final matrix don't cover all combination rows")
for row in final_matrix:
combination_matrix.cover_solution_row(row)
self.assertEqual(expected_total_uncovered, self.cit.combination_matrix.total_uncovered,
"Final matrix don't cover all combinations but CIT thinks it does")
self.assertEqual(expected_uncovered_rows, self.cit.combination_matrix.uncovered_rows,
"Final matrix don't cover all combination rows but CIT thinks it does")
def test_change_one_value_random(self):
final_matrix = self.cit.final_matrix_init()
row, row_index, column_index = self.cit.change_one_value(final_matrix)
self.assertNotEqual(final_matrix[row_index][column_index[0]], row[column_index[0]], "Value did not change")
row[column_index[0]] = final_matrix[row_index][column_index[0]]
self.assertEqual(final_matrix[row_index], row, "Different value was changed")
def test_change_one_value_with_index(self):
final_matrix = self.cit.final_matrix_init()
expected_row_index = 2
expected_column_index = 0
row, row_index, column_index = self.cit.change_one_value(final_matrix, row_index=expected_row_index,
column_index=expected_column_index)
self.assertEqual(expected_column_index, column_index[0], "Column index is wrong")
self.assertEqual(expected_row_index, row_index, "Row index is wrong")
self.assertNotEqual(final_matrix[row_index][column_index[0]], row[column_index[0]], "Value did not change")
row[column_index[0]] = final_matrix[row_index][column_index[0]]
self.assertEqual(final_matrix[row_index], row, "Different value was changed")
def test_change_one_column(self):
final_matrix = self.cit.final_matrix_init()
while self.cit.combination_matrix.total_uncovered == 0:
delete_row = final_matrix.pop(random.randint(0, len(final_matrix) - 1))
self.cit.combination_matrix.uncover_solution_row(delete_row)
expected_total_covered_more_than_ones = self.cit.combination_matrix.total_covered_more_than_ones
expected_total_uncovered = self.cit.combination_matrix.total_uncovered
expected_uncovered_rows = copy(self.cit.combination_matrix.uncovered_rows)
row, row_index, column_index = self.cit.change_one_column(final_matrix)
self.assertEqual(expected_total_uncovered, self.cit.combination_matrix.total_uncovered, "Coverage was change")
self.assertEqual(expected_total_covered_more_than_ones,
self.cit.combination_matrix.total_covered_more_than_ones,
"Coverage was change")
self.assertEqual(expected_uncovered_rows, self.cit.combination_matrix.uncovered_rows, "Coverage was change")
self.assertNotEqual(final_matrix[row_index][column_index[0]], row[column_index[0]], "Value did not change")
row[column_index[0]] = final_matrix[row_index][column_index[0]]
self.assertEqual(final_matrix[row_index], row, "Different value was changed")
def test_get_missing_combination_random(self):
final_matrix = self.cit.final_matrix_init()
while self.cit.combination_matrix.total_uncovered == 0:
delete_row = final_matrix.pop(random.randint(0, len(final_matrix) - 1))
self.cit.combination_matrix.uncover_solution_row(delete_row)
combination_parameters, combination = self.cit.get_missing_combination_random()
self.assertEqual(0, self.cit.combination_matrix.hash_table[combination_parameters].hash_table[combination],
"Combination is already covered")
def test_cover_missing_combination(self):
final_matrix = self.cit.final_matrix_init()
while self.cit.combination_matrix.total_uncovered == 0:
delete_row = final_matrix.pop(random.randint(0, len(final_matrix) - 1))
self.cit.combination_matrix.uncover_solution_row(delete_row)
expected_total_covered_more_than_ones = self.cit.combination_matrix.total_covered_more_than_ones
expected_total_uncovered = self.cit.combination_matrix.total_uncovered
expected_uncovered_rows = copy(self.cit.combination_matrix.uncovered_rows)
row, row_index, parameters = self.cit.cover_missing_combination(final_matrix)
self.assertEqual(expected_total_uncovered, self.cit.combination_matrix.total_uncovered, "Coverage was change")
self.assertEqual(expected_total_covered_more_than_ones,
self.cit.combination_matrix.total_covered_more_than_ones,
"Coverage was change")
self.assertEqual(expected_uncovered_rows, self.cit.combination_matrix.uncovered_rows, "Coverage was change")
self.assertTrue(final_matrix[row_index][parameters[0]] != row[parameters[0]] or
final_matrix[row_index][parameters[1]] != row[parameters[1]], "Value did not change")
row[parameters[0]] = final_matrix[row_index][parameters[0]]
row[parameters[1]] = final_matrix[row_index][parameters[1]]
self.assertEqual(final_matrix[row_index], row, "Different value was changed")
import unittest
from avocado_varianter_cit.CombinationRow import CombinationRow
class RowInitialization(unittest.TestCase):
def test_combination_row_initialization(self):
"""
Test of proper initialization
"""
data = [3, 3, 3, 4]
parameters = (1, 3)
t_value = 2
row = CombinationRow(data, t_value, parameters)
excepted_uncovered = 12
excepted_covered_more_than_ones = 0
excepted_hash_table = {(0, 0): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0,
(1, 0): 0, (1, 1): 0, (1, 2): 0, (1, 3): 0,
(2, 0): 0, (2, 1): 0, (2, 2): 0, (2, 3): 0}
self.assertEqual(row.uncovered, excepted_uncovered, "Uncovered number is wrong.")
self.assertEqual(row.covered_more_than_ones, excepted_covered_more_than_ones,
"Covered_more_than_ones number is wrong.")
self.assertEqual(row.hash_table, excepted_hash_table, "Hash table is wrong.")
class CombinationRowTest(unittest.TestCase):
def setUp(self):
self.data = [3, 3, 3, 4]
self.parameters = (1, 3)
self.t_value = 2
self.row = CombinationRow(self.data, self.t_value, self.parameters)
# Tests of cover_cell function
def test_cover_cell_uncovered_value(self):
self.assertEqual(self.row.cover_cell((0, 0)), (-1, 0), "cover_cell return wrong values")
self.assertEqual(self.row.uncovered, 11, "cover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "cover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], 1, "cover_cell don't cover values")
def test_cover_cell_covered_value(self):
self.row.hash_table[(0, 0)] = 1
self.row.uncovered = 11
self.assertEqual(self.row.cover_cell((0, 0)), (0, 1), "cover_cell return wrong values")
self.assertEqual(self.row.uncovered, 11, "cover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 1, "cover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], 2, "cover_cell don't cover values")
def test_cover_cell_cover_disabled_value(self):
self.row.hash_table[(0, 0)] = None
self.assertEqual(self.row.cover_cell((0, 0)), (0, 0), "cover_cell return wrong values")
self.assertEqual(self.row.uncovered, 12, "cover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "cover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], None, "cover_cell change disabled value")
# Tests of uncover_cell function
def test_uncover_cell_uncovered_value(self):
self.assertEqual(self.row.uncover_cell((0, 0)), (0, 0), "uncover_cell return wrong values")
self.assertEqual(self.row.uncovered, 12, "uncover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "uncover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], 0, "uncover_cell change uncovered value")
def test_uncover_cell_covered_value(self):
self.row.hash_table[(0, 0)] = 1
self.row.uncovered = 11
self.assertEqual(self.row.uncover_cell((0, 0)), (1, 0), "uncover_cell return wrong values")
self.assertEqual(self.row.uncovered, 12, "uncover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "uncover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], 0, "uncover_cell change uncovered value")
def test_uncover_cell_covered_more_than_one_value(self):
self.row.hash_table[(0, 0)] = 2
self.row.uncovered = 11
self.row.covered_more_than_ones = 1
self.assertEqual(self.row.uncover_cell((0, 0)), (0, -1), "uncover_cell return wrong values")
self.assertEqual(self.row.uncovered, 11, "uncover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "uncover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], 1, "uncover_cell change uncovered value")
def test_uncover_cell_disabled_value(self):
self.row.hash_table[(0, 0)] = None
self.assertEqual(self.row.uncover_cell((0, 0)), (0, 0), "uncover_cell return wrong values")
self.assertEqual(self.row.uncovered, 12, "uncover_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "uncover_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], None, "uncover_cell change disabled value")
# Test of completely_uncover function
def test_completely_uncover(self):
self.row.hash_table[(0, 0)] = 1
self.row.hash_table[(0, 1)] = 2
self.row.hash_table[(0, 2)] = None
self.row.completely_uncover()
self.assertEqual(self.row.uncovered, 11, "completely_uncover create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0,
"completely_uncover create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], 0, "completely_uncover don't uncover value")
self.assertEqual(self.row.hash_table[(0, 1)], 0, "completely_uncover don't uncover value")
self.assertEqual(self.row.hash_table[(0, 2)], None, "completely_uncover change disabled value")
# Tests of del_cell function
def test_del_cell_uncovered_value(self):
self.assertEqual(self.row.del_cell((0, 0)), -1, "del_cell return wrong values")
self.assertEqual(self.row.uncovered, 11, "del_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "del_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], None, "del_cell don't disable value")
def test_del_cell_disabled_value(self):
self.row.hash_table[(0, 0)] = None
self.row.uncovered = 11
self.assertEqual(self.row.del_cell((0, 0)), 0, "del_cell return wrong values")
self.assertEqual(self.row.uncovered, 11, "del_cell create wrong uncovered value")
self.assertEqual(self.row.covered_more_than_ones, 0, "del_cell create wrong covered_more_than_ones value")
self.assertEqual(self.row.hash_table[(0, 0)], None, "del_cell don't disable value")
# Tests of is_valid function
def test_is_valid_valid(self):
self.assertEqual(self.row.is_valid((0, 0)), True, "is_valid return wrong values")
def test_is_valid_invalid(self):
self.row.hash_table[(0, 0)] = None
self.assertEqual(self.row.is_valid((0, 0)), False, "is_valid return wrong values")
# Test of get_all_uncovered_combinations function
def test_get_all_uncovered_combinations(self):
self.row.hash_table[(0, 0)] = None
self.row.hash_table[(0, 1)] = 1
self.row.hash_table[(0, 2)] = 2
self.row.hash_table[(0, 3)] = 3
ex = [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 0), (2, 1), (2, 2), (2, 3)]
self.assertEqual(len(set(self.row.get_all_uncovered_combinations()).intersection(ex)), len(ex))
if __name__ == '__main__':
unittest.main()
import unittest
from avocado_varianter_cit.CombinationMatrix import CombinationMatrix
from avocado_varianter_cit.CombinationRow import CombinationRow
def combination_row_equals(row_1, row_2):
return (row_1.covered_more_than_ones == row_2.covered_more_than_ones and row_1.uncovered == row_2.uncovered
and row_1.hash_table == row_2.hash_table)
class MatrixInitialization(unittest.TestCase):
def test_combination_matrix_initialization(self):
"""
Test of proper initialization
"""
data = [3, 3, 3, 4]
t_value = 2
matrix = CombinationMatrix(data, t_value)
excepted_uncovered = 63
excepted_covered_more_than_ones = 0
excepted_row_size = 6
excepted_hash_table = {(0, 1): CombinationRow(data, t_value, (0, 1)),
(0, 2): CombinationRow(data, t_value, (0, 2)),
(0, 3): CombinationRow(data, t_value, (0, 3)),
(1, 2): CombinationRow(data, t_value, (1, 2)),
(1, 3): CombinationRow(data, t_value, (1, 3)),
(2, 3): CombinationRow(data, t_value, (2, 3))}
self.assertEqual(matrix.total_uncovered, excepted_uncovered, "Total uncovered number is wrong.")
self.assertEqual(matrix.total_covered_more_than_ones, excepted_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(len(matrix.hash_table), excepted_row_size, "Matrix has wrong row size")
self.assertEqual(len(matrix.uncovered_rows), excepted_row_size, "Matrix has wrong uncovered row size")
for key in matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(matrix.hash_table[key], excepted_hash_table[key]))
class CombinationMatrixTest(unittest.TestCase):
def setUp(self):
self.data = [3, 3, 3, 4]
self.t_value = 2
self.matrix = CombinationMatrix(self.data, self.t_value)
self.excepted_hash_table = {(0, 1): CombinationRow(self.data, self.t_value, (0, 1)),
(0, 2): CombinationRow(self.data, self.t_value, (0, 2)),
(0, 3): CombinationRow(self.data, self.t_value, (0, 3)),
(1, 2): CombinationRow(self.data, self.t_value, (1, 2)),
(1, 3): CombinationRow(self.data, self.t_value, (1, 3)),
(2, 3): CombinationRow(self.data, self.t_value, (2, 3))}
def test_cover_solution_row(self):
solution_row = [1, 0, 2, 3]
excepted_uncovered = 57
excepted_covered_more_than_ones = 0
excepted_uncovered_row_size = 6
self.excepted_hash_table[0, 1].cover_cell((1, 0))
self.excepted_hash_table[0, 2].cover_cell((1, 2))
self.excepted_hash_table[0, 3].cover_cell((1, 3))
self.excepted_hash_table[1, 2].cover_cell((0, 2))
self.excepted_hash_table[1, 3].cover_cell((0, 3))
self.excepted_hash_table[2, 3].cover_cell((2, 3))
self.matrix.cover_solution_row(solution_row)
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
for key in self.matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(self.matrix.hash_table[key], self.excepted_hash_table[key]))
solution_row = [0, 0, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [0, 1, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [0, 2, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [1, 1, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [1, 2, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [2, 0, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [2, 1, 2, 3]
self.matrix.cover_solution_row(solution_row)
solution_row = [2, 2, 2, 3]
self.matrix.cover_solution_row(solution_row)
excepted_uncovered_row_size = 5
excepted_uncovered = 41
excepted_covered_more_than_ones = 13
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
def test_cover_combination(self):
solution_row = [1, 0, 2, 3]
self.matrix.cover_combination(solution_row, (1, 0))
excepted_uncovered_row_size = 6
excepted_uncovered = 58
excepted_covered_more_than_ones = 0
self.excepted_hash_table[0, 1].cover_cell((1, 0))
self.excepted_hash_table[0, 2].cover_cell((1, 2))
self.excepted_hash_table[0, 3].cover_cell((1, 3))
self.excepted_hash_table[1, 2].cover_cell((0, 2))
self.excepted_hash_table[1, 3].cover_cell((0, 3))
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
for key in self.matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(self.matrix.hash_table[key], self.excepted_hash_table[key]))
self.matrix.cover_combination(solution_row, (1, 0))
excepted_covered_more_than_ones = 5
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
def test_uncover_solution_row(self):
solution_row = [1, 0, 2, 3]
self.matrix.cover_solution_row(solution_row)
self.matrix.uncover_solution_row(solution_row)
excepted_uncovered_row_size = 6
excepted_uncovered = 63
excepted_covered_more_than_ones = 0
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
for key in self.matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(self.matrix.hash_table[key], self.excepted_hash_table[key]))
self.matrix.cover_solution_row(solution_row)
self.matrix.cover_solution_row(solution_row)
self.excepted_hash_table[0, 1].cover_cell((1, 0))
self.excepted_hash_table[0, 2].cover_cell((1, 2))
self.excepted_hash_table[0, 3].cover_cell((1, 3))
self.excepted_hash_table[1, 2].cover_cell((0, 2))
self.excepted_hash_table[1, 3].cover_cell((0, 3))
self.excepted_hash_table[2, 3].cover_cell((2, 3))
self.matrix.uncover_solution_row(solution_row)
excepted_uncovered_row_size = 6
excepted_uncovered = 57
excepted_covered_more_than_ones = 0
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
for key in self.matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(self.matrix.hash_table[key], self.excepted_hash_table[key]))
def test_uncover_combination(self):
solution_row = [1, 0, 2, 3]
self.matrix.cover_solution_row(solution_row)
excepted_uncovered_row_size = 6
excepted_uncovered = 62
excepted_covered_more_than_ones = 0
self.excepted_hash_table[2, 3].cover_cell((2, 3))
self.matrix.uncover_combination(solution_row, (0, 1))
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
for key in self.matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(self.matrix.hash_table[key], self.excepted_hash_table[key]))
def test_uncover(self):
solution_row = [1, 0, 2, 3]
self.matrix.cover_solution_row(solution_row)
self.matrix.cover_solution_row(solution_row)
excepted_uncovered_row_size = 6
excepted_uncovered = 63
excepted_covered_more_than_ones = 0
self.matrix.uncover()
self.assertEqual(excepted_uncovered, self.matrix.total_uncovered, "Total uncovered number is wrong.")
self.assertEqual(excepted_covered_more_than_ones, self.matrix.total_covered_more_than_ones,
"Total uovered_more_than_ones number is wrong.")
self.assertEqual(excepted_uncovered_row_size, len(self.matrix.uncovered_rows),
"Matrix has wrong uncovered row size")
for key in self.matrix.hash_table:
with self.subTest(combination=key):
self.assertTrue(combination_row_equals(self.matrix.hash_table[key], self.excepted_hash_table[key]))
import unittest
from avocado_varianter_cit.Solver import Solver
from avocado_varianter_cit.Parameter import Pair
class SolverTest(unittest.TestCase):
"""
Test for compute_constraints function
"""
def test_compute_constraints_without_secret_constraint(self):
"""
Test that, function shouldn't change constraints if there isn't any secret constraint
"""
parameters = [3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1)), (Pair(1, 0), Pair(2, 0))}
solver = Solver([], [])
solver.data = parameters
solver.constraints = constraints
solver.read_constraints()
solver.compute_constraints()
self.assertEqual(solver.constraints, constraints, "compute_constraints change constraints without secret "
"constraint")
def test_compute_constraints_new_constraint_with_more_than_one_value_for_one_parameter(self):
"""
Test that, function shouldn't change constraints. It founds new constraint,
but the constraint has one parameter with two values. This means that it isn't secreted constraint.
"""
parameters = [3, 3, 3]
constraints = {(Pair(0, 0), Pair(1, 0)), (Pair(0, 1), Pair(1, 1)), (Pair(1, 2), Pair(2, 0))}
solver = Solver([], [])
solver.data = parameters
solver.constraints = constraints
solver.read_constraints()
solver.compute_constraints()
self.assertEqual(solver.constraints, constraints, "compute_constraints change constraints without secret "
"constraint")
def test_compute_constraints_detect_invalid_constraints(self):
"""
Test that, function should raise an exception, if there is invalid_constraints
"""
parameters = [3, 3, 3]
constraints = {(Pair(0, 0),), (Pair(0, 1),), (Pair(0, 2),)}
solver = Solver([], [])
solver.data = parameters
solver.constraints = constraints
solver.read_constraints()
self.assertRaises(ValueError, solver.compute_constraints)
def test_compute_constraints_with_one_secrete_constraint(self):
"""
Test that, function should find new constraint
"""
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1), Pair(2, 0)), (Pair(0, 2), Pair(3, 2))}
solver = Solver([], [])
solver.data = parameters
solver.constraints = constraints
solver.read_constraints()
solver.compute_constraints()
expectation = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1), Pair(2, 0)), (Pair(0, 2), Pair(3, 2)),
(Pair(1, 1), Pair(2, 0), Pair(3, 2))}
self.assertEqual(solver.constraints, expectation, "compute_constraints didn't find secret constraints")
# Test for simplify_constraints function
def test_simplify_constraints_constraints_without_simplification(self):
"""
Test that, function do not delete important constraints
"""
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1), Pair(2, 0)), (Pair(0, 2), Pair(3, 2))}
solver = Solver([], [])
solver.data = parameters
solver.constraints = constraints
solver.read_constraints()
solver.simplify_constraints()
self.assertEqual(solver.constraints, constraints, "simplify_constraints deleted some important constraints")
def test_simplify_constraints_constraints_with_simplification(self):
"""
Test that, function do not delete important constraints
"""
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(2, 0), Pair(1, 1)),
(Pair(0, 2), Pair(2, 0)), (Pair(2, 0),)}
solver = Solver([], [])
solver.data = parameters
solver.constraints = constraints
solver.read_constraints()
solver.simplify_constraints()
expectation = {(Pair(2, 0),)}
self.assertEqual(solver.constraints, expectation, "simplify_constraints deleted some important constraints")
# Test of Minimum forbidden tuple algorithm
def test_solver_without_secrete_constraints(self):
"""
Test that, solver didn't change constraints if there isn't any secret constraint
"""
parameters = [3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1)), (Pair(1, 0), Pair(2, 0))}
solver = Solver(parameters, constraints)
self.assertEqual(solver.constraints, constraints, "solver change constraints without secret "
"constraint")
def test_solver_constraints_without_simplification(self):
"""
Test that, solver do not delete important constraints
"""
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(2, 0)), (Pair(0, 1), Pair(1, 1), Pair(2, 0)), (Pair(0, 2), Pair(3, 2))}
solver = Solver(parameters, constraints)
self.assertEqual(solver.constraints, constraints, "solver deleted some important constraints")
def test_solver_constraints_with_simplification_and_secrete(self):
"""
Test that, Minimum forbidden tuple algorithm can find and simplify constraints
"""
parameters = [3, 3, 3, 3]
constraints = {(Pair(0, 0), Pair(1, 0)), (Pair(0, 0), Pair(1, 2)), (Pair(0, 1), Pair(3, 0)),
(Pair(0, 2), Pair(3, 0)), (Pair(1, 1), Pair(3, 0))}
solver = Solver(parameters, constraints)
expectation = {(Pair(0, 0), Pair(1, 0)), (Pair(0, 0), Pair(1, 2)), (Pair(3, 0),)}
self.assertEqual(solver.constraints, expectation, "solver can not compute and simplify constraints")
if __name__ == '__main__':
unittest.main()
import unittest
from avocado_varianter_cit import Cit
class CitTest(unittest.TestCase):
PARAMS = [('key1', ['x1', 'x2', 'x3']), ('key2', ['y1', 'y2', 'y3']),
('key3', ['z1', 'z2', 'z3']), ('key4', ['w1', 'w2', 'w3'])]
def test_orders(self):
for i in range(len(self.PARAMS)):
cit = Cit(self.PARAMS, i+1)
headers, _ = cit.combine()
# headers should always be the same, no matter the order
# of combinations
self.assertEqual(headers, ['key1', 'key2', 'key3', 'key4'])
def test_number_of_combinations(self):
# the algorithm doesn't allow us to know beforehand, for a
# reason, the precise number of combinations that will be
# computed. still, we can check for the minimum number of
# combinations that should be produced
cit = Cit(self.PARAMS, 1)
self.assertEqual(len(cit.combine()[1]), 3)
cit = Cit(self.PARAMS, 2)
self.assertGreaterEqual(len(cit.combine()[1]), 9)
cit = Cit(self.PARAMS, 3)
self.assertGreaterEqual(len(cit.combine()[1]), 27)
def test_max_order(self):
# test that with a order equal or larger than the number of
# keys, we have a predictable number of combinations
cit = Cit(self.PARAMS, 4)
self.assertEqual(len(cit.combine()[1]), 81)
cit = Cit(self.PARAMS, 10)
self.assertEqual(len(cit.combine()[1]), 81)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册