Commit 733be5e5 authored by Paul Fiterau Brostean's avatar Paul Fiterau Brostean
Browse files

Added random walk from state test generation algorithms. Nothing yet tested.

parent 272c8c8f
...@@ -72,7 +72,7 @@ class MealyMachineBuilder(object): ...@@ -72,7 +72,7 @@ class MealyMachineBuilder(object):
tr.z3_to_label(inp), tr.z3_to_label(inp),
tr.z3_to_label(output), tr.z3_to_label(output),
tr.z3_to_state(to_state)) tr.z3_to_state(to_state))
mut_mm.generate_acc_seq()
return mut_mm.to_immutable() return mut_mm.to_immutable()
class DFABuilder(object): class DFABuilder(object):
...@@ -94,7 +94,7 @@ class DFABuilder(object): ...@@ -94,7 +94,7 @@ class DFABuilder(object):
tr.z3_to_label(labels), tr.z3_to_label(labels),
tr.z3_to_state(to_state)) tr.z3_to_state(to_state))
mut_dfa.add_transition(tr.z3_to_state(state), trans) mut_dfa.add_transition(tr.z3_to_state(state), trans)
mut_dfa.generate_acc_seq()
return mut_dfa.to_immutable() return mut_dfa.to_immutable()
class FATranslator(object): class FATranslator(object):
......
...@@ -102,6 +102,7 @@ class RegisterAutomatonBuilder(object): ...@@ -102,6 +102,7 @@ class RegisterAutomatonBuilder(object):
self._add_state(model, translator, mut_ra, z3state) self._add_state(model, translator, mut_ra, z3state)
for z3label in self.ra.labels.values(): for z3label in self.ra.labels.values():
self._add_transitions(model, translator, mut_ra, z3state, z3label) self._add_transitions(model, translator, mut_ra, z3state, z3label)
mut_ra.generate_acc_seq()
return mut_ra.to_immutable() return mut_ra.to_immutable()
def _add_state(self, model, translator, mut_ra, z3state): def _add_state(self, model, translator, mut_ra, z3state):
...@@ -176,6 +177,7 @@ class IORegisterAutomatonBuilder(object): ...@@ -176,6 +177,7 @@ class IORegisterAutomatonBuilder(object):
self._add_state(translator, mut_ra, z3state) self._add_state(translator, mut_ra, z3state)
for z3label in z3input_labels: for z3label in z3input_labels:
self._add_transitions(model, translator, mut_ra, z3state, z3label, z3output_labels) self._add_transitions(model, translator, mut_ra, z3state, z3label, z3output_labels)
mut_ra.generate_acc_seq()
return mut_ra.to_immutable() return mut_ra.to_immutable()
def _add_state(self, translator, mut_ra, z3state): def _add_state(self, translator, mut_ra, z3state):
......
...@@ -249,6 +249,18 @@ class IORAEncoder(Encoder): ...@@ -249,6 +249,18 @@ class IORAEncoder(Encoder):
ra.transition(q, ra.labels[l], r) == ra.transition(q, ra.labels[l], ra.fresh) ra.transition(q, ra.labels[l], r) == ra.transition(q, ra.labels[l], ra.fresh)
) )
) )
for l in self.output_labels:
if self.param_size[l] == 0:
print("Added for ", l)
axioms.append(
z3.ForAll(
[q,r],
z3.Implies(
r != ra.fresh,
ra.transition(q, ra.labels[l], r) == ra.sink
)
)
)
return axioms return axioms
......
...@@ -11,7 +11,7 @@ class Learner(metaclass=ABCMeta): ...@@ -11,7 +11,7 @@ class Learner(metaclass=ABCMeta):
pass pass
@abstractmethod @abstractmethod
def model(self, old_definition:define.Automaton=None) -> Tuple[model.Automaton, define.Automaton]: def model(self, old_definition:define.Automaton=None, old_model:model.Automaton=None) -> Tuple[model.Automaton, define.Automaton]:
""""Infers a minimal model whose behavior corresponds to the traces added so far. """"Infers a minimal model whose behavior corresponds to the traces added so far.
Returns None if no model could be obtained.""" Returns None if no model could be obtained."""
pass pass
......
...@@ -20,8 +20,9 @@ class RALearner(Learner): ...@@ -20,8 +20,9 @@ class RALearner(Learner):
def add(self, trace): def add(self, trace):
self.encoder.add(trace) self.encoder.add(trace)
def model(self, min_locations=1, max_locations=20, min_registers=0, max_registers=10, def model(self, min_locations=1, max_locations=20, min_registers=0, max_registers=3,
old_definition:define.ra.RegisterMachine = None) -> Tuple[model.ra.RegisterMachine, define.ra.RegisterMachine]: old_definition:define.ra.RegisterMachine = None, old_model:model.ra.RegisterMachine = None) -> \
Tuple[model.ra.RegisterMachine, define.ra.RegisterMachine]:
if old_definition is not None: if old_definition is not None:
min_locations = len(old_definition.locations) min_locations = len(old_definition.locations)
min_registers = len(old_definition.registers) - 1 min_registers = len(old_definition.registers) - 1
......
...@@ -15,17 +15,18 @@ from test.generation import ExhaustiveRAGenerator ...@@ -15,17 +15,18 @@ from test.generation import ExhaustiveRAGenerator
# (model, stats) = learn(learner, IORATest, obs) # (model, stats) = learn(learner, IORATest, obs)
# print(model, "\n \n", stats) # print(model, "\n \n", stats)
# set_sut = new_fifoset_sut(2) # expensive peace of work
# set_sut = new_fifoset_sut(3)
# gen = ExhaustiveRAGenerator(set_sut) # gen = ExhaustiveRAGenerator(set_sut)
# obs = gen.generate_observations(6, max_registers=2) # obs = gen.generate_observations(8, max_registers=3)
# print("\n".join( [str(obs) for obs in obs])) # print("\n".join( [str(obs) for obs in obs]))
# learner = RALearner(IORAEncoder()) # learner = RALearner(IORAEncoder())
# (model, stats) = learn(learner, IORATest, obs) # (model, stats) = learn(learner, IORATest, obs)
# print(model, "\n \n", stats) # print(model, "\n \n", stats)
login_sut = new_login_sut(2) login_sut = new_login_sut(1)
gen = ExhaustiveRAGenerator(login_sut) gen = ExhaustiveRAGenerator(login_sut)
obs = gen.generate_observations(8, max_registers=2) obs = gen.generate_observations(6, max_registers=1)
print("\n".join( [str(obs) for obs in obs])) print("\n".join( [str(obs) for obs in obs]))
learner = RALearner(IORAEncoder()) learner = RALearner(IORAEncoder())
(model, stats) = learn(learner, IORATest, obs) (model, stats) = learn(learner, IORATest, obs)
......
...@@ -45,8 +45,19 @@ class Automaton(metaclass=ABCMeta): ...@@ -45,8 +45,19 @@ class Automaton(metaclass=ABCMeta):
def start_state(self): def start_state(self):
return self._states[0] return self._states[0]
def acc_seq(self, state): def acc_trans_seq(self, state=None) -> List[Transition]:
return self._acc_seq[state] """returns the access sequence to a state in the form of transitions"""
if state is not None:
return list(self._acc_seq[state])
else:
return dict(self._acc_seq)
def acc_seq(self, state=None):
"""returns the access sequence to a state in the form of sequences of inputs"""
if state is not None:
return self._seq(self._acc_seq[state])
else:
return {state:self._seq(self._acc_seq[state]) for state in self.states()}
def states(self): def states(self):
return list(self._states) return list(self._states)
...@@ -76,6 +87,11 @@ class Automaton(metaclass=ABCMeta): ...@@ -76,6 +87,11 @@ class Automaton(metaclass=ABCMeta):
return crt_state return crt_state
@abstractmethod
def _seq(self, transitions:List[Transition])->List[object]:
"""returns the sequence of inputs generated by execution of these transitions"""
pass
def input_labels(self): def input_labels(self):
return set([trans.start_label for trans in self.transitions(self.start_state())]) return set([trans.start_label for trans in self.transitions(self.start_state())])
...@@ -86,6 +102,7 @@ class Automaton(metaclass=ABCMeta): ...@@ -86,6 +102,7 @@ class Automaton(metaclass=ABCMeta):
# Basic __str__ function which works for most FSMs. # Basic __str__ function which works for most FSMs.
def __str__(self): def __str__(self):
str_rep = "" str_rep = ""
str_rep += str(self._acc_seq) + "\n"
for state in self.states(): for state in self.states():
str_rep += str(state) + "\n" str_rep += str(state) + "\n"
for tran in self.transitions(state): for tran in self.transitions(state):
...@@ -94,17 +111,26 @@ class Automaton(metaclass=ABCMeta): ...@@ -94,17 +111,26 @@ class Automaton(metaclass=ABCMeta):
return str_rep return str_rep
class MutableAutomatonMixin(metaclass=ABCMeta): class MutableAutomatonMixin(metaclass=ABCMeta):
def add_state(self, state): def add_state(self:Automaton, state):
if state not in self._states: if state not in self._states:
self._states.append(state) self._states.append(state)
def add_transition(self, state, transition): def add_transition(self:Automaton, state, transition):
if state not in self._state_to_trans: if state not in self._state_to_trans:
self._state_to_trans[state] = [] self._state_to_trans[state] = []
self._state_to_trans[state].append(transition) self._state_to_trans[state].append(transition)
def add_acc_seq(self, state, trace): def generate_acc_seq(self:Automaton):
self._acc_seq[state] = trace """generates access sequences for an automaton. It optionally takes in old access sequences, which are """
new_acc_seq = dict()
ptree = get_prefix_tree(self)
for state in self.states():
pred = lambda x: (x.state == state)
node = ptree.find_node(pred)
if node is None:
raise Exception("Could not find state {0} in tree {1}".format(state, ptree))
new_acc_seq[state] = node.path()
self._acc_seq = new_acc_seq
@abstractmethod @abstractmethod
def to_immutable(self) -> Automaton: def to_immutable(self) -> Automaton:
...@@ -156,14 +182,7 @@ class MutableAcceptorMixin(MutableAutomatonMixin, metaclass=ABCMeta): ...@@ -156,14 +182,7 @@ class MutableAcceptorMixin(MutableAutomatonMixin, metaclass=ABCMeta):
self._state_to_acc[state] = accepts self._state_to_acc[state] = accepts
def get_acc_seq(aut : Automaton, runner, old_acc_seq = dict()):
new_acc_seq = {aut.state(acc_seq):acc_seq for acc_seq in old_acc_seq.values()}
not_covered = [state for state in aut.states() if state not in new_acc_seq.keys()]
ptree = get_prefix_tree(aut)
for state in not_covered:
trace = runner(ptree.path(state))
new_acc_seq[state] = trace
return new_acc_seq
def get_prefix_tree(aut : Automaton): def get_prefix_tree(aut : Automaton):
...@@ -179,27 +198,45 @@ def get_prefix_tree(aut : Automaton): ...@@ -179,27 +198,45 @@ def get_prefix_tree(aut : Automaton):
if trans.end_state not in visited: if trans.end_state not in visited:
child_node = PrefixTree(trans.end_state) child_node = PrefixTree(trans.end_state)
crt_node.add_child(trans, child_node) crt_node.add_child(trans, child_node)
to_visit.add(child_node)
return root return root
class PrefixTree(): class PrefixTree():
def __init__(self, state): def __init__(self, state):
self.state = state self.state = state
self.tr_tree:dict = {} self.tr_tree:dict = {}
self.parent = None self.parent:PrefixTree = None
def add_child(self, trans, tree): def add_child(self, trans, tree):
self.tr_tree[trans] = tree self.tr_tree[trans] = tree
self.tr_tree[trans].parent = self self.tr_tree[trans].parent = self
def path(self, state) -> List[Transition]: def path(self) -> List[Transition]:
if len(self.tr_tree): if self.parent is None:
return []
else:
for (tr, node) in self.parent.tr_tree.items():
if node is self:
return self.parent.path()+[tr]
raise Exception("Miscontructed tree")
def find_node(self, predicate):
if predicate(self):
return self
elif len(self.tr_tree) == 0:
return None return None
else: else:
for (tran, child) in self.tr_tree.items(): for (tran, child) in self.tr_tree.items():
path = child.path(state) node = child.find_node(predicate)
if path is not None: if node is not None:
return [tran] + path return node
return None return None
\ No newline at end of file
def __str__(self, tabs=0):
space = "".join(["\t" for _ in range(0, tabs)])
tree = "(n_{0}".format(self.state)
for (tr, node) in self.tr_tree.items():
tree += "\n" + space + " {0} -> {1}".format(tr, node.__str__(tabs=tabs + 1))
tree += ")"
return tree
\ No newline at end of file
...@@ -32,10 +32,16 @@ class DFA(Acceptor): ...@@ -32,10 +32,16 @@ class DFA(Acceptor):
# print(tr_str) # print(tr_str)
return crt_state return crt_state
def _seq(self, transitions: List[Transition]):
return [trans.start_label for trans in transitions]
class MutableDFA(DFA, MutableAcceptorMixin): class MutableDFA(DFA, MutableAcceptorMixin):
def __init__(self): def __init__(self):
super().__init__([], {}, {}) super().__init__([], {}, {})
def _runner(self):
return None
def to_immutable(self) -> DFA: def to_immutable(self) -> DFA:
return DFA(self._states, self._state_to_trans, self._state_to_acc) return DFA(self._states, self._state_to_trans, self._state_to_acc)
...@@ -55,6 +61,10 @@ class MooreMachine(Transducer): ...@@ -55,6 +61,10 @@ class MooreMachine(Transducer):
crt_state = self.state(trace) crt_state = self.state(trace)
return self.state_to_out[crt_state] return self.state_to_out[crt_state]
def _seq(self, transitions:List[Transition]):
#trace = [(trans.start_label, self.state_to_out[trans.end_state]) for trans in transitions]
return [trans.start_label for trans in transitions]#trace
class MealyMachine(Transducer): class MealyMachine(Transducer):
def __init__(self, states, state_to_trans): def __init__(self, states, state_to_trans):
super().__init__(states, state_to_trans) super().__init__(states, state_to_trans)
...@@ -66,6 +76,10 @@ class MealyMachine(Transducer): ...@@ -66,6 +76,10 @@ class MealyMachine(Transducer):
crt_state = super().state(trace) crt_state = super().state(trace)
return crt_state return crt_state
def _seq(self, transitions:List[IOTransition]):
#trace = [(trans.start_label, trans.output) for trans in transitions]
return [trans.start_label for trans in transitions]
def output(self, trace: List[Symbol]) -> Output: def output(self, trace: List[Symbol]) -> Output:
if len(trace) == 0: if len(trace) == 0:
return None return None
......
...@@ -137,6 +137,20 @@ class RegisterAutomaton(Acceptor, RegisterMachine): ...@@ -137,6 +137,20 @@ class RegisterAutomaton(Acceptor, RegisterMachine):
return crt_state return crt_state
def _seq(self, transitions:List[RATransition]):
run = []
values = set()
reg_val = dict()
for trans in transitions:
if isinstance(trans.guard, EqualityGuard) or isinstance(trans.guard, OrGuard):
inp_val = reg_val[trans.guard.registers()[0]]
else:
inp_val = 0 if len(values) == 0 else max(values) + 1
values.add(inp_val)
inp = Action(trans.start_label, inp_val)
run.append(inp)
return run
class IORegisterAutomaton(Transducer, RegisterMachine): class IORegisterAutomaton(Transducer, RegisterMachine):
def __init__(self, locations, loc_to_trans, registers): def __init__(self, locations, loc_to_trans, registers):
...@@ -194,6 +208,28 @@ class IORegisterAutomaton(Transducer, RegisterMachine): ...@@ -194,6 +208,28 @@ class IORegisterAutomaton(Transducer, RegisterMachine):
output = fired_transition.output(valuation, values) output = fired_transition.output(valuation, values)
return output return output
def _seq(self, transitions: List[IORATransition]):
seq = []
values = set()
reg_val = dict()
for trans in transitions:
if isinstance(trans.guard, EqualityGuard) or isinstance(trans.guard, OrGuard):
inp_val = reg_val[trans.guard.registers()[0]]
else:
inp_val = 0 if len(values) == 0 else max(values) + 1
values.add(inp_val)
inp = Action(trans.start_label, inp_val)
reg_val = trans.update(reg_val, inp)
if isinstance(trans.output_mapping, Register):
out_val = reg_val[trans.output_mapping]
else:
out_val = 0 if len(values) == 0 else max(values) + 1
values.add(out_val)
out = Action(trans.output_label, out_val)
reg_val = trans.output_update(reg_val, out)
seq.append(inp)
return seq
class MutableRegisterAutomaton(RegisterAutomaton, MutableAcceptorMixin): class MutableRegisterAutomaton(RegisterAutomaton, MutableAcceptorMixin):
def __init__(self): def __init__(self):
super().__init__([], dict(), dict(), []) super().__init__([], dict(), dict(), [])
......
...@@ -3,6 +3,7 @@ from typing import List ...@@ -3,6 +3,7 @@ from typing import List
import collections import collections
from model.fa import Symbol
from model.ra import Action from model.ra import Action
from enum import Enum from enum import Enum
...@@ -11,7 +12,7 @@ class SUT(metaclass=ABCMeta): ...@@ -11,7 +12,7 @@ class SUT(metaclass=ABCMeta):
OK = "OK" OK = "OK"
NOK = "NOK" NOK = "NOK"
@abstractmethod @abstractmethod
def run(self, seq:List[object]): def run(self, seq:List[object]) -> Observation:
"""Runs a sequence of inputs on the SUT and returns an observation""" """Runs a sequence of inputs on the SUT and returns an observation"""
pass pass
...@@ -59,13 +60,35 @@ class RASUT(metaclass=ABCMeta): ...@@ -59,13 +60,35 @@ class RASUT(metaclass=ABCMeta):
class Observation(): class Observation():
@abstractmethod @abstractmethod
def trace(self): def trace(self):
"""returns the trace to be added to the solver"""
pass pass
@abstractmethod @abstractmethod
def inputs(self): def inputs(self):
"""returns all the values in the trace""" """returns all the inputs from an observation"""
pass pass
class DFAObservation():
def __init__(self, seq, acc):
self.seq = seq
self.acc = acc
def trace(self):
return (self.seq, self.acc)
def inputs(self) -> List[Symbol]:
return self.seq
class MealyObservation():
def __init__(self, trace):
self.tr = trace
def trace(self):
return self.tr
def inputs(self) -> List[Symbol]:
return [a for (a,_) in self.tr]
class RegisterMachineObservation(Observation): class RegisterMachineObservation(Observation):
@abstractmethod @abstractmethod
...@@ -75,34 +98,37 @@ class RegisterMachineObservation(Observation): ...@@ -75,34 +98,37 @@ class RegisterMachineObservation(Observation):
# class RAObservation(Observation): class RAObservation(RegisterMachineObservation):
# def __init__(self, trace): def __init__(self, seq, acc):
# self.seq = seq
# self.acc = acc
#
# def trace(self): def trace(self):
# return self.trace return (self.trace, self.acc)
#
# def values(self): def inputs(self):
# return return self.seq
def values(self):
return set([a.value for a in self.seq if a.value is not None])
class IORAObservation(RegisterMachineObservation): class IORAObservation(RegisterMachineObservation):
def __init__(self, trace): def __init__(self, trace):
self.trace = trace self.tr = trace
def trace(self): def trace(self):
return self.trace return self.tr
def inputs(self): def inputs(self):
return [a for (a,_) in self.trace] return [a for (a,_) in self.tr]
def values(self): def values(self):
iv = [a.value for (a,_) in self.trace if a.value is not None] iv = [a.value for (a,_) in self.tr if a.value is not None]
ov = [a.value for (_,a) in self.trace if a.value is not None] ov = [a.value for (_,a) in self.tr if a.value is not None]
return set(iv+ov) return set(iv+ov)
def __str__(self): def __str__(self):
return "Obs: " + str(self.trace) return "Obs: " + str(self.tr)
......
from sut import SUT, ObjectSUT, ActionSignature, SUTType
class Login():
INTERFACE = [ActionSignature("register", 0), ActionSignature("login", 1), ActionSignature("logout", 1)]
def __init__(self, size):
super()
self.size = size
self.logged = {}
def register(self):
if len(self.logged) == self.size:
return SUT.NOK
else:
new_id = 100 if len(self.logged) == 0 else max(self.logged.keys()) + 100
self.logged[new_id] = False
return ("OREG", new_id)
def login(self, val):
if val not in self.logged or self.logged[val]:
return SUT.NOK
else:
self.logged[val] = True
return SUT.OK
def logout(self, val):