diff --git a/aalpy/oracles/WMethodEqOracle.py b/aalpy/oracles/WMethodEqOracle.py index 033d75e4..c6ca70f6 100644 --- a/aalpy/oracles/WMethodEqOracle.py +++ b/aalpy/oracles/WMethodEqOracle.py @@ -1,8 +1,8 @@ -from itertools import product from random import shuffle, choice, randint from aalpy.base.Oracle import Oracle from aalpy.base.SUL import SUL +from aalpy.utils.HelperFunctions import product_with_possible_empty_iterable class WMethodEqOracle(Oracle): @@ -10,6 +10,7 @@ class WMethodEqOracle(Oracle): Equivalence oracle based on characterization set/ W-set. From 'Tsun S. Chow. Testing software design modeled by finite-state machines'. """ + def __init__(self, alphabet: list, sul: SUL, max_number_of_states, shuffle_test_set=True): """ Args: @@ -35,9 +36,9 @@ def find_cex(self, hypothesis): middle = [] for i in range(self.m + 1 - len(hypothesis.states)): - middle.extend(list(product(self.alphabet, repeat=i))) + middle.extend(list(product_with_possible_empty_iterable(self.alphabet, repeat=i))) - for seq in product(transition_cover, middle, hypothesis.characterization_set): + for seq in product_with_possible_empty_iterable(transition_cover, middle, hypothesis.characterization_set): inp_seq = tuple([i for sub in seq for i in sub]) if inp_seq not in self.cache: self.reset_hyp_and_sul(hypothesis) @@ -53,7 +54,6 @@ def find_cex(self, hypothesis): self.sul.post() return inp_seq[:ind + 1] self.cache.add(inp_seq) - return None @@ -64,6 +64,7 @@ class RandomWMethodEqOracle(Oracle): Random walks stem from fixed prefix (path to the state). At the end of the random walk an element from the characterization set is added to the test case. """ + def __init__(self, alphabet: list, sul: SUL, walks_per_state=12, walk_len=12): """ Args: diff --git a/aalpy/oracles/WpMethodEqOracle.py b/aalpy/oracles/WpMethodEqOracle.py index 95be105e..8b3714d2 100644 --- a/aalpy/oracles/WpMethodEqOracle.py +++ b/aalpy/oracles/WpMethodEqOracle.py @@ -1,6 +1,8 @@ from aalpy.base.Oracle import Oracle from aalpy.base.SUL import SUL -from itertools import product, chain, tee +from itertools import chain, tee + +from aalpy.utils.HelperFunctions import product_with_possible_empty_iterable def state_characterization_set(hypothesis, alphabet, state): @@ -21,30 +23,35 @@ def state_characterization_set(hypothesis, alphabet, state): return result -def i_star(alph, upto): +def i_star(alphabet, max_seq_len): """ Return an iterator that generates all possible sequences of length upto from the given alphabet. Args: - alph: input alphabet - upto: maximum length of the sequences + alphabet: input alphabet + max_seq_len: maximum length of the sequences """ - return chain(*(product(alph, repeat=i) for i in range(upto))) + return chain(*(product_with_possible_empty_iterable(alphabet, repeat=i) for i in range(max_seq_len))) -def second_phase_it(hyp, alph, difference, middle): +def second_phase_it(hyp, alphabet, difference, middle): """ Return an iterator that generates all possible sequences for the second phase of the Wp-method. Args: hyp: hypothesis automaton - alph: input alphabet + alphabet: input alphabet difference: set of sequences that are in the transition cover but not in the state cover middle: iterator that generates all possible sequences of length upto from the given alphabet """ - for t, mid in product(difference, middle): + state_mapping = {} + for t, mid in product_with_possible_empty_iterable(difference, middle): _ = hyp.execute_sequence(hyp.initial_state, t + mid) state = hyp.current_state - char_set = state_characterization_set(hyp, alph, state) - concatenated = product([t], [mid], char_set) + if state not in state_mapping: + char_set = state_characterization_set(hyp, alphabet, state) + state_mapping[state] = char_set + else: + char_set = state_mapping[state] + concatenated = product_with_possible_empty_iterable([t], [mid], char_set) for el in concatenated: yield el @@ -68,18 +75,26 @@ def find_cex(self, hypothesis): for state in hypothesis.states for letter in self.alphabet ) + state_cover = set(state.prefix for state in hypothesis.states) difference = transition_cover.difference(state_cover) # two views of the same iterator - middle_1, middle_2 = tee(i_star(self.alphabet, self.m), 2) + middle_1, middle_2 = tee(i_star(self.alphabet, self.m - hypothesis.size + 1), 2) + # first phase State Cover * Middle * Characterization Set - first_phase = product(state_cover, middle_1, hypothesis.characterization_set) + first_phase = product_with_possible_empty_iterable(state_cover, middle_1, hypothesis.characterization_set) + # second phase (Transition Cover - State Cover) * Middle * Characterization Set # of the state that the prefix leads to second_phase = second_phase_it(hypothesis, self.alphabet, difference, middle_2) + test_suite = chain(first_phase, second_phase) + + l = 0 for seq in test_suite: + print(l) + l += 1 inp_seq = tuple([i for sub in seq for i in sub]) if inp_seq not in self.cache: self.reset_hyp_and_sul(hypothesis) diff --git a/aalpy/utils/HelperFunctions.py b/aalpy/utils/HelperFunctions.py index 0905d5e3..6d5dbe80 100644 --- a/aalpy/utils/HelperFunctions.py +++ b/aalpy/utils/HelperFunctions.py @@ -1,5 +1,6 @@ import random import string +from itertools import product from collections import defaultdict @@ -376,3 +377,11 @@ def generate_input_output_data_from_vpa(vpa, num_sequances=4000, min_seq_len=1, input_output_sequances.append(list(zip(sequance, outputs))) return input_output_sequances + + +def product_with_possible_empty_iterable(*iterables, repeat=1): + """ + Words like regular product, but if one of the iterables is empty it will just ignore it, instead of returning []. + """ + non_empty_iterables = [it for it in iterables if it] + return product(*non_empty_iterables, repeat=repeat)