diff --git a/pymodel/ActionNameCoverage.py b/pymodel/ActionNameCoverage.py old mode 100644 new mode 100755 diff --git a/pymodel/Analyzer.py b/pymodel/Analyzer.py old mode 100644 new mode 100755 diff --git a/pymodel/AnalyzerOptions.py b/pymodel/AnalyzerOptions.py old mode 100644 new mode 100755 diff --git a/pymodel/Dot.py b/pymodel/Dot.py old mode 100644 new mode 100755 diff --git a/pymodel/FSM.py b/pymodel/FSM.py old mode 100644 new mode 100755 diff --git a/pymodel/GraphicsOptions.py b/pymodel/GraphicsOptions.py old mode 100644 new mode 100755 diff --git a/pymodel/ModelProgram.py b/pymodel/ModelProgram.py old mode 100644 new mode 100755 index e5995ed..6129430 --- a/pymodel/ModelProgram.py +++ b/pymodel/ModelProgram.py @@ -10,7 +10,7 @@ import inspect import itertools from pymodel.model import Model -import collections +import collections, collections.abc import pprint DEBUG = False @@ -69,7 +69,7 @@ def make_argslist(self, a): """ Parameter generator: return list of all args combos for action symbol a """ - arginfo = inspect.getargspec(a)# ArgInfo(args,varargs,keywords,locals) + arginfo = inspect.getfullargspec(a)# ArgInfo(args,varargs,keywords,locals) if arginfo[0]: args = arginfo[0] # usual case: fixed sequence of positional arguments elif arginfo[1]: @@ -77,7 +77,7 @@ def make_argslist(self, a): else: args = () # no arguments anywhere, args must have this value domains = [ self.module.domains[a][arg]() # evaluate state-dependent domain - if isinstance(self.module.domains[a][arg], collections.Callable) + if isinstance(self.module.domains[a][arg], collections.abc.Callable) else self.module.domains[a][arg] # look up static domain for arg in args if a in self.module.domains ] combination = self.module.combinations.get(a, 'all') # default is 'all' @@ -125,7 +125,7 @@ def ActionEnabled(self, a, args): else: # Assumes enablers[a] has only one item, always true in this version a_enabled = self.module.enablers[a][0] - nparams = len(inspect.getargspec(a_enabled)[0]) + nparams = len(inspect.getfullargspec(a_enabled)[0]) nargs = len(args) # nparams == 0 means match any args if nparams > 0 and nparams != nargs: diff --git a/pymodel/ProductModelProgram.py b/pymodel/ProductModelProgram.py old mode 100644 new mode 100755 index d4a6e45..c8c503e --- a/pymodel/ProductModelProgram.py +++ b/pymodel/ProductModelProgram.py @@ -17,8 +17,9 @@ This module translates action function a to string aname: aname = a.__name__ and translates aname string to action function a: a = getattr(module, aname) """ - +import os import pprint +import sys from operator import concat from collections import defaultdict @@ -26,283 +27,286 @@ from pymodel.TestSuite import TestSuite from pymodel.ModelProgram import ModelProgram from functools import reduce +import importlib DEBUG = False - + + class ProductModelProgram(object): - - def __init__(self, options, args): - self.TestSuite = False # used by pmt nruns logic - self.module = dict() # dict of modules keyed by name - self.mp = dict() # dict of model programs keyed by same module name - - if DEBUG: - self.pp = pprint.PrettyPrinter(width=120) - - # Populate self.module and self.mp from modules named in command line args - # Models that users write are just modules, not classes (types) - # so we find out what type to wrap each one in - # by checking for one of each type's required attributes using hasattr - for mname in args: # args is list of module name - self.module[mname] = __import__(mname) - if hasattr(self.module[mname], 'graph'): - self.mp[mname] = FSM(self.module[mname],options.exclude,options.action) - # for backwards compatibility we accept all of these test_suite variants - elif (hasattr(self.module[mname], 'testSuite') or - hasattr(self.module[mname], 'testsuite') or - hasattr(self.module[mname], 'test_suite')): - self.mp[mname] = TestSuite(self.module[mname], - options.exclude, options.action) - self.TestSuite = True # used by pmt nruns logic - elif self.module[mname].__doc__.strip().upper().startswith('PYMODEL CONFIG'): - pass # configuration module, merely importing it did all the work - else: - # got this far, should be a ModelProgram -- if not, crash - self.mp[mname] = ModelProgram(self.module[mname], - options.exclude, options.action) - - # Now that all modules have been imported and executed their __init__ - # do a postprocessing pass over all model objects - # to process metadata that might be affected by configuration modules - for mp in list(self.mp.values()): - mp.post_init() - - # set of all anames in all model programs - the vocabulary of the product - self.anames = set().union(*[set([a.__name__ for a in mp.actions ]) - for mp in list(self.mp.values())]) - # print 'anames %s' % self.anames # DEBUG - - # set of anames of all observable actions in all model programs - # observables obtain arg values from the environment, not parameter gen. - self.observables = set().union(*[set([a.__name__ - for a in mp.module.observables]) - for mp in list(self.mp.values())]) - # FSM and TestSuite must have .observables - # print 'observables %s' % self.observables # DEBUG - - # dict from aname to set of all m where aname is in vocabulary - self.vocabularies = \ - dict([(aname, set([m for m in self.mp if aname in - [a.__name__ for a in self.mp[m].actions]])) - for aname in self.anames]) - # print 'vocabularies %s' % self.vocabularies # DEBUG - - # ProductModelProgram only provides methods etc. called by test runner etc.: - # EnabledActions(cleanup), Properties(), DoAction(a,args), Reset(), TestSuite - # BUT not methods used internally in mp classes: Churrent, Restore, GetNext - - def ActionEnabled(self, aname, args): - """ - True if action aname with args is enabled in the current state - """ - return all([m.ActionEnabled(getattr(m.module, aname), args) - # NOT! empty argument list in model matches any arguments - # NOT! or m.ActionEnabled(getattr(m.module, aname), ()) - # handle zero-args/match-all inside m.ActionEnabled - for m in list(self.mp.values()) - # aname might be an unshared action, not present in all mp - if aname in [ a.__name__ for a in m.actions ]]) - - def EnabledTransitions(self, cleanup): - """ - This is where composition happens! - (aname,args,result) is enabled in the product if it is enabled, - OR (aname, (), None) with empty args and None result is enabled - in every machine where aname is in the vocabulary. - Returns list: [(aname, args, result, next, properties), ... ] - third item result is the same value from all mp, or None - fourth item next is dict of mp name to that mp's next state: - (m1:next1,m2:current2,m3:next3,...),...] - or to its current state if aname is not in that mp vocabulary - fifth item properties is dict of property name to value in next state: - { 'accepting': True, 'statefilter': True, ... } - where there is just one value for each property for the whole product - """ - # Call ParamGen here to allow for state-dependent parameter generation - for mp in list(self.mp.values()): - if isinstance(mp, ModelProgram): - mp.ParamGen() - - # Built up dicts from mp name to list of all its enabled - # (action, args, result, next state, properties) - - # dict for FSM and TestSuite only, they might provide args for observables - enabledScenarioActions = \ - dict([(m, self.mp[m].EnabledTransitions(cleanup)) - for m in self.mp if (not isinstance(self.mp[m], ModelProgram))]) - - if DEBUG: - print('enabledScenarioActions %s' % enabledScenarioActions) - - # dict from action to sequence of argslists - argslists = defaultdict(list) - for transitions in list(enabledScenarioActions.values()): - for (action, args, result, next_state, properties) in transitions: - argslists[action].append(args) # append not extend - - # If more than one scenario in product, there may be duplicates - use sets - scenarioArgslists = dict([(action, set(args)) - for (action,args) in list(argslists.items())]) - if DEBUG: - print('scenarioArgslists %s' % scenarioArgslists) - - # Pass scenarioArgslists to ModelProgram EnabledTransitions - # so any observable actions can use these argslists - enabledModelActions = \ - dict([(m, self.mp[m].EnabledTransitions(scenarioArgslists, cleanup)) - for m in self.mp if isinstance(self.mp[m], ModelProgram)]) - if DEBUG: - print('enabledModelActions:') - self.pp.pprint(enabledModelActions) - - # Combine enabled actions dictionaries (they have distinct keys) - enabledActions = dict() - enabledActions.update(enabledScenarioActions) # FSM and TestSuite - enabledActions.update(enabledModelActions) # ModelProgam - if DEBUG: - print('enabledActions:') - self.pp.pprint(enabledActions) - - # list (with no duplicates) of all (aname, args, result) in enabledActions - # transitions should ideally be an ordered set. Unfortunately the built in - # set type is not guaranteed to be ordered (this appears to have changed in - # Python 3.7+), so we use fromkeys and list here as a workaround. (Could - # instead have used ordered-set from PyPi) - transitions = list(dict.fromkeys([(a.__name__, args, result) - for (a,args,result,next,properties) in - reduce(concat,list(enabledActions.values()))])) - if DEBUG: - print('transitions:') - self.pp.pprint(transitions) - - # dict from (aname, args, result) - # to set of all m where (aname, args, result) is enabled - # this dict can be compared to self.vocabularies - invocations = \ - dict([((aname, args, result), - set([ m for m in self.mp - if (aname,args,result) in - [(a.__name__, argsx, resultx) # argsx,resultx is inner loop - for (a,argsx,resultx,next,properties) in enabledActions[m]]])) - for (aname, args, result) in transitions ]) - - if DEBUG: - print('invocations:') - self.pp.pprint(invocations) - - # list of all (aname, args, result) that are enabled in the product - # (aname,args,result) enabled in product if (aname,args,result) is enabled - # or (aname,()) and None result is enabled in all m where aname is in vocab - # (would be nice to generalize to all prefixes of args, not just ()) - enabledAnameArgs = \ - [(aname, args, result) - for (aname, args, result) in transitions - # set union, maybe use ... .union(* ... ) for all prefixes - if invocations[aname,args,result] | invocations.get((aname,(),None), - set()) - == self.vocabularies[aname]] - - if DEBUG: - print('enabledAnameArgs:') - self.pp.pprint(enabledAnameArgs) - - # Now we have all enabled (action,args,result), now rearrange the data - - # for each enabled (aname,args), associate next states and properties by mp - # all enabled [(aname,args,result,{m1:(next1,properties1),m2:...}), ...] - enabledTs = \ - [(aname, args, result, - dict([(m, [(next,properties) - for (a,argsx,resultx,next,properties) in enabledActions[m] - # ...[0] to extract item from singleton list - if a.__name__ == aname - and (argsx == args or argsx == ())][0] - if m in # aname,args or aname,() is enabled in m - invocations[aname,args,result] | invocations.get((aname,(), - None),set()) - # a,args not enabled in m, m remains in same state - # (the following pair is the value for key m) - else (self.mp[m].Current(), self.mp[m].Properties())) - for m in self.mp ])) - for (aname, args, result) in enabledAnameArgs ] - - if DEBUG: - print('enabledTs:') - self.pp.pprint(enabledTs) - - # combine result and properties from all the mp - # list, all enabled [(aname,args,result,{m1:next1,m2:next2},properties),...] - mpEnabledTransitions = [(aname, args, result, - # dict of next states: {m:next1,m2:next2, ... } - dict([ (m,mdict[m][0]) for m in mdict ]), - # combined properties - self.NextProperties(dict([ (m,mdict[m][1]) - for m in mdict ]))) - for (aname,args,result,mdict) in enabledTs ] - return mpEnabledTransitions - - def Accepting(self): - return self.Properties()['accepting'] - - def StateInvariant(self): - return self.Properties()['stateinvariant'] - - # lots of nearly-repeated code in next two methods, can we streamline ... ? - - def Properties(self): - """ - Combine properties of mps in the current state - """ - return { 'accepting': - # all mp in the current state are in their accepting states - all([ m.Properties()['accepting'] for m in list(self.mp.values()) ]), - 'statefilter': - all([ m.Properties()['statefilter'] for m in list(self.mp.values()) ]), - 'stateinvariant': - all([ m.Properties()['stateinvariant'] for m in list(self.mp.values()) ]) - } - - def NextProperties(self, next_properties): - """ - Combine properties of mps in the next state - """ - return { 'accepting': - # all mp in the next state are in their accepting states - all([ next_properties[m]['accepting'] for m in next_properties]), - 'statefilter': - all([ next_properties[m]['statefilter'] for m in next_properties]), - 'stateinvariant': - all([ next_properties[m]['stateinvariant'] for m in next_properties]) - } - - def DoAction(self, aname, args): - """ - Execute action with aname in all the mp where it is enabled, - return result from last mp arg - """ - result = None - for m in list(self.mp.values()): - # aname might be an unshared action, not present in all mp - if aname in [ a.__name__ for a in m.actions ]: - result = m.DoAction(getattr(m.module, aname), args) - return result # results from all mp should be the same, return any one - - def Reset(self): - """ - Reset all the mp - """ - for m in list(self.mp.values()): - m.Reset() - - def Current(self): - """ - Return dictionary of current states - """ - return dict([(m, self.mp[m].Current()) for m in self.mp ]) - - def Restore(self, state): - """ - Restore states from dictionary - """ - for m in self.mp: - self.mp[m].Restore(state[m]) + + def __init__(self, options, args): + self.TestSuite = False # used by pmt nruns logic + self.module = dict() # dict of modules keyed by name + self.mp = dict() # dict of model programs keyed by same module name + sys.path.insert(0, os.getcwd()) + + if DEBUG: + self.pp = pprint.PrettyPrinter(width=120) + + # Populate self.module and self.mp from modules named in command line args + # Models that users write are just modules, not classes (types) + # so we find out what type to wrap each one in + # by checking for one of each type's required attributes using hasattr + for mname in args: # args is list of module name + self.module[mname] = importlib.import_module(mname) + if hasattr(self.module[mname], 'graph'): + self.mp[mname] = FSM(self.module[mname], options.exclude, options.action) + # for backwards compatibility we accept all of these test_suite variants + elif (hasattr(self.module[mname], 'testSuite') or + hasattr(self.module[mname], 'testsuite') or + hasattr(self.module[mname], 'test_suite')): + self.mp[mname] = TestSuite(self.module[mname], + options.exclude, options.action) + self.TestSuite = True # used by pmt nruns logic + elif self.module[mname].__doc__.strip().upper().startswith('PYMODEL CONFIG'): + pass # configuration module, merely importing it did all the work + else: + # got this far, should be a ModelProgram -- if not, crash + self.mp[mname] = ModelProgram(self.module[mname], + options.exclude, options.action) + + # Now that all modules have been imported and executed their __init__ + # do a postprocessing pass over all model objects + # to process metadata that might be affected by configuration modules + for mp in list(self.mp.values()): + mp.post_init() + + # set of all anames in all model programs - the vocabulary of the product + self.anames = set().union(*[set([a.__name__ for a in mp.actions]) + for mp in list(self.mp.values())]) + # print 'anames %s' % self.anames # DEBUG + + # set of anames of all observable actions in all model programs + # observables obtain arg values from the environment, not parameter gen. + self.observables = set().union(*[set([a.__name__ + for a in mp.module.observables]) + for mp in list(self.mp.values())]) + # FSM and TestSuite must have .observables + # print 'observables %s' % self.observables # DEBUG + + # dict from aname to set of all m where aname is in vocabulary + self.vocabularies = \ + dict([(aname, set([m for m in self.mp if aname in + [a.__name__ for a in self.mp[m].actions]])) + for aname in self.anames]) + # print 'vocabularies %s' % self.vocabularies # DEBUG + + # ProductModelProgram only provides methods etc. called by test runner etc.: + # EnabledActions(cleanup), Properties(), DoAction(a,args), Reset(), TestSuite + # BUT not methods used internally in mp classes: Churrent, Restore, GetNext + + def ActionEnabled(self, aname, args): + """ + True if action aname with args is enabled in the current state + """ + return all([m.ActionEnabled(getattr(m.module, aname), args) + # NOT! empty argument list in model matches any arguments + # NOT! or m.ActionEnabled(getattr(m.module, aname), ()) + # handle zero-args/match-all inside m.ActionEnabled + for m in list(self.mp.values()) + # aname might be an unshared action, not present in all mp + if aname in [a.__name__ for a in m.actions]]) + + def EnabledTransitions(self, cleanup): + """ + This is where composition happens! + (aname,args,result) is enabled in the product if it is enabled, + OR (aname, (), None) with empty args and None result is enabled + in every machine where aname is in the vocabulary. + Returns list: [(aname, args, result, next, properties), ... ] + third item result is the same value from all mp, or None + fourth item next is dict of mp name to that mp's next state: + (m1:next1,m2:current2,m3:next3,...),...] + or to its current state if aname is not in that mp vocabulary + fifth item properties is dict of property name to value in next state: + { 'accepting': True, 'statefilter': True, ... } + where there is just one value for each property for the whole product + """ + # Call ParamGen here to allow for state-dependent parameter generation + for mp in list(self.mp.values()): + if isinstance(mp, ModelProgram): + mp.ParamGen() + + # Built up dicts from mp name to list of all its enabled + # (action, args, result, next state, properties) + + # dict for FSM and TestSuite only, they might provide args for observables + enabledScenarioActions = \ + dict([(m, self.mp[m].EnabledTransitions(cleanup)) + for m in self.mp if (not isinstance(self.mp[m], ModelProgram))]) + + if DEBUG: + print('enabledScenarioActions %s' % enabledScenarioActions) + + # dict from action to sequence of argslists + argslists = defaultdict(list) + for transitions in list(enabledScenarioActions.values()): + for (action, args, result, next_state, properties) in transitions: + argslists[action].append(args) # append not extend + + # If more than one scenario in product, there may be duplicates - use sets + scenarioArgslists = dict([(action, set(args)) + for (action, args) in list(argslists.items())]) + if DEBUG: + print('scenarioArgslists %s' % scenarioArgslists) + + # Pass scenarioArgslists to ModelProgram EnabledTransitions + # so any observable actions can use these argslists + enabledModelActions = \ + dict([(m, self.mp[m].EnabledTransitions(scenarioArgslists, cleanup)) + for m in self.mp if isinstance(self.mp[m], ModelProgram)]) + if DEBUG: + print('enabledModelActions:') + self.pp.pprint(enabledModelActions) + + # Combine enabled actions dictionaries (they have distinct keys) + enabledActions = dict() + enabledActions.update(enabledScenarioActions) # FSM and TestSuite + enabledActions.update(enabledModelActions) # ModelProgam + if DEBUG: + print('enabledActions:') + self.pp.pprint(enabledActions) + + # list (with no duplicates) of all (aname, args, result) in enabledActions + # transitions should ideally be an ordered set. Unfortunately the built in + # set type is not guaranteed to be ordered (this appears to have changed in + # Python 3.7+), so we use fromkeys and list here as a workaround. (Could + # instead have used ordered-set from PyPi) + transitions = list(dict.fromkeys([(a.__name__, args, result) + for (a, args, result, next, properties) in + reduce(concat, list(enabledActions.values()))])) + if DEBUG: + print('transitions:') + self.pp.pprint(transitions) + + # dict from (aname, args, result) + # to set of all m where (aname, args, result) is enabled + # this dict can be compared to self.vocabularies + invocations = \ + dict([((aname, args, result), + set([m for m in self.mp + if (aname, args, result) in + [(a.__name__, argsx, resultx) # argsx,resultx is inner loop + for (a, argsx, resultx, next, properties) in enabledActions[m]]])) + for (aname, args, result) in transitions]) + + if DEBUG: + print('invocations:') + self.pp.pprint(invocations) + + # list of all (aname, args, result) that are enabled in the product + # (aname,args,result) enabled in product if (aname,args,result) is enabled + # or (aname,()) and None result is enabled in all m where aname is in vocab + # (would be nice to generalize to all prefixes of args, not just ()) + enabledAnameArgs = \ + [(aname, args, result) + for (aname, args, result) in transitions + # set union, maybe use ... .union(* ... ) for all prefixes + if invocations[aname, args, result] | invocations.get((aname, (), None), + set()) + == self.vocabularies[aname]] + + if DEBUG: + print('enabledAnameArgs:') + self.pp.pprint(enabledAnameArgs) + + # Now we have all enabled (action,args,result), now rearrange the data + + # for each enabled (aname,args), associate next states and properties by mp + # all enabled [(aname,args,result,{m1:(next1,properties1),m2:...}), ...] + enabledTs = \ + [(aname, args, result, + dict([(m, [(next, properties) + for (a, argsx, resultx, next, properties) in enabledActions[m] + # ...[0] to extract item from singleton list + if a.__name__ == aname + and (argsx == args or argsx == ())][0] + if m in # aname,args or aname,() is enabled in m + invocations[aname, args, result] | invocations.get((aname, (), + None), set()) + # a,args not enabled in m, m remains in same state + # (the following pair is the value for key m) + else (self.mp[m].Current(), self.mp[m].Properties())) + for m in self.mp])) + for (aname, args, result) in enabledAnameArgs] + + if DEBUG: + print('enabledTs:') + self.pp.pprint(enabledTs) + + # combine result and properties from all the mp + # list, all enabled [(aname,args,result,{m1:next1,m2:next2},properties),...] + mpEnabledTransitions = [(aname, args, result, + # dict of next states: {m:next1,m2:next2, ... } + dict([(m, mdict[m][0]) for m in mdict]), + # combined properties + self.NextProperties(dict([(m, mdict[m][1]) + for m in mdict]))) + for (aname, args, result, mdict) in enabledTs] + return mpEnabledTransitions + + def Accepting(self): + return self.Properties()['accepting'] + + def StateInvariant(self): + return self.Properties()['stateinvariant'] + + # lots of nearly-repeated code in next two methods, can we streamline ... ? + + def Properties(self): + """ + Combine properties of mps in the current state + """ + return {'accepting': + # all mp in the current state are in their accepting states + all([m.Properties()['accepting'] for m in list(self.mp.values())]), + 'statefilter': + all([m.Properties()['statefilter'] for m in list(self.mp.values())]), + 'stateinvariant': + all([m.Properties()['stateinvariant'] for m in list(self.mp.values())]) + } + + def NextProperties(self, next_properties): + """ + Combine properties of mps in the next state + """ + return {'accepting': + # all mp in the next state are in their accepting states + all([next_properties[m]['accepting'] for m in next_properties]), + 'statefilter': + all([next_properties[m]['statefilter'] for m in next_properties]), + 'stateinvariant': + all([next_properties[m]['stateinvariant'] for m in next_properties]) + } + + def DoAction(self, aname, args): + """ + Execute action with aname in all the mp where it is enabled, + return result from last mp arg + """ + result = None + for m in list(self.mp.values()): + # aname might be an unshared action, not present in all mp + if aname in [a.__name__ for a in m.actions]: + result = m.DoAction(getattr(m.module, aname), args) + return result # results from all mp should be the same, return any one + + def Reset(self): + """ + Reset all the mp + """ + for m in list(self.mp.values()): + m.Reset() + + def Current(self): + """ + Return dictionary of current states + """ + return dict([(m, self.mp[m].Current()) for m in self.mp]) + + def Restore(self, state): + """ + Restore states from dictionary + """ + for m in self.mp: + self.mp[m].Restore(state[m]) diff --git a/pymodel/README.md b/pymodel/README.md old mode 100644 new mode 100755 diff --git a/pymodel/StateCoverage.py b/pymodel/StateCoverage.py old mode 100644 new mode 100755 diff --git a/pymodel/TestSuite.py b/pymodel/TestSuite.py old mode 100644 new mode 100755 diff --git a/pymodel/TesterOptions.py b/pymodel/TesterOptions.py old mode 100644 new mode 100755 diff --git a/pymodel/ViewerOptions.py b/pymodel/ViewerOptions.py old mode 100644 new mode 100755 diff --git a/pymodel/__init__.py b/pymodel/__init__.py old mode 100644 new mode 100755 diff --git a/pymodel/model.py b/pymodel/model.py old mode 100644 new mode 100755 diff --git a/pymodel/observation_queue.py b/pymodel/observation_queue.py old mode 100644 new mode 100755 diff --git a/pymodel/pmg.py b/pymodel/pmg.py index 363d3ae..65f2444 100755 --- a/pymodel/pmg.py +++ b/pymodel/pmg.py @@ -2,9 +2,12 @@ """ PyModel Graphics - generate graphics from pymodel FSM """ +import os.path from pymodel import GraphicsOptions from pymodel.Dot import dotfile +import importlib +import sys def main(): (options, args) = GraphicsOptions.parse_args() @@ -12,7 +15,8 @@ def main(): GraphicsOptions.print_help() exit() else: - fsm = __import__(args[0]) + sys.path.append(os.path.dirname(os.path.abspath(args[0]))) + fsm = importlib.import_module(args[0]) fbasename = options.output if options.output else args[0] fname = '%s.dot' % fbasename dotfile(fname, fsm, options.transitionLabels, options.noStateTooltip, diff --git a/pymodel/trun.py b/pymodel/trun.py index 91b0494..f53ef2c 100755 --- a/pymodel/trun.py +++ b/pymodel/trun.py @@ -2,6 +2,7 @@ import sys import os +import importlib # argv[1] is name of module containing test cases # dir containing this module must be on PYTHONPATH @@ -11,7 +12,8 @@ sys.exit() try: - test = __import__(sys.argv[1]) + sys.path.append(os.path.curdir) + test = importlib.import_module(sys.argv[1]) except ModuleNotFoundError: print('\nCould not find tests file "{}".\n\n'.format(sys.argv[1])) sys.exit() diff --git a/pymodel/wsgidemo.py b/pymodel/wsgidemo.py old mode 100644 new mode 100755 diff --git a/samples/PowerSwitch/SpeedControlFSM.py b/samples/PowerSwitch/SpeedControlFSM.py new file mode 100644 index 0000000..e37c00e --- /dev/null +++ b/samples/PowerSwitch/SpeedControlFSM.py @@ -0,0 +1,33 @@ + +# pma SpeedControl +# 3 states, 3 transitions, 1 accepting states, 0 unsafe states, 0 finished and 0 deadend states + +# actions here are just labels, but must be symbols with __name__ attribute + +def IncrementSpeed(): pass + +# states, key of each state here is its number in graph etc. below + +states = { + 0 : {'SpeedControl': 0}, + 1 : {'SpeedControl': 1}, + 2 : {'SpeedControl': 2}, +} + +# initial state, accepting states, unsafe states, frontier states, deadend states + +initial = 0 +accepting = [0] +unsafe = [] +frontier = [] +finished = [] +deadend = [] +runstarts = [0] + +# finite state machine, list of tuples: (current, (action, args, result), next) + +graph = ( + (0, (IncrementSpeed, (), None), 1), + (1, (IncrementSpeed, (), None), 2), + (2, (IncrementSpeed, (), None), 0), +)