From e0d404e122202c25c85dcebedcbd567837068b65 Mon Sep 17 00:00:00 2001 From: Python3pkg Date: Mon, 22 May 2017 10:29:25 -0700 Subject: [PATCH 01/11] Convert to Python3 --- pymodel/FSM.py | 206 +++---- pymodel/ModelProgram.py | 389 ++++++------- pymodel/ProductModelProgram.py | 559 ++++++++++--------- pymodel/TestSuite.py | 235 ++++---- pymodel/pma.py | 10 +- pymodel/pmg.py | 4 +- pymodel/pmt.py | 18 +- pymodel/pmv.py | 6 +- pymodel/trun.py | 4 +- pymodel/wsgirunner.py | 4 +- samples/Socket/stepper.py | 2 +- samples/Socket/stepper_a.py | 2 +- samples/Socket/stepper_d.py | 2 +- samples/Socket/stepper_o.py | 2 +- samples/Socket/stepper_util.py | 8 +- samples/Timeout/Stepper.py | 2 +- samples/WebApplication/Session.py | 160 +++--- samples/WebApplication/Stepper.py | 284 +++++----- samples/WebApplication/WSGIVerboseConfig.py | 2 +- samples/WebApplication/webapp.py | 358 ++++++------ samples/tracemultiplexer/tracemultiplexer.py | 410 +++++++------- 21 files changed, 1335 insertions(+), 1332 deletions(-) diff --git a/pymodel/FSM.py b/pymodel/FSM.py index 2fcef4a..2c04987 100644 --- a/pymodel/FSM.py +++ b/pymodel/FSM.py @@ -1,103 +1,103 @@ -""" -Interface to an FSM module (graph) used by ProductModelProgram -""" - -from model import Model - -class FSM(Model): - - def __init__(self, module, exclude, include): - Model.__init__(self, module, exclude, include) - - def post_init(self): - """ - Now that all modules have been imported and executed their __init__ - do a postprocessing pass - to process metadata that might be affected by configuration modules - """ - # Do all of this work here rather than in __init__ - # so it can include the effects of any pymodel config modules - - # Make copies of collections that may be altered later - # self.actions is not used in this module outside this __init__ - # BUT it is used in several places in Model and ProductModelProgram - # EnabledTransitions below works directly on self.module.graph, - # not self.actions - if not hasattr(self.module, 'actions'): - self.actions = list(self.actions_in_graph()) # default, make copy - else: - self.actions = list(self.module.actions) # copy - Model.post_init(self) # uses self.actions - # Construct self.graph like self.module.graph - # BUT also accounts for include, exclude via self.actions - self.graph = [ (current, (action,args,result), next) - for (current, (action,args,result), next) in - self.module.graph if action in self.actions ] - # prepare for first run - self.current = self.module.initial # raise exception if module is not FSM - - - def actions_in_graph(self): - return tuple(set([ action for (current, (action,args,result), next) in - self.module.graph])) # not self.graph, here ONLY - - def make_properties(self, state): - return { 'accepting': state in self.module.accepting, 'statefilter': True, - 'stateinvariant': True } - - def Properties(self): - return self.make_properties(self.current) - - def Reset(self): # needed by stepper - self.current = self.module.initial - - def CleanupGraph(self, cleanup=False): - """ - if cleanup, return the graph with just the cleanup transitions - """ - if cleanup: - graph = [(current,(action,args,result),next) - for (current,(action,args,result),next) in self.graph - if action in self.module.cleanup] - else: - graph = self.graph - return graph - - def ActionEnabled(self, a, args): - """ - action a with args is enabled in the current state - """ - # no cleanup check here - # any args matches empty arguments in FSM - return any([(a == action and (not arguments or args == arguments)) - for (current,(action, arguments, result),next) - in self.graph - if current == self.current ]) - - def EnabledTransitions(self, cleanup=False): - """ - Return list tuples for all enabled actions: - (action, args, next state, properties) - """ - graph = self.CleanupGraph(cleanup) - return [(action, args, result, next, self.make_properties(next)) - for (current,(action,args,result),next) in graph - if current == self.current ] - - def DoAction(self, a, arguments): - ts = [(current,(action,args,result),next) - for (current,(action,args,result),next) in self.graph - if current == self.current and action == a - and args == arguments[0:len(args)]] #ignore extra trailing args - # print 'List ts %s' % ts # DEBUG - # Might be nondet. FSM: for now, just pick first transition that matches - current, (action,args,result), self.current = ts[0] - return result - - def Current(self): - return self.current - - def Restore(self, state): - self.current = state - - # GetNext not needed +""" +Interface to an FSM module (graph) used by ProductModelProgram +""" + +from .model import Model + +class FSM(Model): + + def __init__(self, module, exclude, include): + Model.__init__(self, module, exclude, include) + + def post_init(self): + """ + Now that all modules have been imported and executed their __init__ + do a postprocessing pass + to process metadata that might be affected by configuration modules + """ + # Do all of this work here rather than in __init__ + # so it can include the effects of any pymodel config modules + + # Make copies of collections that may be altered later + # self.actions is not used in this module outside this __init__ + # BUT it is used in several places in Model and ProductModelProgram + # EnabledTransitions below works directly on self.module.graph, + # not self.actions + if not hasattr(self.module, 'actions'): + self.actions = list(self.actions_in_graph()) # default, make copy + else: + self.actions = list(self.module.actions) # copy + Model.post_init(self) # uses self.actions + # Construct self.graph like self.module.graph + # BUT also accounts for include, exclude via self.actions + self.graph = [ (current, (action,args,result), next) + for (current, (action,args,result), next) in + self.module.graph if action in self.actions ] + # prepare for first run + self.current = self.module.initial # raise exception if module is not FSM + + + def actions_in_graph(self): + return tuple(set([ action for (current, (action,args,result), next) in + self.module.graph])) # not self.graph, here ONLY + + def make_properties(self, state): + return { 'accepting': state in self.module.accepting, 'statefilter': True, + 'stateinvariant': True } + + def Properties(self): + return self.make_properties(self.current) + + def Reset(self): # needed by stepper + self.current = self.module.initial + + def CleanupGraph(self, cleanup=False): + """ + if cleanup, return the graph with just the cleanup transitions + """ + if cleanup: + graph = [(current,(action,args,result),next) + for (current,(action,args,result),next) in self.graph + if action in self.module.cleanup] + else: + graph = self.graph + return graph + + def ActionEnabled(self, a, args): + """ + action a with args is enabled in the current state + """ + # no cleanup check here + # any args matches empty arguments in FSM + return any([(a == action and (not arguments or args == arguments)) + for (current,(action, arguments, result),next) + in self.graph + if current == self.current ]) + + def EnabledTransitions(self, cleanup=False): + """ + Return list tuples for all enabled actions: + (action, args, next state, properties) + """ + graph = self.CleanupGraph(cleanup) + return [(action, args, result, next, self.make_properties(next)) + for (current,(action,args,result),next) in graph + if current == self.current ] + + def DoAction(self, a, arguments): + ts = [(current,(action,args,result),next) + for (current,(action,args,result),next) in self.graph + if current == self.current and action == a + and args == arguments[0:len(args)]] #ignore extra trailing args + # print 'List ts %s' % ts # DEBUG + # Might be nondet. FSM: for now, just pick first transition that matches + current, (action,args,result), self.current = ts[0] + return result + + def Current(self): + return self.current + + def Restore(self, state): + self.current = state + + # GetNext not needed diff --git a/pymodel/ModelProgram.py b/pymodel/ModelProgram.py index 64c0e16..9fce372 100644 --- a/pymodel/ModelProgram.py +++ b/pymodel/ModelProgram.py @@ -1,194 +1,195 @@ -""" -Interface to a model program (python module) used by ProductModelProgram -""" - -# Actions a are still function objects (not aname strings) here -# because composition happens in ProductModelProgram, a level above. - -import sys -import copy -import inspect -import itertools -from model import Model - -class ModelProgram(Model): - - def __init__(self, module, exclude, include): - Model.__init__(self, module, exclude, include) - - def post_init(self): - """ - Now that all modules have been imported and executed their __init__ - do a postprocessing pass - to process metadata that might be affected by configuration modules - """ - # Do all of this work here rather than in __init__ - # so it can include the effects of any pymodel config modules - - # recognize PEP-8 style names (all lowercase) if present - if hasattr(self.module, 'accepting'): - self.module.Accepting = self.module.accepting - if hasattr(self.module, 'statefilter'): - self.module.StateFilter = self.module.statefilter - if hasattr(self.module, 'state_filter'): - self.module.StateFilter = self.module.state_filter - if hasattr(self.module, 'stateinvariant'): - self.module.StateInvariant = self.module.stateinvariant - if hasattr(self.module, 'state_invariant'): - self.module.StateInvariant = self.module.state_invariant - if hasattr(self.module, 'reset'): - self.module.Reset = self.module.reset - - # assign defaults to optional attributes - # cleanup and observables are handled in Models base class - if not hasattr(self.module, 'enablers'): - self.module.enablers = dict() # all actions always enabled - if not hasattr(self.module, 'domains'): - self.module.domains = dict() # empty domains - if not hasattr(self.module, 'combinations'): - self.module.combinations = dict() # 'all', Cartesian product - if not hasattr(self.module, 'Accepting'): - self.module.Accepting = self.TrueDefault - if not hasattr(self.module, 'StateFilter'): - self.module.StateFilter = self.TrueDefault - if not hasattr(self.module, 'StateInvariant'): - self.module.StateInvariant = self.TrueDefault - - # Make copies of collections that may be altered by post_init - self.actions = list(self.module.actions) - Model.post_init(self) # uses self.actions - - - 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) - if arginfo[0]: - args = arginfo[0] # usual case: fixed sequence of positional arguments - elif arginfo[1]: - args = [arginfo[1]] # special case: either no arg, or one exception arg - else: - args = () # no arguments anywhere, args must have this value - domains = [ self.module.domains[a][arg]() # evaluate state-dependent domain - if callable(self.module.domains[a][arg]) - 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' - if combination == 'cases': # as many args as items in smallest domain - argslists = zip(*domains) - elif combination == 'all': # Cartesian product - argslists = itertools.product(*domains) - # might be nice to support 'pairwise' also - # return tuple not list, hashable so it can be key in dictionary - # also handle special case (None,) indicates empty argslist in domains - return tuple([() if x == (None,) else x for x in argslists ]) - - def ParamGen(self): - #print 'ModelProgram ParamGen for', self.module.__name__ #DEBUG - #print ' actions ', self.actions - #print ' domains ', self.module.domains - self.argslists = dict([(a, self.make_argslist(a)) - for a in self.actions - if not a in self.module.observables ]) - - def TrueDefault(self): - return True - - def Properties(self): - return { 'accepting': self.module.Accepting(), - 'statefilter': self.module.StateFilter(), - 'stateinvariant': self.module.StateInvariant() } - - def Reset(self): - try: - self.module.Reset() - except AttributeError: # Reset is optional, but there is no default - print 'No Reset function for model program %s' % self.module.__name__ - sys.exit() - - def ActionEnabled(self, a, args): - """ - action a with args is enabled in the current state - """ - if a not in self.module.enablers: - return True - 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]) - nargs = len(args) - # nparams == 0 means match any args - if nparams > 0 and nparams != nargs: - print 'Error: %s needs %s arguments, got %s. Check parameter generator.' %\ - (a_enabled.__name__, nparams, nargs) - sys.exit(1) # Don't return, just exit with error status - else: - if nparams > 0: - return a_enabled(*args) - else: - return a_enabled() # nparams == 0 means match any args - - def Transitions(self, actions, argslists): - """ - Return tuple for all enabled transitions: - (action, args, result, next state, properties) - Pass appropriate params for observable or controllable actions + argslists - """ - enabled = list() - for a in actions: - enabled += [(a, args) + self.GetNext(a,args) # (a,args,result,next,prop's) - for args in argslists[a] if self.ActionEnabled(a, args) ] - return [(a,args,result,next,properties) - for (a,args,result,next,properties) in enabled - if properties['statefilter']] - - def EnabledTransitions(self, argslists, cleanup=False): - """ - Return tuple for all enabled observable and controllable transitions: - (action, args, result, next state, properties) - """ - actions = self.cleanup if cleanup else self.actions - controllableActions = set(actions) - set(self.module.observables) - observableActions = set(argslists.keys()) & set(self.module.observables) - if cleanup: - observableActions = set(observableActions) & set(self.cleanup) - enabled = list() - # Controllable actions use self.argslists assigned by ParamGen - enabled += self.Transitions(controllableActions, self.argslists) - # Observable actions use argslists parameter here - enabled += self.Transitions(observableActions, argslists) - return enabled - - def DoAction(self, a, args): - """ - Execute action in model, update state, - """ - return a(*args) - - def Current(self): - """ - Return current state, a dictionary of variable names to values - This is used to save/restore states, so make deep copies of values - """ - return dict([(vname, copy.deepcopy(getattr(self.module, vname))) - for vname in self.module.state ]) - - def Restore(self, state): - """ - Restore state - """ - self.module.__dict__.update(state) - - def GetNext(self, a, args): - """ - Return result and next state, given action and args. - Nondestructive, restores state. - also return dict. of properties of next state as third element in tuple - """ - saved = self.Current() - result = self.DoAction(a, args) - next = self.Current() - properties = self.Properties() # accepting state, etc. - self.Restore(saved) - return (result, next, properties) +""" +Interface to a model program (python module) used by ProductModelProgram +""" + +# Actions a are still function objects (not aname strings) here +# because composition happens in ProductModelProgram, a level above. + +import sys +import copy +import inspect +import itertools +from .model import Model +import collections + +class ModelProgram(Model): + + def __init__(self, module, exclude, include): + Model.__init__(self, module, exclude, include) + + def post_init(self): + """ + Now that all modules have been imported and executed their __init__ + do a postprocessing pass + to process metadata that might be affected by configuration modules + """ + # Do all of this work here rather than in __init__ + # so it can include the effects of any pymodel config modules + + # recognize PEP-8 style names (all lowercase) if present + if hasattr(self.module, 'accepting'): + self.module.Accepting = self.module.accepting + if hasattr(self.module, 'statefilter'): + self.module.StateFilter = self.module.statefilter + if hasattr(self.module, 'state_filter'): + self.module.StateFilter = self.module.state_filter + if hasattr(self.module, 'stateinvariant'): + self.module.StateInvariant = self.module.stateinvariant + if hasattr(self.module, 'state_invariant'): + self.module.StateInvariant = self.module.state_invariant + if hasattr(self.module, 'reset'): + self.module.Reset = self.module.reset + + # assign defaults to optional attributes + # cleanup and observables are handled in Models base class + if not hasattr(self.module, 'enablers'): + self.module.enablers = dict() # all actions always enabled + if not hasattr(self.module, 'domains'): + self.module.domains = dict() # empty domains + if not hasattr(self.module, 'combinations'): + self.module.combinations = dict() # 'all', Cartesian product + if not hasattr(self.module, 'Accepting'): + self.module.Accepting = self.TrueDefault + if not hasattr(self.module, 'StateFilter'): + self.module.StateFilter = self.TrueDefault + if not hasattr(self.module, 'StateInvariant'): + self.module.StateInvariant = self.TrueDefault + + # Make copies of collections that may be altered by post_init + self.actions = list(self.module.actions) + Model.post_init(self) # uses self.actions + + + 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) + if arginfo[0]: + args = arginfo[0] # usual case: fixed sequence of positional arguments + elif arginfo[1]: + args = [arginfo[1]] # special case: either no arg, or one exception arg + 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) + 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' + if combination == 'cases': # as many args as items in smallest domain + argslists = list(zip(*domains)) + elif combination == 'all': # Cartesian product + argslists = itertools.product(*domains) + # might be nice to support 'pairwise' also + # return tuple not list, hashable so it can be key in dictionary + # also handle special case (None,) indicates empty argslist in domains + return tuple([() if x == (None,) else x for x in argslists ]) + + def ParamGen(self): + #print 'ModelProgram ParamGen for', self.module.__name__ #DEBUG + #print ' actions ', self.actions + #print ' domains ', self.module.domains + self.argslists = dict([(a, self.make_argslist(a)) + for a in self.actions + if not a in self.module.observables ]) + + def TrueDefault(self): + return True + + def Properties(self): + return { 'accepting': self.module.Accepting(), + 'statefilter': self.module.StateFilter(), + 'stateinvariant': self.module.StateInvariant() } + + def Reset(self): + try: + self.module.Reset() + except AttributeError: # Reset is optional, but there is no default + print('No Reset function for model program %s' % self.module.__name__) + sys.exit() + + def ActionEnabled(self, a, args): + """ + action a with args is enabled in the current state + """ + if a not in self.module.enablers: + return True + 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]) + nargs = len(args) + # nparams == 0 means match any args + if nparams > 0 and nparams != nargs: + print('Error: %s needs %s arguments, got %s. Check parameter generator.' %\ + (a_enabled.__name__, nparams, nargs)) + sys.exit(1) # Don't return, just exit with error status + else: + if nparams > 0: + return a_enabled(*args) + else: + return a_enabled() # nparams == 0 means match any args + + def Transitions(self, actions, argslists): + """ + Return tuple for all enabled transitions: + (action, args, result, next state, properties) + Pass appropriate params for observable or controllable actions + argslists + """ + enabled = list() + for a in actions: + enabled += [(a, args) + self.GetNext(a,args) # (a,args,result,next,prop's) + for args in argslists[a] if self.ActionEnabled(a, args) ] + return [(a,args,result,next,properties) + for (a,args,result,next,properties) in enabled + if properties['statefilter']] + + def EnabledTransitions(self, argslists, cleanup=False): + """ + Return tuple for all enabled observable and controllable transitions: + (action, args, result, next state, properties) + """ + actions = self.cleanup if cleanup else self.actions + controllableActions = set(actions) - set(self.module.observables) + observableActions = set(argslists.keys()) & set(self.module.observables) + if cleanup: + observableActions = set(observableActions) & set(self.cleanup) + enabled = list() + # Controllable actions use self.argslists assigned by ParamGen + enabled += self.Transitions(controllableActions, self.argslists) + # Observable actions use argslists parameter here + enabled += self.Transitions(observableActions, argslists) + return enabled + + def DoAction(self, a, args): + """ + Execute action in model, update state, + """ + return a(*args) + + def Current(self): + """ + Return current state, a dictionary of variable names to values + This is used to save/restore states, so make deep copies of values + """ + return dict([(vname, copy.deepcopy(getattr(self.module, vname))) + for vname in self.module.state ]) + + def Restore(self, state): + """ + Restore state + """ + self.module.__dict__.update(state) + + def GetNext(self, a, args): + """ + Return result and next state, given action and args. + Nondestructive, restores state. + also return dict. of properties of next state as third element in tuple + """ + saved = self.Current() + result = self.DoAction(a, args) + next = self.Current() + properties = self.Properties() # accepting state, etc. + self.Restore(saved) + return (result, next, properties) diff --git a/pymodel/ProductModelProgram.py b/pymodel/ProductModelProgram.py index a374a20..96f6bf1 100644 --- a/pymodel/ProductModelProgram.py +++ b/pymodel/ProductModelProgram.py @@ -1,279 +1,280 @@ -""" -ProductModelProgram - -Uniform interface to every kind of model: ModelProgram, FSM, TestSuite. - -This module is used by both the analyzer and the tester. - -This module uses *composition* to construct and use the *product* of -all the models in the session. - -This module performs composition, so it must identify actions by name -strings (which are the same in all the composed models) not function -objects (which are specific to each model's module). - -Users of this module identify actions by aname strings. -Modules used by this module invoke action function objects. -This module translates action function a to string aname: aname = a.__name__ -and translates aname string to action function a: a = getattr(module, aname) -""" - -from operator import concat -from collections import defaultdict - -from FSM import FSM -from TestSuite import TestSuite -from ModelProgram import ModelProgram - -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 - - # 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 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 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 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 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 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))]) - # print 'enabledScenarioActions %s' % enabledScenarioActions # DEBUG - - # dict from action to sequence of argslists - argslists = defaultdict(list) - for transitions in 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 argslists.items()]) - # 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)]) - # print 'enabledModelActions %s' % enabledModelActions # DEBUG - - # Combine enabled actions dictionaries (they have distinct keys) - enabledActions = dict() - enabledActions.update(enabledScenarioActions) # FSM and TestSuite - enabledActions.update(enabledModelActions) # ModelProgam - # print 'enabledActions %s' % enabledActions # DEBUG - - # set (with no duplicates) of all (aname, args, result) in enabledActions - transitions = set([(a.__name__, args, result) - for (a,args,result,next,properties) in - reduce(concat,enabledActions.values())]) - # print 'transitions %s' % 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 ]) - # print 'invocations %s' % invocations # DEBUG - - # 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]] - - # 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 ] - - # print 'enabledTs %s' % enabledTs # DEBUG - - # 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 self.mp.values() ]), - 'statefilter': - all([ m.Properties()['statefilter'] for m in self.mp.values() ]), - 'stateinvariant': - all([ m.Properties()['stateinvariant'] for m in 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 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 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]) +""" +ProductModelProgram + +Uniform interface to every kind of model: ModelProgram, FSM, TestSuite. + +This module is used by both the analyzer and the tester. + +This module uses *composition* to construct and use the *product* of +all the models in the session. + +This module performs composition, so it must identify actions by name +strings (which are the same in all the composed models) not function +objects (which are specific to each model's module). + +Users of this module identify actions by aname strings. +Modules used by this module invoke action function objects. +This module translates action function a to string aname: aname = a.__name__ +and translates aname string to action function a: a = getattr(module, aname) +""" + +from operator import concat +from collections import defaultdict + +from .FSM import FSM +from .TestSuite import TestSuite +from .ModelProgram import ModelProgram +from functools import reduce + +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 + + # 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))]) + # print 'enabledScenarioActions %s' % enabledScenarioActions # DEBUG + + # 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())]) + # 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)]) + # print 'enabledModelActions %s' % enabledModelActions # DEBUG + + # Combine enabled actions dictionaries (they have distinct keys) + enabledActions = dict() + enabledActions.update(enabledScenarioActions) # FSM and TestSuite + enabledActions.update(enabledModelActions) # ModelProgam + # print 'enabledActions %s' % enabledActions # DEBUG + + # set (with no duplicates) of all (aname, args, result) in enabledActions + transitions = set([(a.__name__, args, result) + for (a,args,result,next,properties) in + reduce(concat,list(enabledActions.values()))]) + # print 'transitions %s' % 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 ]) + # print 'invocations %s' % invocations # DEBUG + + # 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]] + + # 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 ] + + # print 'enabledTs %s' % enabledTs # DEBUG + + # 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/TestSuite.py b/pymodel/TestSuite.py index 68e9e49..f709b3b 100644 --- a/pymodel/TestSuite.py +++ b/pymodel/TestSuite.py @@ -1,117 +1,118 @@ -""" -Interface to a test suite module (one or more runs) used by ProductModelProgram -""" - -from operator import concat -from model import Model - -class TestSuite(Model): - - def __init__(self, module, exclude, include): - Model.__init__(self, module, exclude, include) - - def post_init(self): - """ - Now that all modules have been imported and executed their __init__ - do a postprocessing pass - to process metadata that might be affected by configuration modules - """ - # Do all of this work here rather than in __init__ - # so it can include the effects of any pymodel config modules - - # recognize PEP-8 style names (all lowercase) if present - if hasattr(self.module, 'testsuite'): - self.module.testSuite = self.module.testsuite - if hasattr(self.module, 'test_suite'): - self.module.testSuite = self.module.test_suite - - if hasattr(self.module, 'actions'): - self.actions = list(self.module.actions) # copy, actions from cmd line - else: - self.actions = list(self.actions_in_suite()) # default, copy - Model.post_init(self) # uses self.actions - # Revise the test suite to account for excluded, included actions - self.test_suite = list() - for run in self.module.testSuite: - new_run = list() # list not tuple, must mutable - for action in run: - if action[0] in self.actions: - new_run.append(action) - else: - break # truncate the run before the excluded action - self.test_suite.append(new_run) - # prepare for first run - self.irun = 0 # index of current test run in test suite - self.pc = 0 # program counter - - - def actions_in_suite(self): - # there might be two or three items in action_tuple - return tuple(set(reduce(concat,[[action_tuple[0] for action_tuple in run] - for run in self.module.testSuite]))) - - def Accepting(self): - # In a test suite, the only accepting states are at ends of runs - # NB Here Accepting() is called *after* DoAction() that advances self.pc - length = len(self.test_suite[self.irun]) # number of tuples in run - return (self.pc == length) - - def make_properties(self, accepting): - return { 'accepting': accepting, 'statefilter': True, - 'stateinvariant': True } - - def Properties(self): - return self.make_properties(self.Accepting()) - - def Reset(self): # needed by stepper - self.pc = 0 - if self.irun < len(self.test_suite) - 1: - self.irun += 1 - else: - raise StopIteration # no more runs in test suite - - def ActionEnabled(self, a, args): - """ - action a with args is enabled in the current state - """ - step = self.test_suite[self.irun][self.pc] - action, arguments = step[0:2] # works whether or not step has result - return (a == action and args == arguments) - - def EnabledTransitions(self, cleanup=False): - """ - Return list of all tuples for enabled actions. Here, there is just one. - (action, args, next state, next state is accepting state) - Next state is a list of two elements:the run number and step within the run - In a test suite, there is always just *one* next action, or *none* - Ignore cleanup, test suite should always end in accepting state. - """ - run = self.test_suite[self.irun] - length = len(run) - if self.pc < length: - step = run[self.pc] - action, args = step[0:2] - result = step[2] if len(step) > 2 else None # result is optional - next = self.pc + 1 - accepting = (next == length) - return([(action, args, result, (self.irun,next), - self.make_properties(accepting))]) - else: - return list() # test run finished, nothing enabled, - - def DoAction(self, a, args): - step = self.test_suite[self.irun][self.pc] - result = step[2] if len(step) > 2 else None # result is optional - self.pc += 1 - return result - - def Current(self): - return (self.irun, self.pc) - - def Restore(self, state): - """ - Restore state - """ - self.irun, self.pc = state - - # GetNext not needed +""" +Interface to a test suite module (one or more runs) used by ProductModelProgram +""" + +from operator import concat +from .model import Model +from functools import reduce + +class TestSuite(Model): + + def __init__(self, module, exclude, include): + Model.__init__(self, module, exclude, include) + + def post_init(self): + """ + Now that all modules have been imported and executed their __init__ + do a postprocessing pass + to process metadata that might be affected by configuration modules + """ + # Do all of this work here rather than in __init__ + # so it can include the effects of any pymodel config modules + + # recognize PEP-8 style names (all lowercase) if present + if hasattr(self.module, 'testsuite'): + self.module.testSuite = self.module.testsuite + if hasattr(self.module, 'test_suite'): + self.module.testSuite = self.module.test_suite + + if hasattr(self.module, 'actions'): + self.actions = list(self.module.actions) # copy, actions from cmd line + else: + self.actions = list(self.actions_in_suite()) # default, copy + Model.post_init(self) # uses self.actions + # Revise the test suite to account for excluded, included actions + self.test_suite = list() + for run in self.module.testSuite: + new_run = list() # list not tuple, must mutable + for action in run: + if action[0] in self.actions: + new_run.append(action) + else: + break # truncate the run before the excluded action + self.test_suite.append(new_run) + # prepare for first run + self.irun = 0 # index of current test run in test suite + self.pc = 0 # program counter + + + def actions_in_suite(self): + # there might be two or three items in action_tuple + return tuple(set(reduce(concat,[[action_tuple[0] for action_tuple in run] + for run in self.module.testSuite]))) + + def Accepting(self): + # In a test suite, the only accepting states are at ends of runs + # NB Here Accepting() is called *after* DoAction() that advances self.pc + length = len(self.test_suite[self.irun]) # number of tuples in run + return (self.pc == length) + + def make_properties(self, accepting): + return { 'accepting': accepting, 'statefilter': True, + 'stateinvariant': True } + + def Properties(self): + return self.make_properties(self.Accepting()) + + def Reset(self): # needed by stepper + self.pc = 0 + if self.irun < len(self.test_suite) - 1: + self.irun += 1 + else: + raise StopIteration # no more runs in test suite + + def ActionEnabled(self, a, args): + """ + action a with args is enabled in the current state + """ + step = self.test_suite[self.irun][self.pc] + action, arguments = step[0:2] # works whether or not step has result + return (a == action and args == arguments) + + def EnabledTransitions(self, cleanup=False): + """ + Return list of all tuples for enabled actions. Here, there is just one. + (action, args, next state, next state is accepting state) + Next state is a list of two elements:the run number and step within the run + In a test suite, there is always just *one* next action, or *none* + Ignore cleanup, test suite should always end in accepting state. + """ + run = self.test_suite[self.irun] + length = len(run) + if self.pc < length: + step = run[self.pc] + action, args = step[0:2] + result = step[2] if len(step) > 2 else None # result is optional + next = self.pc + 1 + accepting = (next == length) + return([(action, args, result, (self.irun,next), + self.make_properties(accepting))]) + else: + return list() # test run finished, nothing enabled, + + def DoAction(self, a, args): + step = self.test_suite[self.irun][self.pc] + result = step[2] if len(step) > 2 else None # result is optional + self.pc += 1 + return result + + def Current(self): + return (self.irun, self.pc) + + def Restore(self, state): + """ + Restore state + """ + self.irun, self.pc = state + + # GetNext not needed diff --git a/pymodel/pma.py b/pymodel/pma.py index 38daf55..86015db 100755 --- a/pymodel/pma.py +++ b/pymodel/pma.py @@ -3,9 +3,9 @@ PyModel Analyzer - generate FSM from product model program """ -import Analyzer -import AnalyzerOptions -from ProductModelProgram import ProductModelProgram +from . import Analyzer +from . import AnalyzerOptions +from .ProductModelProgram import ProductModelProgram def main(): (options, args) = AnalyzerOptions.parse_args() @@ -15,8 +15,8 @@ def main(): else: mp = ProductModelProgram(options, args) Analyzer.explore(mp, options.maxTransitions) - print '%s states, %s transitions, %s accepting states, %s unsafe states' % \ - (len(Analyzer.states),len(Analyzer.graph),len(Analyzer.accepting),len(Analyzer.unsafe)) + print('%s states, %s transitions, %s accepting states, %s unsafe states' % \ + (len(Analyzer.states),len(Analyzer.graph),len(Analyzer.accepting),len(Analyzer.unsafe))) mname = options.output if options.output else '%sFSM' % args[0] Analyzer.save(mname) diff --git a/pymodel/pmg.py b/pymodel/pmg.py index 129b5ec..40dcbd1 100755 --- a/pymodel/pmg.py +++ b/pymodel/pmg.py @@ -3,8 +3,8 @@ PyModel Graphics - generate graphics from pymodel FSM """ -import GraphicsOptions -from Dot import dotfile +from . import GraphicsOptions +from .Dot import dotfile def main(): (options, args) = GraphicsOptions.parse_args() diff --git a/pymodel/pmt.py b/pymodel/pmt.py index d73aa9f..7bbda27 100755 --- a/pymodel/pmt.py +++ b/pymodel/pmt.py @@ -10,10 +10,10 @@ import random import signal import traceback -import TesterOptions -import observation_queue as observation +from . import TesterOptions +from . import observation_queue as observation -from ProductModelProgram import ProductModelProgram +from .ProductModelProgram import ProductModelProgram class TimeoutException(Exception): pass @@ -103,9 +103,9 @@ def RunTest(options, mp, stepper, strategy, f, krun): modelResult = mp.DoAction(aname, args) # Execute in model, get result qResult = quote(modelResult) if modelResult != None: - print aname if options.quiet else '%s%s / %s' % (aname, args, qResult) + print(aname if options.quiet else '%s%s / %s' % (aname, args, qResult)) else: - print aname if options.quiet else '%s%s' % (aname, args) + print(aname if options.quiet else '%s%s' % (aname, args)) if options.output: if qResult != None: f.write(' (%s, %s, %s),\n' % (aname, args, qResult)) @@ -160,11 +160,11 @@ def RunTest(options, mp, stepper, strategy, f, krun): if stepper and not mp.Accepting() and not failMessage: failMessage = infoMessage # test run ends in non-accepting state: fail if failMessage: - print '%3d. Failure at step %s, %s' % (krun, isteps, failMessage) + print('%3d. Failure at step %s, %s' % (krun, isteps, failMessage)) else: - print '%3d. %s at step %s%s' % (krun, 'Success' if stepper else 'Finished', + print('%3d. %s at step %s%s' % (krun, 'Success' if stepper else 'Finished', isteps, - (', %s' % infoMessage) if infoMessage else '') + (', %s' % infoMessage) if infoMessage else '')) if options.output: f.write(' ],\n') @@ -231,7 +231,7 @@ def main(): RunTest(options, mp, stepper, strategy, f, k) k += 1 if k > 1: - print 'Test finished, completed %s runs' % k + print('Test finished, completed %s runs' % k) if options.output: f.write(']') diff --git a/pymodel/pmv.py b/pymodel/pmv.py index 8794f8e..c9c92dd 100755 --- a/pymodel/pmv.py +++ b/pymodel/pmv.py @@ -5,7 +5,7 @@ """ import os -import ViewerOptions +from . import ViewerOptions # an option in a singleton tuple means there might be a list of such options # use tuples not lists here so they can be keys in dict @@ -27,10 +27,10 @@ def make_opts(keys, options): else k[0]]]) def command(cmd): - print cmd # DEBUG + print(cmd) # DEBUG status = os.system(cmd) if status: - print 'Failed: %s' % cmd # status 0 means success + print('Failed: %s' % cmd) # status 0 means success def main(): (options, args) = ViewerOptions.parse_args() diff --git a/pymodel/trun.py b/pymodel/trun.py index 3ec5b70..5bba96d 100755 --- a/pymodel/trun.py +++ b/pymodel/trun.py @@ -15,6 +15,6 @@ # ... ] for (description, cmd) in test.cases: - print description + print(description) os.system(cmd) - print + print() diff --git a/pymodel/wsgirunner.py b/pymodel/wsgirunner.py index dbea1e6..af431e4 100755 --- a/pymodel/wsgirunner.py +++ b/pymodel/wsgirunner.py @@ -41,8 +41,8 @@ def main(): app_module = args[0] app = __import__(app_module) application = app.application - print "Running %s at http://localhost:%s/" \ - % (app_module, options.port) + print("Running %s at http://localhost:%s/" \ + % (app_module, options.port)) httpd = simple_server.WSGIServer(('', options.port), simple_server.WSGIRequestHandler) httpd.set_app(application) diff --git a/samples/Socket/stepper.py b/samples/Socket/stepper.py index ad7ec85..40dcfa5 100644 --- a/samples/Socket/stepper.py +++ b/samples/Socket/stepper.py @@ -70,7 +70,7 @@ def test_action(aname, args, modelResult): return None # pmt will call wait(), below else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) def wait(timeout): diff --git a/samples/Socket/stepper_a.py b/samples/Socket/stepper_a.py index ba9705c..8070807 100644 --- a/samples/Socket/stepper_a.py +++ b/samples/Socket/stepper_a.py @@ -70,7 +70,7 @@ def wait_for_return(): return None # pmt will check observation_queue else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) def wait(timeout): diff --git a/samples/Socket/stepper_d.py b/samples/Socket/stepper_d.py index c04750b..4269b6a 100644 --- a/samples/Socket/stepper_d.py +++ b/samples/Socket/stepper_d.py @@ -76,4 +76,4 @@ def test_action(aname, args, model_result): return 'recv returned %s (%s), expected %s (%s)' % (sdata, nd, smodel, nm) else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) diff --git a/samples/Socket/stepper_o.py b/samples/Socket/stepper_o.py index e2ef324..acae755 100644 --- a/samples/Socket/stepper_o.py +++ b/samples/Socket/stepper_o.py @@ -57,4 +57,4 @@ def test_action(aname, args, modelResult): return None # pmt will check observation_queue else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) diff --git a/samples/Socket/stepper_util.py b/samples/Socket/stepper_util.py index 73f7e82..b046f4e 100644 --- a/samples/Socket/stepper_util.py +++ b/samples/Socket/stepper_util.py @@ -26,7 +26,7 @@ def listen(): # Listen, prepare for senders to connect listener.bind(('localhost', port)) listener.listen(1) - print '\nServer listens on localhost port %s with RCVBUF size %s' % (port, rcvbuf) + print('\nServer listens on localhost port %s with RCVBUF size %s' % (port, rcvbuf)) # Define function for sender connect - also used in stepper reset() @@ -35,11 +35,11 @@ def connect(): sender = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # get and print send buffer size, just FYI sndbuf = sender.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) - print 'Sender creates socket with SNDBUF size %s' % (sndbuf) + print('Sender creates socket with SNDBUF size %s' % (sndbuf)) sender.connect(('localhost', port)) - print 'Sender connects to localhost port %s' % port + print('Sender connects to localhost port %s' % port) receiver, addr = listener.accept() - print 'Server accepts connection from ', addr + print('Server accepts connection from ', addr) # State needed to remember _call args for __return msg = '' n = 0 diff --git a/samples/Timeout/Stepper.py b/samples/Timeout/Stepper.py index db2eaf4..8a8e77e 100644 --- a/samples/Timeout/Stepper.py +++ b/samples/Timeout/Stepper.py @@ -9,7 +9,7 @@ def TestAction(aname, args, modelResult): (seconds,) = args time.sleep(seconds) else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) def Reset(): pass diff --git a/samples/WebApplication/Session.py b/samples/WebApplication/Session.py index dea5ccf..4e487d2 100644 --- a/samples/WebApplication/Session.py +++ b/samples/WebApplication/Session.py @@ -1,80 +1,80 @@ -""" -Experiment with code for WebApplication stepper -""" - -import re -import urllib -import urllib2 -import cookielib - -# Scrape page contents - -def loginFailed(page): - return (page.find('Incorrect login') > -1) - -intPattern = re.compile(r'Number: (\d+)') - -def intContents(page): - m = intPattern.search(page) - if m: - return int(m.group(1)) - else: - return None - -def main(): - - # Configure. Web application in this sample requires cookies, redirect - cookies = cookielib.CookieJar() - cookie_handler = urllib2.HTTPCookieProcessor(cookies) - redirect_handler= urllib2.HTTPRedirectHandler() - debug_handler = urllib2.HTTPHandler(debuglevel=1) # print headers on console - opener = urllib2.build_opener(cookie_handler,redirect_handler,debug_handler) - - # Constants - site = 'http://localhost/' - path = 'nmodel/webapplication/php/' - webAppPage = 'doStuff.php' # Shouldn't this be called webAppPage, ...Url -? - logoutPage = 'logout.php' - - webAppUrl = site + path + webAppPage - logoutUrl = site + path + logoutPage - - print 'GET to show login page' - print opener.open(webAppUrl).read() - - print 'POST to login with sample username and password, pass separate args for POST' - args = urllib.urlencode({'username':'user1', 'password':'123'}) - page = opener.open(webAppUrl, args).read() # should show successful login - print page - if loginFailed(page): - print 'Login FAILED' - - print 'GET with arg in URL to UpdateInt on server' - num = 99 - wrongNum = 'xx' - numArg = urllib.urlencode({'num':num}) - print opener.open("%s?%s" % (webAppUrl,numArg)).read() - - print 'GET to retrieve page with integer' - page = opener.open(webAppUrl).read() - print page - print '%s found in page, expected %s' % (intContents(page), num) - print - - print 'GET to logout' - print opener.open(logoutUrl).read() - - print 'GET to show login page -- again' - print opener.open(webAppUrl).read() - - print 'POST to login with username and WRONG password' - args = urllib.urlencode({'username':'user1', 'password':'321'}) # wrong pass - page = opener.open(webAppUrl, args).read() # should show login fail - print page - if loginFailed(page): - print 'Login FAILED' - - # No logout this time - we're not logged in - -if __name__ == '__main__': - main() +""" +Experiment with code for WebApplication stepper +""" + +import re +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +import http.cookiejar + +# Scrape page contents + +def loginFailed(page): + return (page.find('Incorrect login') > -1) + +intPattern = re.compile(r'Number: (\d+)') + +def intContents(page): + m = intPattern.search(page) + if m: + return int(m.group(1)) + else: + return None + +def main(): + + # Configure. Web application in this sample requires cookies, redirect + cookies = http.cookiejar.CookieJar() + cookie_handler = urllib.request.HTTPCookieProcessor(cookies) + redirect_handler= urllib.request.HTTPRedirectHandler() + debug_handler = urllib.request.HTTPHandler(debuglevel=1) # print headers on console + opener = urllib.request.build_opener(cookie_handler,redirect_handler,debug_handler) + + # Constants + site = 'http://localhost/' + path = 'nmodel/webapplication/php/' + webAppPage = 'doStuff.php' # Shouldn't this be called webAppPage, ...Url -? + logoutPage = 'logout.php' + + webAppUrl = site + path + webAppPage + logoutUrl = site + path + logoutPage + + print('GET to show login page') + print(opener.open(webAppUrl).read()) + + print('POST to login with sample username and password, pass separate args for POST') + args = urllib.parse.urlencode({'username':'user1', 'password':'123'}) + page = opener.open(webAppUrl, args).read() # should show successful login + print(page) + if loginFailed(page): + print('Login FAILED') + + print('GET with arg in URL to UpdateInt on server') + num = 99 + wrongNum = 'xx' + numArg = urllib.parse.urlencode({'num':num}) + print(opener.open("%s?%s" % (webAppUrl,numArg)).read()) + + print('GET to retrieve page with integer') + page = opener.open(webAppUrl).read() + print(page) + print('%s found in page, expected %s' % (intContents(page), num)) + print() + + print('GET to logout') + print(opener.open(logoutUrl).read()) + + print('GET to show login page -- again') + print(opener.open(webAppUrl).read()) + + print('POST to login with username and WRONG password') + args = urllib.parse.urlencode({'username':'user1', 'password':'321'}) # wrong pass + page = opener.open(webAppUrl, args).read() # should show login fail + print(page) + if loginFailed(page): + print('Login FAILED') + + # No logout this time - we're not logged in + +if __name__ == '__main__': + main() diff --git a/samples/WebApplication/Stepper.py b/samples/WebApplication/Stepper.py index ef4f8ee..2c53cba 100644 --- a/samples/WebApplication/Stepper.py +++ b/samples/WebApplication/Stepper.py @@ -1,142 +1,142 @@ -""" -WebApplication stepper (test harness) -""" - -import re -import urllib -import urllib2 -import cookielib - -# Default stepper configuration for -# webapp.py running on localhost at port 8080 - -site = 'http://localhost:8000/' -path = '' -webAppPage = 'webapp.py' -logoutPage = 'logout.py' - -webAppUrl = site + path + webAppPage -logoutUrl = site + path + logoutPage - -# user in model : user in implementation - -users = { "OleBrumm":"user1", "VinniPuhh":"user2" } -passwords = { "user1":"123", "user2":"234" } -wrongPassword = "000" - -debuglevel = 0 # for debug_handler 1: print HTTP headers, 0: don't print -show_page = False - -# Optionally rebind configuration. If no Config module found, retain defaults. - -try: - from Config import * -except ImportError: - pass - -print 'webAppUrl: %s' % webAppUrl # show which config file (if any) - -# handlers that are the same for all users - -redirect_handler= urllib2.HTTPRedirectHandler() -debug_handler = urllib2.HTTPHandler(debuglevel=debuglevel) - -# handlers that are different for each user are part of the session state - -class Session(object): - """ - One user's session state: cookies and handlers - """ - def __init__(self): - self.cookies = cookielib.CookieJar() - self.cookie_handler = urllib2.HTTPCookieProcessor(self.cookies) - self.opener = urllib2.build_opener(self.cookie_handler, - redirect_handler,debug_handler) - -session = dict() # each user's Session - -# helpers, determine test results by scraping the page - -# like in NModel WebApplication WebTestHelper -def loginFailed(page): - return (page.find('Incorrect login') > -1) - -# not in NModel WebApplication, it has no positive check for login success -def loginSuccess(page): - return (page.find('DoStuff') > -1) - -# similar to NModel WebApplication WebTestHelper -intPattern = re.compile(r'Number: (\d+)') - -def intContents(page): - m = intPattern.search(page) - if m: - return int(m.group(1)) - -# stepper core - -def TestAction(aname, args, modelResult): - """ - To indicate success, no return statement (or return None) - To indicate failure, return string that explains failure - Test runner also treats unhandled exceptions as failures - """ - - global session - - if aname == 'Initialize': - session = dict() # clear out cookies/session IDs from previous session - - elif aname == 'Login': - user = users[args[0]] - if user not in session: - session[user] = Session() - password = passwords[user] if args[1] == 'Correct' else wrongPassword - postArgs = urllib.urlencode({'username':user, 'password':password}) - # GET login page - page = session[user].opener.open(webAppUrl).read() - if show_page: - print page - # POST username, password - page = session[user].opener.open(webAppUrl, postArgs).read() - if show_page: - print page - # Check conformance, reproduce NModel WebApplication Stepper logic: - # check for login failed message, no check for positive indication of login - result = 'Success' - if loginFailed(page): - result = 'Failure' - if result != modelResult: - return 'received Login %s, expected %s' % (result, modelResult) - - elif aname == 'Logout': - user = users[args[0]] - page = session[user].opener.open(logoutUrl).read() - del session[user] # clear out cookie/session ID - if show_page: - print page - - elif aname == 'UpdateInt': - user = users[args[0]] - numArg = urllib.urlencode({'num':args[1]}) - page = session[user].opener.open("%s?%s" % (webAppUrl,numArg)).read() - if show_page: - print page - - elif aname == 'ReadInt': - user = users[args[0]] - page = session[user].opener.open(webAppUrl).read() - if show_page: - print page - numInPage = intContents(page) - if numInPage != modelResult: # check conformance - return 'found %s in page, expected %s' % (numInPage, modelResult) - else: - return None - else: - raise NotImplementedError, 'action %s not handled by stepper' % aname - - -def Reset(): - global session - session.clear() +""" +WebApplication stepper (test harness) +""" + +import re +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +import http.cookiejar + +# Default stepper configuration for +# webapp.py running on localhost at port 8080 + +site = 'http://localhost:8000/' +path = '' +webAppPage = 'webapp.py' +logoutPage = 'logout.py' + +webAppUrl = site + path + webAppPage +logoutUrl = site + path + logoutPage + +# user in model : user in implementation + +users = { "OleBrumm":"user1", "VinniPuhh":"user2" } +passwords = { "user1":"123", "user2":"234" } +wrongPassword = "000" + +debuglevel = 0 # for debug_handler 1: print HTTP headers, 0: don't print +show_page = False + +# Optionally rebind configuration. If no Config module found, retain defaults. + +try: + from Config import * +except ImportError: + pass + +print('webAppUrl: %s' % webAppUrl) # show which config file (if any) + +# handlers that are the same for all users + +redirect_handler= urllib.request.HTTPRedirectHandler() +debug_handler = urllib.request.HTTPHandler(debuglevel=debuglevel) + +# handlers that are different for each user are part of the session state + +class Session(object): + """ + One user's session state: cookies and handlers + """ + def __init__(self): + self.cookies = http.cookiejar.CookieJar() + self.cookie_handler = urllib.request.HTTPCookieProcessor(self.cookies) + self.opener = urllib.request.build_opener(self.cookie_handler, + redirect_handler,debug_handler) + +session = dict() # each user's Session + +# helpers, determine test results by scraping the page + +# like in NModel WebApplication WebTestHelper +def loginFailed(page): + return (page.find('Incorrect login') > -1) + +# not in NModel WebApplication, it has no positive check for login success +def loginSuccess(page): + return (page.find('DoStuff') > -1) + +# similar to NModel WebApplication WebTestHelper +intPattern = re.compile(r'Number: (\d+)') + +def intContents(page): + m = intPattern.search(page) + if m: + return int(m.group(1)) + +# stepper core + +def TestAction(aname, args, modelResult): + """ + To indicate success, no return statement (or return None) + To indicate failure, return string that explains failure + Test runner also treats unhandled exceptions as failures + """ + + global session + + if aname == 'Initialize': + session = dict() # clear out cookies/session IDs from previous session + + elif aname == 'Login': + user = users[args[0]] + if user not in session: + session[user] = Session() + password = passwords[user] if args[1] == 'Correct' else wrongPassword + postArgs = urllib.parse.urlencode({'username':user, 'password':password}) + # GET login page + page = session[user].opener.open(webAppUrl).read() + if show_page: + print(page) + # POST username, password + page = session[user].opener.open(webAppUrl, postArgs).read() + if show_page: + print(page) + # Check conformance, reproduce NModel WebApplication Stepper logic: + # check for login failed message, no check for positive indication of login + result = 'Success' + if loginFailed(page): + result = 'Failure' + if result != modelResult: + return 'received Login %s, expected %s' % (result, modelResult) + + elif aname == 'Logout': + user = users[args[0]] + page = session[user].opener.open(logoutUrl).read() + del session[user] # clear out cookie/session ID + if show_page: + print(page) + + elif aname == 'UpdateInt': + user = users[args[0]] + numArg = urllib.parse.urlencode({'num':args[1]}) + page = session[user].opener.open("%s?%s" % (webAppUrl,numArg)).read() + if show_page: + print(page) + + elif aname == 'ReadInt': + user = users[args[0]] + page = session[user].opener.open(webAppUrl).read() + if show_page: + print(page) + numInPage = intContents(page) + if numInPage != modelResult: # check conformance + return 'found %s in page, expected %s' % (numInPage, modelResult) + else: + return None + else: + raise NotImplementedError('action %s not handled by stepper' % aname) + + +def Reset(): + global session + session.clear() diff --git a/samples/WebApplication/WSGIVerboseConfig.py b/samples/WebApplication/WSGIVerboseConfig.py index 1f5df9b..e2b65f0 100644 --- a/samples/WebApplication/WSGIVerboseConfig.py +++ b/samples/WebApplication/WSGIVerboseConfig.py @@ -12,7 +12,7 @@ webAppUrl = site + path + webAppPage logoutUrl = site + path + logoutPage -print 'webAppUrl %s' % webAppUrl # debug +print('webAppUrl %s' % webAppUrl) # debug debuglevel = 1 # 1: print HTTP headers, 0: don't print # show_page = True diff --git a/samples/WebApplication/webapp.py b/samples/WebApplication/webapp.py index 1edd6ba..1b05683 100644 --- a/samples/WebApplication/webapp.py +++ b/samples/WebApplication/webapp.py @@ -1,179 +1,179 @@ -""" -Web application to test with PyModel WebApplication sample - -This is a WSGI-compliant application. Its behavior is similar to -Juhan Ernits' sample application in PHP at http://nmodel.codeplex.com/ - -To run this web application: - - wsgirunner webapp # runs on port 8000 on localhost - -or - - wsgirunner -p 8080 webapp # -p option sets port number on localhost - -""" - -import pprint -import urlparse - -# page templates appear at the end of this file - -# configuration -password = { 'user1':'123', 'user2':'234' } - -# data state -integers = dict() # user to int -strings = dict() # user to str -sessions = dict() # cookie to user, assume each user has at most one session -next_cookie = 0 - -def application(environ, start_response): - global next_cookie - - # print environ_template % pprint.pformat(environ) # DEBUG, voluminous! - - response_headers = [] # add headers below - cookie = environ.get('HTTP_COOKIE') # might be None - - # show login page - if (environ['PATH_INFO'] == '/webapp.py' - and environ['REQUEST_METHOD'] == 'GET' - and cookie not in sessions): # cookie might be None - response_body = login_page - response_headers += [ - ('Set-Cookie','PYSESSID=%s; path=/' % next_cookie)] - next_cookie += 1 - status = '200 OK' - - # log in, if successful show data form page - elif (environ['PATH_INFO'] == '/webapp.py' - and environ['REQUEST_METHOD'] == 'POST'): - wd = environ['wsgi.input'] - method = environ['REQUEST_METHOD'] - length = int(environ['CONTENT_LENGTH']) - request_body = wd.read(length) - vars = urlparse.parse_qs(request_body) - user = vars['username'][0] # vars[x] are lists, get first item - passwd = vars['password'][0] - if user in password and password[user] == passwd: - sessions[cookie] = user - if not user in strings: - strings[user] = '' - # CORRECT CODE comented out - # if not user in integers: - # integers[user] = 0 - # BUG follows, should be guarded by if ... like strings - integers[user] = 0 # BUG, always overwrites data from last session - # PHP version sends redirect back to doStuff instead of this response_body - #response_body = dostuff_template % (integers[user], - # strings[user]) - response_headers += [('Location','webapp.py')] - status = "302 Found" - response_body = '' - else: - response_body = login_failure_page - status = '200 OK' - - # submit data in form page - elif (environ['PATH_INFO'] == '/webapp.py' - and environ['REQUEST_METHOD'] == 'GET' - and cookie in sessions): - user = sessions[cookie] - vars = urlparse.parse_qs(environ['QUERY_STRING']) - if 'num' in vars: - integers[user] = str(vars['num'][0]) # vars[x] are lists, 1st item - if 'str' in vars: - strings[user] = vars['str'][0] - response_body = dostuff_template % (integers[user], - strings[user]) - status = '200 OK' - - # log out - elif environ['PATH_INFO'] == '/logout.py': - if cookie in sessions: - del sessions[cookie] - response_body = '' # blank page, like original NModel version - status = '200 OK' - pass - - # unknown page - elif environ['PATH_INFO'] not in ('/webapp.py', '/logout.py'): - response_body = p404_page - status = '404 Not Found' - - # nonsense: doStuff REQUEST_METHOD not GET or POST, or ... ? - else: - raise ValueError # send 500 Server Error - - # response - response_headers += [('Content-Type', 'text/html'), - ('Content-Length', str(len(response_body)))] - start_response(status, response_headers) - return [response_body] - - -environ_template = """environ is - -%s - -""" - - -p404_page = """ - -404 Not Found - - -404 Not found - - -""" - -login_page = """ - -LoginPage - - -
-Username: -Password: - -
- - -""" - -login_failure_page = """ - -Login Failure - - -Incorrect login name or password. Please try again. - - -""" - -# usage: dostuff_template % (integers[user], strings[user]) -dostuff_template = """ - - -DoStuff - - -Number: %s
-String: %s -
-Number: - -
-
-String: - -
- -Log out - - -""" - +""" +Web application to test with PyModel WebApplication sample + +This is a WSGI-compliant application. Its behavior is similar to +Juhan Ernits' sample application in PHP at http://nmodel.codeplex.com/ + +To run this web application: + + wsgirunner webapp # runs on port 8000 on localhost + +or + + wsgirunner -p 8080 webapp # -p option sets port number on localhost + +""" + +import pprint +import urllib.parse + +# page templates appear at the end of this file + +# configuration +password = { 'user1':'123', 'user2':'234' } + +# data state +integers = dict() # user to int +strings = dict() # user to str +sessions = dict() # cookie to user, assume each user has at most one session +next_cookie = 0 + +def application(environ, start_response): + global next_cookie + + # print environ_template % pprint.pformat(environ) # DEBUG, voluminous! + + response_headers = [] # add headers below + cookie = environ.get('HTTP_COOKIE') # might be None + + # show login page + if (environ['PATH_INFO'] == '/webapp.py' + and environ['REQUEST_METHOD'] == 'GET' + and cookie not in sessions): # cookie might be None + response_body = login_page + response_headers += [ + ('Set-Cookie','PYSESSID=%s; path=/' % next_cookie)] + next_cookie += 1 + status = '200 OK' + + # log in, if successful show data form page + elif (environ['PATH_INFO'] == '/webapp.py' + and environ['REQUEST_METHOD'] == 'POST'): + wd = environ['wsgi.input'] + method = environ['REQUEST_METHOD'] + length = int(environ['CONTENT_LENGTH']) + request_body = wd.read(length) + vars = urllib.parse.parse_qs(request_body) + user = vars['username'][0] # vars[x] are lists, get first item + passwd = vars['password'][0] + if user in password and password[user] == passwd: + sessions[cookie] = user + if not user in strings: + strings[user] = '' + # CORRECT CODE comented out + # if not user in integers: + # integers[user] = 0 + # BUG follows, should be guarded by if ... like strings + integers[user] = 0 # BUG, always overwrites data from last session + # PHP version sends redirect back to doStuff instead of this response_body + #response_body = dostuff_template % (integers[user], + # strings[user]) + response_headers += [('Location','webapp.py')] + status = "302 Found" + response_body = '' + else: + response_body = login_failure_page + status = '200 OK' + + # submit data in form page + elif (environ['PATH_INFO'] == '/webapp.py' + and environ['REQUEST_METHOD'] == 'GET' + and cookie in sessions): + user = sessions[cookie] + vars = urllib.parse.parse_qs(environ['QUERY_STRING']) + if 'num' in vars: + integers[user] = str(vars['num'][0]) # vars[x] are lists, 1st item + if 'str' in vars: + strings[user] = vars['str'][0] + response_body = dostuff_template % (integers[user], + strings[user]) + status = '200 OK' + + # log out + elif environ['PATH_INFO'] == '/logout.py': + if cookie in sessions: + del sessions[cookie] + response_body = '' # blank page, like original NModel version + status = '200 OK' + pass + + # unknown page + elif environ['PATH_INFO'] not in ('/webapp.py', '/logout.py'): + response_body = p404_page + status = '404 Not Found' + + # nonsense: doStuff REQUEST_METHOD not GET or POST, or ... ? + else: + raise ValueError # send 500 Server Error + + # response + response_headers += [('Content-Type', 'text/html'), + ('Content-Length', str(len(response_body)))] + start_response(status, response_headers) + return [response_body] + + +environ_template = """environ is + +%s + +""" + + +p404_page = """ + +404 Not Found + + +404 Not found + + +""" + +login_page = """ + +LoginPage + + +
+Username: +Password: + +
+ + +""" + +login_failure_page = """ + +Login Failure + + +Incorrect login name or password. Please try again. + + +""" + +# usage: dostuff_template % (integers[user], strings[user]) +dostuff_template = """ + + +DoStuff + + +Number: %s
+String: %s +
+Number: + +
+
+String: + +
+ +Log out + + +""" + diff --git a/samples/tracemultiplexer/tracemultiplexer.py b/samples/tracemultiplexer/tracemultiplexer.py index f85b74d..09332b7 100644 --- a/samples/tracemultiplexer/tracemultiplexer.py +++ b/samples/tracemultiplexer/tracemultiplexer.py @@ -1,205 +1,205 @@ -""" -Simulate a program where two threads write to the same log -file. Exhibit nondeterminism in scheduling threads. Try to synchronize -so that only one thread at a time can write to the log. Detect unsafe -states where both threads may write to the log. Identify log messages -that may have been corrupted by unsynchronized writes from both -threads. - -The simulated multi-threaded program has been instrumented to save -traces of its API calls in a log. Instead of simply calling the API -functions, each thread instead calls the *tracecapture* function, -which calls the API function but also saves the call (with arguments) -and return (with return value) in the trace log. The tracecapture -function uses a lock: - - tracelock = lock() - - def tracecapture(thread_id, call, args): # call is action name - tracelock.acquire() - log(thread_id, call, "start", args) # log the call with arguments - tracelock.release() # release here allows threads to interleave - ret = call(*args) - tracelock.acquire() - log(thread_id, call, "finish", ret) # log the return with return value - tracelock.release() - return ret - -The purpose of *tracelock* is to ensure that only one thread at a time -can write to the trace log. To allow threads to interleave, the first -*tracelock.release()* *precedes* the call, otherwise the call might -block and hold *tracelock*, prevent other threads from running while -that call blocks. - -This model program exhibits nondeterminism in thread scheduling, so -traces (paths through the graphs) differ in the order that API calls -and returns are made, which results in different orders for messages in -the log. - -Parameters (constants that do not change when the model executes): - -program: models the multi-threaded program. It is a sequence of API -call ids (action ids) for each thread, first index is thread id, -second index is thread pc. Here we model a simple program with just -two threads, where each thread makes just one API call, executes -tracecapture just once, and writes just two log messages. With a few -revisions, we could also handle a more complicated simulated program -(with more threads and more actions). - -unsynchronized: set False to use tracelock, True to ignore tracelock - - -State: - -pc: program counter for each thread, indexed by thread_id -Indicates which action in program each thread is executing - -phase: phase of each thread, indexed by thread id -models progress through the tracecapture function (see above) -For each API call in program, phases in order are: ready, start, call, finish -phase[thread] == start means thread holds tracelock to write call start -phase[thread] == finish means thread holds tracelock to write call finish -After processing last API call in its thread, phase[thread] == done - -log: contents of the tracelog written by all the threads - -Actions: - -Each action represents progress through the phases of tracecapture. -Each action might be enabled for more than one thread in some states; -this models the nondeterminism of threading. - -""" - -from copy import copy - -## Parameters - -# This simple program has only two threads, with only one API call in each - -program = (( 'listfiles', ), # thread 0 - ( 'openfile', )) # thread 1 - -threads = range(len(program)) # one element of program for each thread - -unsynchronized = False # False: use tracelock, True: ignore tracelock - -### State - -# tracecapture state - -pc = list() # program counter for each thread -phase = list() # phase of each thread in tracecapture -log = list() # contents of tracelog written by all threads - -# file system state - -files = list() # filenames in filesystem -listing = list() # listfiles return value, FIXME ret should be in tracecapture - -### Safety condition - -# phases where a thread can write to the log -writing = ('start','finish') - -def writing_threads(): - """ - list of threads that can write to the log - """ - return [ t for t in threads if phase[t] in writing ] - -def state_invariant(): - """ - At most one thread can write to the log - """ - return len(writing_threads()) <= 1 - - -### Other necessary functions - -# run is allowed to stop -def accepting(): - return all([ phase[t] == 'done' for t in threads ]) - -# reset before another run -def reset(): - global pc, phase, log - pc = [ 0 for thread in program ] - phase = [ 'ready' for thread in program ] - log = [] - files = [] - -### Initialize - -reset() - -### Actions - -def start_enabled(thread): - return (phase[thread] == 'ready' - and (not writing_threads() # lock is free - or unsynchronized)) # ignore lock - might corrupt file - -def start(thread): - phase[thread] = 'start' # acquire lock - # write log, if it might be corrupted write 'XXX' at the end - if state_invariant(): - log.append((thread, program[thread][pc[thread]], 'start')) - else: - log.append((thread, program[thread][pc[thread]], 'start', 'XXX')) - -def call_enabled(thread): - return phase[thread] == 'start' # holding lock - -def call(thread): - global listing # we reassign whole list, we don't just update it - phase[thread] = 'call' # release lock, execute call - action = program[thread][pc[thread]] - # for now. handle each action in *program* as a special case inline here - if action == 'openfile': - files.append('file0') # only works if openfiles just called once - if action == 'listfiles': - listing = copy(files) # must copy now because open may change files - -def finish_enabled(thread): - return (phase[thread] == 'call' - and (not writing_threads() # lock is free - or unsynchronized)) # ignore lock - might corrupt file - -def finish(thread): - phase[thread] = 'finish' # acquire lock - action = program[thread][pc[thread]] - # for now, handle each action in *program* as a special case inline here - if action == 'openfile': - ret = files[-1] # most recently appended - if action == 'listfiles': - ret = listing # most recently appended - # write log, if it might be corrupted write 'XXX' at the end - if state_invariant(): - log.append((thread, action, 'finish', ret)) - else: - log.append((thread, action, 'finish', ret, 'XXX')) - -def exit_enabled(thread): - return phase[thread] == 'finish' # holding lock - -# For now, handle exit as a special case, assign phase 'done'. -# If the simulated threads had more than one action, here we -# would advance to the next action and reset phase to 'start'. -def exit(thread): - phase[thread] = 'done' # release lock, indicate done - - -### Metadata - -state = ('pc', 'phase', 'log', 'files', 'listing') - -actions = (start, call, finish, exit) - -enablers = {start:(start_enabled,), call:(call_enabled,), - finish:(finish_enabled,), exit:(exit_enabled,)} - -domains = { start: { 'thread': threads }, - call: { 'thread': threads }, - finish: { 'thread': threads }, - exit: { 'thread': threads }} +""" +Simulate a program where two threads write to the same log +file. Exhibit nondeterminism in scheduling threads. Try to synchronize +so that only one thread at a time can write to the log. Detect unsafe +states where both threads may write to the log. Identify log messages +that may have been corrupted by unsynchronized writes from both +threads. + +The simulated multi-threaded program has been instrumented to save +traces of its API calls in a log. Instead of simply calling the API +functions, each thread instead calls the *tracecapture* function, +which calls the API function but also saves the call (with arguments) +and return (with return value) in the trace log. The tracecapture +function uses a lock: + + tracelock = lock() + + def tracecapture(thread_id, call, args): # call is action name + tracelock.acquire() + log(thread_id, call, "start", args) # log the call with arguments + tracelock.release() # release here allows threads to interleave + ret = call(*args) + tracelock.acquire() + log(thread_id, call, "finish", ret) # log the return with return value + tracelock.release() + return ret + +The purpose of *tracelock* is to ensure that only one thread at a time +can write to the trace log. To allow threads to interleave, the first +*tracelock.release()* *precedes* the call, otherwise the call might +block and hold *tracelock*, prevent other threads from running while +that call blocks. + +This model program exhibits nondeterminism in thread scheduling, so +traces (paths through the graphs) differ in the order that API calls +and returns are made, which results in different orders for messages in +the log. + +Parameters (constants that do not change when the model executes): + +program: models the multi-threaded program. It is a sequence of API +call ids (action ids) for each thread, first index is thread id, +second index is thread pc. Here we model a simple program with just +two threads, where each thread makes just one API call, executes +tracecapture just once, and writes just two log messages. With a few +revisions, we could also handle a more complicated simulated program +(with more threads and more actions). + +unsynchronized: set False to use tracelock, True to ignore tracelock + + +State: + +pc: program counter for each thread, indexed by thread_id +Indicates which action in program each thread is executing + +phase: phase of each thread, indexed by thread id +models progress through the tracecapture function (see above) +For each API call in program, phases in order are: ready, start, call, finish +phase[thread] == start means thread holds tracelock to write call start +phase[thread] == finish means thread holds tracelock to write call finish +After processing last API call in its thread, phase[thread] == done + +log: contents of the tracelog written by all the threads + +Actions: + +Each action represents progress through the phases of tracecapture. +Each action might be enabled for more than one thread in some states; +this models the nondeterminism of threading. + +""" + +from copy import copy + +## Parameters + +# This simple program has only two threads, with only one API call in each + +program = (( 'listfiles', ), # thread 0 + ( 'openfile', )) # thread 1 + +threads = list(range(len(program))) # one element of program for each thread + +unsynchronized = False # False: use tracelock, True: ignore tracelock + +### State + +# tracecapture state + +pc = list() # program counter for each thread +phase = list() # phase of each thread in tracecapture +log = list() # contents of tracelog written by all threads + +# file system state + +files = list() # filenames in filesystem +listing = list() # listfiles return value, FIXME ret should be in tracecapture + +### Safety condition + +# phases where a thread can write to the log +writing = ('start','finish') + +def writing_threads(): + """ + list of threads that can write to the log + """ + return [ t for t in threads if phase[t] in writing ] + +def state_invariant(): + """ + At most one thread can write to the log + """ + return len(writing_threads()) <= 1 + + +### Other necessary functions + +# run is allowed to stop +def accepting(): + return all([ phase[t] == 'done' for t in threads ]) + +# reset before another run +def reset(): + global pc, phase, log + pc = [ 0 for thread in program ] + phase = [ 'ready' for thread in program ] + log = [] + files = [] + +### Initialize + +reset() + +### Actions + +def start_enabled(thread): + return (phase[thread] == 'ready' + and (not writing_threads() # lock is free + or unsynchronized)) # ignore lock - might corrupt file + +def start(thread): + phase[thread] = 'start' # acquire lock + # write log, if it might be corrupted write 'XXX' at the end + if state_invariant(): + log.append((thread, program[thread][pc[thread]], 'start')) + else: + log.append((thread, program[thread][pc[thread]], 'start', 'XXX')) + +def call_enabled(thread): + return phase[thread] == 'start' # holding lock + +def call(thread): + global listing # we reassign whole list, we don't just update it + phase[thread] = 'call' # release lock, execute call + action = program[thread][pc[thread]] + # for now. handle each action in *program* as a special case inline here + if action == 'openfile': + files.append('file0') # only works if openfiles just called once + if action == 'listfiles': + listing = copy(files) # must copy now because open may change files + +def finish_enabled(thread): + return (phase[thread] == 'call' + and (not writing_threads() # lock is free + or unsynchronized)) # ignore lock - might corrupt file + +def finish(thread): + phase[thread] = 'finish' # acquire lock + action = program[thread][pc[thread]] + # for now, handle each action in *program* as a special case inline here + if action == 'openfile': + ret = files[-1] # most recently appended + if action == 'listfiles': + ret = listing # most recently appended + # write log, if it might be corrupted write 'XXX' at the end + if state_invariant(): + log.append((thread, action, 'finish', ret)) + else: + log.append((thread, action, 'finish', ret, 'XXX')) + +def exit_enabled(thread): + return phase[thread] == 'finish' # holding lock + +# For now, handle exit as a special case, assign phase 'done'. +# If the simulated threads had more than one action, here we +# would advance to the next action and reset phase to 'start'. +def exit(thread): + phase[thread] = 'done' # release lock, indicate done + + +### Metadata + +state = ('pc', 'phase', 'log', 'files', 'listing') + +actions = (start, call, finish, exit) + +enablers = {start:(start_enabled,), call:(call_enabled,), + finish:(finish_enabled,), exit:(exit_enabled,)} + +domains = { start: { 'thread': threads }, + call: { 'thread': threads }, + finish: { 'thread': threads }, + exit: { 'thread': threads }} From 928a288566f14da425d0b47a0f6d6bfaf1ac48c8 Mon Sep 17 00:00:00 2001 From: philscrace2 Date: Fri, 7 May 2021 15:45:49 +0100 Subject: [PATCH 02/11] Update the pymodel framework code and samples to use Python3 --- .idea/.gitignore | 3 + .idea/PyModel.iml | 12 ++++ .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/misc.xml | 4 ++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ pymodel/FSM.py | 2 +- pymodel/ModelProgram.py | 10 ++-- pymodel/ProductModelProgram.py | 33 ++++++----- pymodel/TestSuite.py | 3 +- pymodel/pma.py | 10 ++-- pymodel/pmg.py | 4 +- pymodel/pmt.py | 18 +++--- pymodel/pmv.py | 6 +- pymodel/trun.py | 4 +- pymodel/wsgirunner.py | 4 +- samples/Socket/stepper.py | 2 +- samples/Socket/stepper_a.py | 2 +- samples/Socket/stepper_d.py | 2 +- samples/Socket/stepper_o.py | 2 +- samples/Socket/stepper_util.py | 8 +-- samples/Timeout/Stepper.py | 2 +- samples/WebApplication/Session.py | 58 +++++++++---------- samples/WebApplication/Stepper.py | 34 +++++------ samples/WebApplication/WSGIVerboseConfig.py | 2 +- samples/WebApplication/webapp.py | 6 +- samples/tracemultiplexer/tracemultiplexer.py | 2 +- 27 files changed, 147 insertions(+), 106 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/PyModel.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/PyModel.iml b/.idea/PyModel.iml new file mode 100644 index 0000000..8b8c395 --- /dev/null +++ b/.idea/PyModel.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e061cac --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9ef61b7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pymodel/FSM.py b/pymodel/FSM.py index 2fcef4a..b7f9876 100644 --- a/pymodel/FSM.py +++ b/pymodel/FSM.py @@ -2,7 +2,7 @@ Interface to an FSM module (graph) used by ProductModelProgram """ -from model import Model +from .model import Model class FSM(Model): diff --git a/pymodel/ModelProgram.py b/pymodel/ModelProgram.py index 64c0e16..b65e824 100644 --- a/pymodel/ModelProgram.py +++ b/pymodel/ModelProgram.py @@ -9,7 +9,7 @@ import copy import inspect import itertools -from model import Model +from .model import Model class ModelProgram(Model): @@ -76,7 +76,7 @@ def make_argslist(self, a): for arg in args if a in self.module.domains ] combination = self.module.combinations.get(a, 'all') # default is 'all' if combination == 'cases': # as many args as items in smallest domain - argslists = zip(*domains) + argslists = list(zip(*domains)) elif combination == 'all': # Cartesian product argslists = itertools.product(*domains) # might be nice to support 'pairwise' also @@ -104,7 +104,7 @@ def Reset(self): try: self.module.Reset() except AttributeError: # Reset is optional, but there is no default - print 'No Reset function for model program %s' % self.module.__name__ + print('No Reset function for model program %s' % self.module.__name__) sys.exit() def ActionEnabled(self, a, args): @@ -120,8 +120,8 @@ def ActionEnabled(self, a, args): nargs = len(args) # nparams == 0 means match any args if nparams > 0 and nparams != nargs: - print 'Error: %s needs %s arguments, got %s. Check parameter generator.' %\ - (a_enabled.__name__, nparams, nargs) + print('Error: %s needs %s arguments, got %s. Check parameter generator.' %\ + (a_enabled.__name__, nparams, nargs)) sys.exit(1) # Don't return, just exit with error status else: if nparams > 0: diff --git a/pymodel/ProductModelProgram.py b/pymodel/ProductModelProgram.py index a374a20..e9c2b86 100644 --- a/pymodel/ProductModelProgram.py +++ b/pymodel/ProductModelProgram.py @@ -21,9 +21,10 @@ from operator import concat from collections import defaultdict -from FSM import FSM -from TestSuite import TestSuite -from ModelProgram import ModelProgram +from .FSM import FSM +from .TestSuite import TestSuite +from .ModelProgram import ModelProgram +from functools import reduce class ProductModelProgram(object): @@ -57,19 +58,19 @@ def __init__(self, options, args): # 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 self.mp.values(): + 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 self.mp.values()]) + 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 self.mp.values()]) + for mp in list(self.mp.values())]) # FSM and TestSuite must have .observables # print 'observables %s' % self.observables # DEBUG @@ -92,7 +93,7 @@ def ActionEnabled(self, 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 self.mp.values() + 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 ]]) @@ -112,7 +113,7 @@ def EnabledTransitions(self, cleanup): 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 self.mp.values(): + for mp in list(self.mp.values()): if isinstance(mp, ModelProgram): mp.ParamGen() @@ -127,13 +128,13 @@ def EnabledTransitions(self, cleanup): # dict from action to sequence of argslists argslists = defaultdict(list) - for transitions in enabledScenarioActions.values(): + 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 argslists.items()]) + for (action,args) in list(argslists.items())]) # print 'scenarioArgslists %s' % scenarioArgslists # Pass scenarioArgslists to ModelProgram EnabledTransitions @@ -152,7 +153,7 @@ def EnabledTransitions(self, cleanup): # set (with no duplicates) of all (aname, args, result) in enabledActions transitions = set([(a.__name__, args, result) for (a,args,result,next,properties) in - reduce(concat,enabledActions.values())]) + reduce(concat,list(enabledActions.values()))]) # print 'transitions %s' % transitions # dict from (aname, args, result) @@ -226,11 +227,11 @@ def Properties(self): """ return { 'accepting': # all mp in the current state are in their accepting states - all([ m.Properties()['accepting'] for m in self.mp.values() ]), + all([ m.Properties()['accepting'] for m in list(self.mp.values()) ]), 'statefilter': - all([ m.Properties()['statefilter'] for m in self.mp.values() ]), + all([ m.Properties()['statefilter'] for m in list(self.mp.values()) ]), 'stateinvariant': - all([ m.Properties()['stateinvariant'] for m in self.mp.values() ]) + all([ m.Properties()['stateinvariant'] for m in list(self.mp.values()) ]) } def NextProperties(self, next_properties): @@ -252,7 +253,7 @@ def DoAction(self, aname, args): return result from last mp arg """ result = None - for m in self.mp.values(): + 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) @@ -262,7 +263,7 @@ def Reset(self): """ Reset all the mp """ - for m in self.mp.values(): + for m in list(self.mp.values()): m.Reset() def Current(self): diff --git a/pymodel/TestSuite.py b/pymodel/TestSuite.py index 68e9e49..6f0a209 100644 --- a/pymodel/TestSuite.py +++ b/pymodel/TestSuite.py @@ -3,7 +3,8 @@ """ from operator import concat -from model import Model +from .model import Model +from functools import reduce class TestSuite(Model): diff --git a/pymodel/pma.py b/pymodel/pma.py index 38daf55..86015db 100755 --- a/pymodel/pma.py +++ b/pymodel/pma.py @@ -3,9 +3,9 @@ PyModel Analyzer - generate FSM from product model program """ -import Analyzer -import AnalyzerOptions -from ProductModelProgram import ProductModelProgram +from . import Analyzer +from . import AnalyzerOptions +from .ProductModelProgram import ProductModelProgram def main(): (options, args) = AnalyzerOptions.parse_args() @@ -15,8 +15,8 @@ def main(): else: mp = ProductModelProgram(options, args) Analyzer.explore(mp, options.maxTransitions) - print '%s states, %s transitions, %s accepting states, %s unsafe states' % \ - (len(Analyzer.states),len(Analyzer.graph),len(Analyzer.accepting),len(Analyzer.unsafe)) + print('%s states, %s transitions, %s accepting states, %s unsafe states' % \ + (len(Analyzer.states),len(Analyzer.graph),len(Analyzer.accepting),len(Analyzer.unsafe))) mname = options.output if options.output else '%sFSM' % args[0] Analyzer.save(mname) diff --git a/pymodel/pmg.py b/pymodel/pmg.py index 129b5ec..40dcbd1 100755 --- a/pymodel/pmg.py +++ b/pymodel/pmg.py @@ -3,8 +3,8 @@ PyModel Graphics - generate graphics from pymodel FSM """ -import GraphicsOptions -from Dot import dotfile +from . import GraphicsOptions +from .Dot import dotfile def main(): (options, args) = GraphicsOptions.parse_args() diff --git a/pymodel/pmt.py b/pymodel/pmt.py index d73aa9f..7bbda27 100755 --- a/pymodel/pmt.py +++ b/pymodel/pmt.py @@ -10,10 +10,10 @@ import random import signal import traceback -import TesterOptions -import observation_queue as observation +from . import TesterOptions +from . import observation_queue as observation -from ProductModelProgram import ProductModelProgram +from .ProductModelProgram import ProductModelProgram class TimeoutException(Exception): pass @@ -103,9 +103,9 @@ def RunTest(options, mp, stepper, strategy, f, krun): modelResult = mp.DoAction(aname, args) # Execute in model, get result qResult = quote(modelResult) if modelResult != None: - print aname if options.quiet else '%s%s / %s' % (aname, args, qResult) + print(aname if options.quiet else '%s%s / %s' % (aname, args, qResult)) else: - print aname if options.quiet else '%s%s' % (aname, args) + print(aname if options.quiet else '%s%s' % (aname, args)) if options.output: if qResult != None: f.write(' (%s, %s, %s),\n' % (aname, args, qResult)) @@ -160,11 +160,11 @@ def RunTest(options, mp, stepper, strategy, f, krun): if stepper and not mp.Accepting() and not failMessage: failMessage = infoMessage # test run ends in non-accepting state: fail if failMessage: - print '%3d. Failure at step %s, %s' % (krun, isteps, failMessage) + print('%3d. Failure at step %s, %s' % (krun, isteps, failMessage)) else: - print '%3d. %s at step %s%s' % (krun, 'Success' if stepper else 'Finished', + print('%3d. %s at step %s%s' % (krun, 'Success' if stepper else 'Finished', isteps, - (', %s' % infoMessage) if infoMessage else '') + (', %s' % infoMessage) if infoMessage else '')) if options.output: f.write(' ],\n') @@ -231,7 +231,7 @@ def main(): RunTest(options, mp, stepper, strategy, f, k) k += 1 if k > 1: - print 'Test finished, completed %s runs' % k + print('Test finished, completed %s runs' % k) if options.output: f.write(']') diff --git a/pymodel/pmv.py b/pymodel/pmv.py index 8794f8e..c9c92dd 100755 --- a/pymodel/pmv.py +++ b/pymodel/pmv.py @@ -5,7 +5,7 @@ """ import os -import ViewerOptions +from . import ViewerOptions # an option in a singleton tuple means there might be a list of such options # use tuples not lists here so they can be keys in dict @@ -27,10 +27,10 @@ def make_opts(keys, options): else k[0]]]) def command(cmd): - print cmd # DEBUG + print(cmd) # DEBUG status = os.system(cmd) if status: - print 'Failed: %s' % cmd # status 0 means success + print('Failed: %s' % cmd) # status 0 means success def main(): (options, args) = ViewerOptions.parse_args() diff --git a/pymodel/trun.py b/pymodel/trun.py index 3ec5b70..5bba96d 100755 --- a/pymodel/trun.py +++ b/pymodel/trun.py @@ -15,6 +15,6 @@ # ... ] for (description, cmd) in test.cases: - print description + print(description) os.system(cmd) - print + print() diff --git a/pymodel/wsgirunner.py b/pymodel/wsgirunner.py index dbea1e6..af431e4 100755 --- a/pymodel/wsgirunner.py +++ b/pymodel/wsgirunner.py @@ -41,8 +41,8 @@ def main(): app_module = args[0] app = __import__(app_module) application = app.application - print "Running %s at http://localhost:%s/" \ - % (app_module, options.port) + print("Running %s at http://localhost:%s/" \ + % (app_module, options.port)) httpd = simple_server.WSGIServer(('', options.port), simple_server.WSGIRequestHandler) httpd.set_app(application) diff --git a/samples/Socket/stepper.py b/samples/Socket/stepper.py index ad7ec85..40dcfa5 100644 --- a/samples/Socket/stepper.py +++ b/samples/Socket/stepper.py @@ -70,7 +70,7 @@ def test_action(aname, args, modelResult): return None # pmt will call wait(), below else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) def wait(timeout): diff --git a/samples/Socket/stepper_a.py b/samples/Socket/stepper_a.py index ba9705c..8070807 100644 --- a/samples/Socket/stepper_a.py +++ b/samples/Socket/stepper_a.py @@ -70,7 +70,7 @@ def wait_for_return(): return None # pmt will check observation_queue else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) def wait(timeout): diff --git a/samples/Socket/stepper_d.py b/samples/Socket/stepper_d.py index c04750b..4269b6a 100644 --- a/samples/Socket/stepper_d.py +++ b/samples/Socket/stepper_d.py @@ -76,4 +76,4 @@ def test_action(aname, args, model_result): return 'recv returned %s (%s), expected %s (%s)' % (sdata, nd, smodel, nm) else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) diff --git a/samples/Socket/stepper_o.py b/samples/Socket/stepper_o.py index e2ef324..acae755 100644 --- a/samples/Socket/stepper_o.py +++ b/samples/Socket/stepper_o.py @@ -57,4 +57,4 @@ def test_action(aname, args, modelResult): return None # pmt will check observation_queue else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) diff --git a/samples/Socket/stepper_util.py b/samples/Socket/stepper_util.py index 73f7e82..b046f4e 100644 --- a/samples/Socket/stepper_util.py +++ b/samples/Socket/stepper_util.py @@ -26,7 +26,7 @@ def listen(): # Listen, prepare for senders to connect listener.bind(('localhost', port)) listener.listen(1) - print '\nServer listens on localhost port %s with RCVBUF size %s' % (port, rcvbuf) + print('\nServer listens on localhost port %s with RCVBUF size %s' % (port, rcvbuf)) # Define function for sender connect - also used in stepper reset() @@ -35,11 +35,11 @@ def connect(): sender = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # get and print send buffer size, just FYI sndbuf = sender.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) - print 'Sender creates socket with SNDBUF size %s' % (sndbuf) + print('Sender creates socket with SNDBUF size %s' % (sndbuf)) sender.connect(('localhost', port)) - print 'Sender connects to localhost port %s' % port + print('Sender connects to localhost port %s' % port) receiver, addr = listener.accept() - print 'Server accepts connection from ', addr + print('Server accepts connection from ', addr) # State needed to remember _call args for __return msg = '' n = 0 diff --git a/samples/Timeout/Stepper.py b/samples/Timeout/Stepper.py index db2eaf4..8a8e77e 100644 --- a/samples/Timeout/Stepper.py +++ b/samples/Timeout/Stepper.py @@ -9,7 +9,7 @@ def TestAction(aname, args, modelResult): (seconds,) = args time.sleep(seconds) else: - raise NotImplementedError, 'action not supported by stepper: %s' % aname + raise NotImplementedError('action not supported by stepper: %s' % aname) def Reset(): pass diff --git a/samples/WebApplication/Session.py b/samples/WebApplication/Session.py index dea5ccf..54051fa 100644 --- a/samples/WebApplication/Session.py +++ b/samples/WebApplication/Session.py @@ -3,9 +3,9 @@ """ import re -import urllib -import urllib2 -import cookielib +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +import http.cookiejar # Scrape page contents @@ -24,11 +24,11 @@ def intContents(page): def main(): # Configure. Web application in this sample requires cookies, redirect - cookies = cookielib.CookieJar() - cookie_handler = urllib2.HTTPCookieProcessor(cookies) - redirect_handler= urllib2.HTTPRedirectHandler() - debug_handler = urllib2.HTTPHandler(debuglevel=1) # print headers on console - opener = urllib2.build_opener(cookie_handler,redirect_handler,debug_handler) + cookies = http.cookiejar.CookieJar() + cookie_handler = urllib.request.HTTPCookieProcessor(cookies) + redirect_handler= urllib.request.HTTPRedirectHandler() + debug_handler = urllib.request.HTTPHandler(debuglevel=1) # print headers on console + opener = urllib.request.build_opener(cookie_handler,redirect_handler,debug_handler) # Constants site = 'http://localhost/' @@ -39,40 +39,40 @@ def main(): webAppUrl = site + path + webAppPage logoutUrl = site + path + logoutPage - print 'GET to show login page' - print opener.open(webAppUrl).read() + print('GET to show login page') + print(opener.open(webAppUrl).read()) - print 'POST to login with sample username and password, pass separate args for POST' - args = urllib.urlencode({'username':'user1', 'password':'123'}) + print('POST to login with sample username and password, pass separate args for POST') + args = urllib.parse.urlencode({'username':'user1', 'password':'123'}) page = opener.open(webAppUrl, args).read() # should show successful login - print page + print(page) if loginFailed(page): - print 'Login FAILED' + print('Login FAILED') - print 'GET with arg in URL to UpdateInt on server' + print('GET with arg in URL to UpdateInt on server') num = 99 wrongNum = 'xx' - numArg = urllib.urlencode({'num':num}) - print opener.open("%s?%s" % (webAppUrl,numArg)).read() + numArg = urllib.parse.urlencode({'num':num}) + print(opener.open("%s?%s" % (webAppUrl,numArg)).read()) - print 'GET to retrieve page with integer' + print('GET to retrieve page with integer') page = opener.open(webAppUrl).read() - print page - print '%s found in page, expected %s' % (intContents(page), num) - print + print(page) + print('%s found in page, expected %s' % (intContents(page), num)) + print() - print 'GET to logout' - print opener.open(logoutUrl).read() + print('GET to logout') + print(opener.open(logoutUrl).read()) - print 'GET to show login page -- again' - print opener.open(webAppUrl).read() + print('GET to show login page -- again') + print(opener.open(webAppUrl).read()) - print 'POST to login with username and WRONG password' - args = urllib.urlencode({'username':'user1', 'password':'321'}) # wrong pass + print('POST to login with username and WRONG password') + args = urllib.parse.urlencode({'username':'user1', 'password':'321'}) # wrong pass page = opener.open(webAppUrl, args).read() # should show login fail - print page + print(page) if loginFailed(page): - print 'Login FAILED' + print('Login FAILED') # No logout this time - we're not logged in diff --git a/samples/WebApplication/Stepper.py b/samples/WebApplication/Stepper.py index ef4f8ee..668ff4c 100644 --- a/samples/WebApplication/Stepper.py +++ b/samples/WebApplication/Stepper.py @@ -3,9 +3,9 @@ """ import re -import urllib -import urllib2 -import cookielib +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +import http.cookiejar # Default stepper configuration for # webapp.py running on localhost at port 8080 @@ -34,12 +34,12 @@ except ImportError: pass -print 'webAppUrl: %s' % webAppUrl # show which config file (if any) +print('webAppUrl: %s' % webAppUrl) # show which config file (if any) # handlers that are the same for all users -redirect_handler= urllib2.HTTPRedirectHandler() -debug_handler = urllib2.HTTPHandler(debuglevel=debuglevel) +redirect_handler= urllib.request.HTTPRedirectHandler() +debug_handler = urllib.request.HTTPHandler(debuglevel=debuglevel) # handlers that are different for each user are part of the session state @@ -48,9 +48,9 @@ class Session(object): One user's session state: cookies and handlers """ def __init__(self): - self.cookies = cookielib.CookieJar() - self.cookie_handler = urllib2.HTTPCookieProcessor(self.cookies) - self.opener = urllib2.build_opener(self.cookie_handler, + self.cookies = http.cookiejar.CookieJar() + self.cookie_handler = urllib.request.HTTPCookieProcessor(self.cookies) + self.opener = urllib.request.build_opener(self.cookie_handler, redirect_handler,debug_handler) session = dict() # each user's Session @@ -92,15 +92,15 @@ def TestAction(aname, args, modelResult): if user not in session: session[user] = Session() password = passwords[user] if args[1] == 'Correct' else wrongPassword - postArgs = urllib.urlencode({'username':user, 'password':password}) + postArgs = urllib.parse.urlencode({'username':user, 'password':password}) # GET login page page = session[user].opener.open(webAppUrl).read() if show_page: - print page + print(page) # POST username, password page = session[user].opener.open(webAppUrl, postArgs).read() if show_page: - print page + print(page) # Check conformance, reproduce NModel WebApplication Stepper logic: # check for login failed message, no check for positive indication of login result = 'Success' @@ -114,27 +114,27 @@ def TestAction(aname, args, modelResult): page = session[user].opener.open(logoutUrl).read() del session[user] # clear out cookie/session ID if show_page: - print page + print(page) elif aname == 'UpdateInt': user = users[args[0]] - numArg = urllib.urlencode({'num':args[1]}) + numArg = urllib.parse.urlencode({'num':args[1]}) page = session[user].opener.open("%s?%s" % (webAppUrl,numArg)).read() if show_page: - print page + print(page) elif aname == 'ReadInt': user = users[args[0]] page = session[user].opener.open(webAppUrl).read() if show_page: - print page + print(page) numInPage = intContents(page) if numInPage != modelResult: # check conformance return 'found %s in page, expected %s' % (numInPage, modelResult) else: return None else: - raise NotImplementedError, 'action %s not handled by stepper' % aname + raise NotImplementedError('action %s not handled by stepper' % aname) def Reset(): diff --git a/samples/WebApplication/WSGIVerboseConfig.py b/samples/WebApplication/WSGIVerboseConfig.py index 1f5df9b..e2b65f0 100644 --- a/samples/WebApplication/WSGIVerboseConfig.py +++ b/samples/WebApplication/WSGIVerboseConfig.py @@ -12,7 +12,7 @@ webAppUrl = site + path + webAppPage logoutUrl = site + path + logoutPage -print 'webAppUrl %s' % webAppUrl # debug +print('webAppUrl %s' % webAppUrl) # debug debuglevel = 1 # 1: print HTTP headers, 0: don't print # show_page = True diff --git a/samples/WebApplication/webapp.py b/samples/WebApplication/webapp.py index 1edd6ba..6c368fe 100644 --- a/samples/WebApplication/webapp.py +++ b/samples/WebApplication/webapp.py @@ -15,7 +15,7 @@ """ import pprint -import urlparse +import urllib.parse # page templates appear at the end of this file @@ -53,7 +53,7 @@ def application(environ, start_response): method = environ['REQUEST_METHOD'] length = int(environ['CONTENT_LENGTH']) request_body = wd.read(length) - vars = urlparse.parse_qs(request_body) + vars = urllib.parse.parse_qs(request_body) user = vars['username'][0] # vars[x] are lists, get first item passwd = vars['password'][0] if user in password and password[user] == passwd: @@ -80,7 +80,7 @@ def application(environ, start_response): and environ['REQUEST_METHOD'] == 'GET' and cookie in sessions): user = sessions[cookie] - vars = urlparse.parse_qs(environ['QUERY_STRING']) + vars = urllib.parse.parse_qs(environ['QUERY_STRING']) if 'num' in vars: integers[user] = str(vars['num'][0]) # vars[x] are lists, 1st item if 'str' in vars: diff --git a/samples/tracemultiplexer/tracemultiplexer.py b/samples/tracemultiplexer/tracemultiplexer.py index f85b74d..c8526b4 100644 --- a/samples/tracemultiplexer/tracemultiplexer.py +++ b/samples/tracemultiplexer/tracemultiplexer.py @@ -80,7 +80,7 @@ def tracecapture(thread_id, call, args): # call is action name program = (( 'listfiles', ), # thread 0 ( 'openfile', )) # thread 1 -threads = range(len(program)) # one element of program for each thread +threads = list(range(len(program))) # one element of program for each thread unsynchronized = False # False: use tracelock, True: ignore tracelock From e9dea07f527b0a063af6c546871b5529f71768ab Mon Sep 17 00:00:00 2001 From: Zohar Lorberbaum Date: Thu, 8 Jul 2021 13:51:06 -0700 Subject: [PATCH 03/11] cleanup --- .idea/.gitignore | 3 --- .idea/PyModel.iml | 12 ------------ .idea/inspectionProfiles/profiles_settings.xml | 6 ------ .idea/misc.xml | 4 ---- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 6 files changed, 39 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/PyModel.iml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/PyModel.iml b/.idea/PyModel.iml deleted file mode 100644 index 8b8c395..0000000 --- a/.idea/PyModel.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index e061cac..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9ef61b7..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 6fa205eb52773703b45c42dbc961aa3f598434e0 Mon Sep 17 00:00:00 2001 From: Zohar Lorberbaum Date: Thu, 8 Jul 2021 21:04:32 -0700 Subject: [PATCH 04/11] remove relative imports, fix imp deprecation warning --- bin/pma | 4 ++-- bin/pmg | 4 ++-- bin/pmt | 4 ++-- bin/pmv | 4 ++-- bin/trun | 2 +- bin/wsgirunner | 4 ++-- pymodel/FSM.py | 2 +- pymodel/ModelProgram.py | 2 +- pymodel/ProductModelProgram.py | 6 +++--- pymodel/TestSuite.py | 2 +- pymodel/__pycache__/Analyzer.cpython-38.pyc | Bin 0 -> 4921 bytes .../__pycache__/AnalyzerOptions.cpython-38.pyc | Bin 0 -> 1961 bytes pymodel/__pycache__/Dot.cpython-38.pyc | Bin 0 -> 2885 bytes pymodel/__pycache__/FSM.cpython-38.pyc | Bin 0 -> 4042 bytes .../__pycache__/GraphicsOptions.cpython-38.pyc | Bin 0 -> 2627 bytes pymodel/__pycache__/ModelProgram.cpython-38.pyc | Bin 0 -> 6524 bytes .../ProductModelProgram.cpython-38.pyc | Bin 0 -> 10753 bytes pymodel/__pycache__/TestSuite.cpython-38.pyc | Bin 0 -> 4184 bytes .../__pycache__/TesterOptions.cpython-38.pyc | Bin 0 -> 3171 bytes .../__pycache__/ViewerOptions.cpython-38.pyc | Bin 0 -> 2152 bytes pymodel/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 234 bytes pymodel/__pycache__/model.cpython-38.pyc | Bin 0 -> 2524 bytes .../observation_queue.cpython-38.pyc | Bin 0 -> 426 bytes pymodel/__pycache__/pma.cpython-38.pyc | Bin 0 -> 882 bytes pymodel/__pycache__/pmg.cpython-38.pyc | Bin 0 -> 715 bytes pymodel/__pycache__/pmt.cpython-38.pyc | Bin 0 -> 5025 bytes pymodel/__pycache__/trun.cpython-38.pyc | Bin 0 -> 300 bytes pymodel/__pycache__/wsgirunner.cpython-38.pyc | Bin 0 -> 1529 bytes pymodel/pma.py | 6 +++--- pymodel/pmg.py | 4 ++-- pymodel/pmt.py | 10 +++++----- pymodel/pmv.py | 2 +- 32 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 pymodel/__pycache__/Analyzer.cpython-38.pyc create mode 100644 pymodel/__pycache__/AnalyzerOptions.cpython-38.pyc create mode 100644 pymodel/__pycache__/Dot.cpython-38.pyc create mode 100644 pymodel/__pycache__/FSM.cpython-38.pyc create mode 100644 pymodel/__pycache__/GraphicsOptions.cpython-38.pyc create mode 100644 pymodel/__pycache__/ModelProgram.cpython-38.pyc create mode 100644 pymodel/__pycache__/ProductModelProgram.cpython-38.pyc create mode 100644 pymodel/__pycache__/TestSuite.cpython-38.pyc create mode 100644 pymodel/__pycache__/TesterOptions.cpython-38.pyc create mode 100644 pymodel/__pycache__/ViewerOptions.cpython-38.pyc create mode 100644 pymodel/__pycache__/__init__.cpython-38.pyc create mode 100644 pymodel/__pycache__/model.cpython-38.pyc create mode 100644 pymodel/__pycache__/observation_queue.cpython-38.pyc create mode 100644 pymodel/__pycache__/pma.cpython-38.pyc create mode 100644 pymodel/__pycache__/pmg.cpython-38.pyc create mode 100644 pymodel/__pycache__/pmt.cpython-38.pyc create mode 100644 pymodel/__pycache__/trun.cpython-38.pyc create mode 100644 pymodel/__pycache__/wsgirunner.cpython-38.pyc diff --git a/bin/pma b/bin/pma index c3eeb5c..11244d6 100755 --- a/bin/pma +++ b/bin/pma @@ -3,6 +3,6 @@ This is pma command in bin directory, runs pma.py module in pymodel directory """ -import pma # find pma.py anywhere on Python path +import pymodel.pma # find pma.py anywhere on Python path -pma.main() +pymodel.pma.main() diff --git a/bin/pmg b/bin/pmg index b813573..243800a 100755 --- a/bin/pmg +++ b/bin/pmg @@ -3,6 +3,6 @@ This is pmg command in bin directory, runs pmg.py module in pymodel directory """ -import pmg # find pmg.py anywhere on Python path +import pymodel.pmg # find pmg.py anywhere on Python path -pmg.main() +pymodel.pmg.main() diff --git a/bin/pmt b/bin/pmt index 2589c8a..0dfaff2 100755 --- a/bin/pmt +++ b/bin/pmt @@ -3,6 +3,6 @@ This is pmt command in bin directory, runs pmt.py module in pymodel directory """ -import pmt # find pmt.py anywhere on Python path +import pymodel.pmt # find pmt.py anywhere on Python path -pmt.main() +pymodel.pmt.main() diff --git a/bin/pmv b/bin/pmv index 04636b1..ada7921 100755 --- a/bin/pmv +++ b/bin/pmv @@ -3,6 +3,6 @@ This is pmv command in bin directory, runs pmv.py module in pymodel directory """ -import pmv # find pmv.py anywhere on Python path +import pymodel.pmv # find pmv.py anywhere on Python path -pmv.main() +pymodel.pmv.main() diff --git a/bin/trun b/bin/trun index 3d0949a..fa1946c 100755 --- a/bin/trun +++ b/bin/trun @@ -3,4 +3,4 @@ This is trun command in bin directory, runs trun.py module in pymodel directory """ -import trun # find and execute trun.py anywhere on Python path +import pymodel.trun # find and execute trun.py anywhere on Python path diff --git a/bin/wsgirunner b/bin/wsgirunner index 0c4bf5b..9ed1388 100755 --- a/bin/wsgirunner +++ b/bin/wsgirunner @@ -3,6 +3,6 @@ This is wsgirunner command in bin directory, runs wsgirunner.py module in pymodel directory """ -import wsgirunner # find wsgirunner.py anywhere on Python path +import pymodel.wsgirunner # find wsgirunner.py anywhere on Python path -wsgirunner.main() +pymodel.wsgirunner.main() diff --git a/pymodel/FSM.py b/pymodel/FSM.py index 2c04987..5d8cb6d 100644 --- a/pymodel/FSM.py +++ b/pymodel/FSM.py @@ -2,7 +2,7 @@ Interface to an FSM module (graph) used by ProductModelProgram """ -from .model import Model +from pymodel.model import Model class FSM(Model): diff --git a/pymodel/ModelProgram.py b/pymodel/ModelProgram.py index 9fce372..706d18e 100644 --- a/pymodel/ModelProgram.py +++ b/pymodel/ModelProgram.py @@ -9,7 +9,7 @@ import copy import inspect import itertools -from .model import Model +from pymodel.model import Model import collections class ModelProgram(Model): diff --git a/pymodel/ProductModelProgram.py b/pymodel/ProductModelProgram.py index 96f6bf1..d7fdb97 100644 --- a/pymodel/ProductModelProgram.py +++ b/pymodel/ProductModelProgram.py @@ -21,9 +21,9 @@ from operator import concat from collections import defaultdict -from .FSM import FSM -from .TestSuite import TestSuite -from .ModelProgram import ModelProgram +from pymodel.FSM import FSM +from pymodel.TestSuite import TestSuite +from pymodel.ModelProgram import ModelProgram from functools import reduce class ProductModelProgram(object): diff --git a/pymodel/TestSuite.py b/pymodel/TestSuite.py index f709b3b..eaa71fd 100644 --- a/pymodel/TestSuite.py +++ b/pymodel/TestSuite.py @@ -3,7 +3,7 @@ """ from operator import concat -from .model import Model +from pymodel.model import Model from functools import reduce class TestSuite(Model): diff --git a/pymodel/__pycache__/Analyzer.cpython-38.pyc b/pymodel/__pycache__/Analyzer.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..011c5d8981787bad97fb9d9c19a3268eecf365d2 GIT binary patch literal 4921 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 literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/AnalyzerOptions.cpython-38.pyc b/pymodel/__pycache__/AnalyzerOptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0984c9b4945e4fea3b4f6e12d48610f2609d87c GIT binary patch literal 1961 zcmZuy%Wm676eTHHk{LU8(yrRgtcnPbthAc|K@ijk+MosO1hBJFQK)0iP@0x6%nTii z-FEALNS6I2ZMz8a3*EHm&XBh1lmLh1o%=lZ%;lT?{W!q&A^Gsj!7vE^qL1yXhmUV@ zn{Tj)U>=B2L|K?e^C%2NZ{CxU?1}z$IPZ%cG58~x?}(uo;W-eyXTdmbCh@DvsAoZZ}XaKBQ^gWSyVY^Kb79o&nzc1 z!&n^Ob#c_~DFkGhGM1GoD+|dGFE6#E{W_DZ;JGyH(OYF#aZ$3PYRycYrs`^Z#MpN{ zS#2u{ri#vSUc+Zn7M4TCy>t@W6}L>8r=5gVx?F0WPf#}d{_JEz@j0^+#->(QBFYc2 z7eWy_P>Z(`+m4sO!A-bUoo6IU)w0%{%wtsm&)mH`WZS&DED-FZ&aA3Xc#GX5_v{o5 zJS15yRI&W8=Lscms0y)_ycwgD5}J8c8cEsO5*46kQ70DB&Q}s9zN>603v>~s1AtT& zD4rRMZF0II~h0Xy>KO))V;S4iZi#H&G}vo@JDs zDlW_4klV%DC06B^1f}PnL1(BFDAPG5cIyyID&g0roWO|h0wCuolfNy9Szw$xsBP1JaapKw-o#&D*WWl;dGkw zt8>i@<4(NU^l)lE3Lw|~c*3t#UgxZ+^9!()*mH$TFrX_(`*0!qcd|<3blQ%*0@|BZx$BTxdpNWLh)=UIh+ZbTcvs7t}v2K<> z$FV`!LFK0PJjP60_({EqoooS3^Jk#%BNMm${dD#QgWb#;oKqBh!RvhX0yBNq9;sQi zcH?Q*{TbSS2r|<9@bV>Y^97b5jKk=kLFCsx_wHAx_X%Som^jAn;l&2;!uJ9GW5l%Z zbR21tB58nQ7sP
    OTQ_Vpo_uW%c37KHBmbUfS)7Yk7)i^Y6|>OB~wp?kEDXP1&w c|3AN*(KG)CcpgI6(0{q#-;4SW<7jm6UpLWNqW}N^ literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/Dot.cpython-38.pyc b/pymodel/__pycache__/Dot.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a64bd95ffc68d7d6b48a8f39ac15a8ccf1f062f GIT binary patch literal 2885 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 G9p?x5PFwi^ literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/GraphicsOptions.cpython-38.pyc b/pymodel/__pycache__/GraphicsOptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b8be5eaca174e5a39f87591f2396c7bc26baa8a GIT binary patch literal 2627 zcma)8PmdEf6rY)7v&*mx6w0j!SL$I?C0U6>4}cIWDo~{YEA3KMsjAc&JBf?O9@(B` zlXI_p2RQai^n>Ku1L{}kq3zH1WW&N98YxcZ&HMj;?>)adILH%x-WTtG`QuuW{OKP) z{Iu}!7+-r22TO(tOIaq8*;LIt{Io)nW5E86gK}0R+4VI-j6aT)L3q4-bs?GoRXh( z)bul%*fP&QmL8C^87Ee#spJ=iJS!2FXvN7yDB-~2D9(}=Gog5X`1JI7mz0{-kW#ed z5;0W z5PoWO85p3!)23v~6~c4e<`mAszMhammnAY~iBN%=X-B3$KYeEC0>KeU)wHHl9{hnP z1JB~c^MVE$MaG=KtAbm*b<1;s2wg}>)E)9Z7b{ZH;x#mq30%w6$#A#w`<)3n2_a;Z=L|b=S7*V+%Ir;W!kU0I}S&tZQ zD_%G;=W;n9Accr5@gX6ax!?=Va`;Wjf{)4A=mnrL$n#_AxKT7phE#{#_9EQ80~b+y zU8tDXC)8ZOzQ|2pmGcc(k5~;^l+Gfz-ghQ!CFyeG3M~+QC54?udD=iOZ;}F^o@nKa zmZXrigLj*BholmZu8_>WB>lcKR9WF0MSPo9 zilG|rYNN=U%9{7Qbii_y)k;QaURHqEJfClk$fq>G<-I@*-$jUQAj%>L(WJfLif<)B zWz$T^6oNzH$Z=6{M4+a5XfsP@bdGLB9qKUvKGuezY(SKozyh70S5o5v zU_!k^uDJs$$i<6Bvlr4xA8>n!Rs}>$R4Ld)_fEBUVw+x{LF$4f1x)yW6?<0waUVw+mN$)&eZ zV3Z2v5DA)4A!{_wwpvYF^vF!lfjd9ID&)aqFc5&%F#OP+8&p%Ys+LYv(aSB^BIleM zK~cHb3Q#Ca#F#~#Cz2L%Fa#?YUr_R~Nwp-EILDL+lws@w?_`dC$H4eMzHFriIo88Y zZ85D1zbrod?Bd}GzV=5PF8MX}%Zgz=`PKAhCyg|$C(y33`~%u`w$BdO4R({=y5Rea z-3ILr`+P%to4rf0((b-kWqrNc>-PbXs&>PUbp}{lC#%OV10~M#xJx|NfMQuQKf3*> z?qXc>39ThwSn|;Cr%fwA>bBNxEEc}rZCKXr8J86d%GK>phsC-R8+!HNY@62D!S|c# z(F=1R%vs;)muxq}S^s=>tKV1p)MMi;qG1Tkp$kXO)O>y8^1IdX%Ti!HMoz49zt|#N z#`QRWDtdw-*Q+B*aLW9cDpUJha%zDz>z&)IK;SI-Suv4V`+R@-?@Ug z`sxZNwm5-dzao&(P8Dtu!<;fUiqmzS2igdEx4$Rm0OrH-_x&SmI%AJkm_TE0#-5YKfZymq%f9Vi?AIip4F#>_f}f_}VXUBu-$h_I}p>GSBw*{{!1ZOVt1X literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/ModelProgram.cpython-38.pyc b/pymodel/__pycache__/ModelProgram.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3443baa12cf2889668d81f4f2d96062ea79c115b GIT binary patch literal 6524 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 lc3YvO;5D|?_iv2KG&*AYjN;Wu994h zvZBpLg%teo8UIXSNuvLd3z?$bQ}Lkr4TN&qtPGMoOJa>y&CJt~l2|SiT5|ViW&O@s8LM z7hkZ>1#wAS#z;$iUtGcenz$;q@xLyvK?T>##v@$$fNq?SW@itz1HAX}7MDP@bH*ml zgiYLu_mZ^h95}Doe%n<`V7>pQ$>wUcg(pAPyUEEoNsT zB#fr2hb~gXYcBFN8rqpS$4szOS6!vnu}d`as&NV^ep+(0h04O<>n;k|a0@)Y-&6YeKEq z`S>~Be>{GU86RV=8Pfusq6>z$L=AQe#{X(^Jp1_*u8(5P<20=hTkxa!E6ER~%y@q= z%#{XO<4kb*R3; zJLvb0G|u92w<{B34!;2OyZzos#k!woGpP)tvOx}J{HttIGqUY^oY2}rk>&Q=Yi0c? zE@G{fYG6REgV?PGs0v}hDgCrxXthbr&1yR!Xc<~eHt0SEmSqqRhcXkT--ipA?l5Wl zDx|F*`ki_vpF}hfSVjh)maJisx(q@x6h8uSnk{C2Zi{WY>#XU9=m#uh>#XGlY#s0T z;sTX(A(miC7oGbTM@KuFX;*vJ@yYzhwfg|psf(AvlHIFSv{Gw_A|T%na}|Ug4g3~-+lHQ>Zg!t z0umX{1|C@4-G<3v2IttDuoL&dQN$hr)k|e&K(DS~KwYI#*Wx$8R8JqR2v{~gP7*oP z@Zw)#v9X5ZZ7$(*z9KP^6wBzD!OBQS`UWWI=lKB8!&m zrqj>9id8?(w2`TjQ@saPt3qxJ;u9GmuneWreQ6T9)hP4Hn%fJFuB=>AD7g}u&WKEI zikM6?;#i}jWJ~r9pH}>G)W3fe*zs2AJR~G76 zTKCD4A5aZSrG`=GU(s0TW$MDPh|16md8!A~{u#PJ8`|jA`6JRCTJe6SX$z=_e^ zL#an9;}kr3-ZeFV3167?bCHxMg-?5_3q{qc~G8(pQdKuO2Nh;%PGz6D9M_wG|qZE~qWryN` z5V~?Vs~MXKe*zHfs~e#0;2caj2CT=L0CSP%n-;b8>R6k+en<;=0jbo%Gt0e!Nl1N&`K@i<2HmRFLNCj3S z6gOABIJ}=*|LkG1un}zw_gr79`DcsXh{+UBjPsf#R^6&QORBHjMJj!3Fs=~+^*nTb_} z)T?bnth6He=S8Y!_uK4~_6B{AW${2pQQ3?l`z3~UD~cYE;?&NVPbpK|DynB}ZZshN zF?Dx{+#>P;kzW$|kjO_w7U|QpOg%hH~;_u literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/TesterOptions.cpython-38.pyc b/pymodel/__pycache__/TesterOptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dac6fa2508094899c94654f73ad57b8d9f2713e GIT binary patch literal 3171 zcmai1TW=dh6ka>FlP*bblxryrmqJ{;NlRZUR3V_E5=cp^CKZw^6egarGwt3wGn>Tw z%xnHb`^aDND-Tuw01v#tcV>3uO>o?56f%e zR&G_68pcz>xSkY=Os!$#OtX8_jZDax?MP##4qpHx=(lFee#bqojnrIpf*?;Udzg7W zzp;ci8OFl!w1PEOe^NkWu`-rrW0s|o0W8V1r0+#6S;`Y>*y^FO`z+1EwS3A2%I4P8XW~`<)2Ai#%Oqs6$__LF-N0f;J zB@f*auO4wulqEr^61Yp1=EKt5Ss1{gpg5Hz3S=BBRESGXWpAdvrjXEItI36g6w!*T zuz7(}bM?s(48srn7jIVRYGt1ND%su-9}bU?P$P;5@ZIS>jT|<8IJN5}%%b+DsRx0b zLsMl?1Hg9|S)>7PNM+LOPH$PAJm>2vz%WZAY>&qAbdK~=o1v|%E^wW{j=D4>nR2Z% zS5+_p#pZEc0#2yGqq`W+ahwl^nT+h7GU|&x8EWaP4z&`aM3A;O<|LtRZuVjok~I)d zw=jGnM?^!-zeT*_PjrtvEidU2?TIrY6mK1OE7`JG5og6Yab8?F z?v7iP^rCnhdoMNiz9Zfh?}_)t2aUZSipx&+QA2h`eC#kjX~>xP)UErhA^Ti>0o<$N zOTw*CzY?q1+ZWdw>%JBPr!Q*A*2KD#U9V+N+s^_J`Hju89r1E0iui~~XV&9+E>ltd z_=9_=vaf0cy=)ljX-fB0N_6sosWFI_MZ8|bcpNjY%JkdN=CyYAWt;!sia6)S|ocQ8BjdNM@}r}in`jy@_laPfC)L~pg(rqjT{(J zeeW;KI(C}N*T8oO2(a@KXC^6LGQVVT5s$GK`{{5QjZHp@nBb zd2_oaN#gc;#`SAP%v@ts1Y2*1I#qU~NENoc{qt$UT&0lai0)8nv{APT2vE3+Z#{ zh))`%4;L^Bjv6!-iaggO&5<)V01u>|g1GbqVZiZ2YQv-*tr4^)=oE1>FI>^D&0|_e z5YwV|V}%^TR36aVWuDBkjH*iI6fSjJ{r51yR+jk#1U)P&&^rrWV?vA z`7_Yx(Vgz{&#m7w*Q>+jdOmeUzP{s5sayUOih|PTpyLXD z<`-OoR@iF)+iNeMT$kD}OPXK*|Jmt%WeBwa;fByM9>m^(?>%oT$0A*>CH!kitc( ztX<;Jxr6@c0>0~uxZJ|ekg}lV-Z%S;v&G$Ak&SkDhf9c+4;`)03v?mP&Lp?|b$Vw@ Yw>?XIN2O}$KZ;Igx!t)GwwEsd2UVc%UH||9 literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/ViewerOptions.cpython-38.pyc b/pymodel/__pycache__/ViewerOptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..282f47379b848c17d311a5489b06db6986837564 GIT binary patch literal 2152 zcmai0TW=dh6yA&NBumn!EiJbK0~8b^uTu~b2&#&z5=e+NmEs~~gv{7y91q>ySZ42YY*whv(Vo*)Ml@Jnv8Y zvH67f_!_VI6a#sEk9-OeKMneU?@`zfMIb_IJoftyYEtVruivCC+Qx5-c22x@RDB#B z=Y>?6VLT(o^=X-ktT1e>G`l}LQX~@ggA|X1j-JDZ*Y@YT^^W^o8=?7+f#>Dvl>MY? zJ3q73ZZeET(TfJUjNM^IW~Yf@xmKr|r>vOpg2`;E9ty+qly@+kb{v*(HP0tgSw*A@ zRw(S3BUYf6F6lsYvZH~d=CLaB$ey&>75h+d&x+M^4Pn+vUrv`?4lGF{n_y-HUhyGE~V$X=&6 z=uLWyuG8DAz3fW0^9!{y#5!BY=M?W22D5c6s~j`_$QxOofgiHxd~ zJMP=kBePLblCb5utX^W@oE)K`4MS;zH^VX^NKl8#G0PN-`D&M~l-jmw2QXi|X-q_t z!?mb(;#izH2i50mp9>KiYnA@5SJ+9b{WwnfSx@uKNY^^%q0EZsgiKW59q}`nmMP21 zbcpk0D|fB)=`9EC6^L}Is z;2adX6!Qx&u2pxAQ)yeE(?n~wnKE`W8v)*3))7 zbii>72oSd5aaLWmFxIgwu*|QVBdl(m17gh%9BcK+Ah=!?#j$O;J>;$*0!4M;TDk4J zR?civ*}blzMgv>-sVVdz%|; z%=6v%z4q38YcL=+8Vvf|V5KI>YUt1o*0O`-)X%d!-@XMd%0%4up=<2F6^+Jj(AbND H?d$&n3rvp? literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/__init__.cpython-38.pyc b/pymodel/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62367ec76120b119c893765e815d3b656a1dd488 GIT binary patch literal 234 zcmYk0F$w}P5Jj^#%7S;8*2WaJB7(IdhzK?@n+)z?OqL{ztT*yXwiX`1&dSls5C47s z6f?6O4oks$n&)B1`S_N96W|Yf4}#qRAn4prO&`q>l(G z0A(=RLxn&-W$heVxKOSkcmzib@{lmM&A92x)u_1lm9oJobrZ42=YDuksDlckI8`DW@<+#yhg{?lKatuTmx+=6{>fDGS*d0P*cUN3(?mBoFo{KI0IetuEC4>B z@g(O#kmQL80>tWP;<*f9UsB{6b_v755O-^lNpeP{Y2#3hEVa-14W8?4 zT=iS%UElWyT+~Q$qO{@K2qQC@!vQ%@jON9!D_8m<1ZnNE;q4*Z`g0g08(R=UyAI&k ziJZuN#l~*bi9C3AqArB8S9QpV5F_7SI7g^Mxb+SU3v0mwwy^9$hiz!Q-C?qo-ObJ({P5nn*PfWi~p5ERCLA(65YU7l16&5sm)rqUyB~z616e zL73}Vo;BYhgJl9jVAb-%Ii8KTHZZ(qXH4Dyoy8VKvQFIAsg= zZ2LEBVVz;(t><0kF04Q77w&OQ!z&wUm4n`cfH(>tZ2v)Ji7QKW0D;J=x-hHy`$TW!LlvclUA{Pcevc=I3CI{>HN#o?Bp`)vKJqk&6l8+RL0 zP-v=dqoivHghhGWT2Iq9BI22>=UF>YPuhFFeOp%7!r4}rL&SIlg8{;wL1nqL^^UPG z=`C=YuQq%gF2A~_gL}`MODxM!j{o%jVis#!&tl6ZYit_)tzDjjptlUpnM$-x>a{7v@k|dpQ<{hoTZX> zst<6xi`rF4l51pI3a;8%`tt>B(Wnx(xoPz+xScI$ey`@xa@WzoBMDdiAb^BGDuSTe z41yP>NSiX$!?WnXk8nn3tp=p9Lf)Y+N#;Gfx6yl#zF|Y|20>JWfa=wWqlMd0s@{I> M!2!Z)DxST60V98PZU6uP literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/observation_queue.cpython-38.pyc b/pymodel/__pycache__/observation_queue.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d0074e1e268f066e29c062c24c47120a393d8ea GIT binary patch literal 426 zcmY*WyH3L}6m`8CKk$i)9-WvKWC zrrxF?l`HuiUHkax9(z5TWsGW9?;c(w#=Z{CX~ooB(Q+3Af)z|eB9>7R4ICG-7!@Oo z-ja7#j75CU=E?Rm)0IQ}%zdL3JOvC0!J&Yv1Lx5gv=01+d$8z(Rq!jXBm%Dwm)s)D zR>6~~anLAeyaWBBpw%Lf$Z6Vvnk%Rfn$~FRkM5@RkZ~o3GcHL-0&NbKT*~gB_HU9! z_D#{)-OYT`Ps>v1x-9$iT1$z?S&B2ivB*WE9{UM*ovJteSRm!<$3wUeW~byZz|S&Y zQ+AhcrM4B?iibA8)&lcYcS|I5(@_fIlQ${{n}zB6ba{+;Nq@myN{~#`C>Z{<*Bv1UGzyb|%N6OP?(r7ABWtwAB~}6WWDBIVWvHi9O-oeb4OtUy z-#aBK(Ve>m3A7hs-O=`)ziHNkcI~!nZ-b|FfHoK-i0RAR*wl|m3F19L1GKi_#lzCt z!i@-lMD${HdZYrSK@asj0H`HT)x0jQ_W5KYpw@J@f2l|npNe*8WgjV!Qer4=_&`Eu z5%Y5{l?b_hM;?c}2xyuBA~#nr<^!Eb=&$g7p)=c>6DPW!FKK6^oQP4LxRq zwD~RT{H4U4hDhaPh0Y7jsNx^RkTN%G+EdY*0ZDRZCn-%Zes^o|;HC0E2KBQ|w`n__z<~*>8tD#~<+ktDRW}knZMR1M`9F<9yY~$7FszC1nS(mpG0-exabna|CVlo@Ey@Wggm_Q^a~NJ5TMZz_2b2>Ab!!#-{hY zHx*Lo+=|}x4x4CI_3C-GiF`XePu3W!_Hp~*F6~2Y3ndLe5m$Cnlwu4KdkMe`K~F)A z1Y^+;e2YGLOSHrd)=#Y0;1%ZhhUjN-=ifq)mbgyki47Xy_86`3(reI{P@g#~!!5e> zx!?GY(304wA&rZOK|7$yDgdSkn39XN8`v5YEyAZ}08RD}w8Lmp=h_IC>#1q|Qb+)F zAuYQWW!)0-wXki%SW(qVTgKW@&1^lh+IP7}R_D?bj{Ez3BuYaMr5aio-lbBdEo%Bu zs>^-M4(tasQrm&G#MPz##8k918RbUEyb?5+xZ@|%2T-tW5!%0V;B9XjTqx< s3AZZ%Fb&R-izJOW@EC)%q378dUD3Mw& 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 literal 0 HcmV?d00001 diff --git a/pymodel/__pycache__/trun.cpython-38.pyc b/pymodel/__pycache__/trun.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5825ecc423ab2b7af75ed32e97cd7b0fe90fe7a8 GIT binary patch literal 300 zcmYk1Jx;?w5QS&_lPrd$r=y@r!(IR(B&d);NGQz;WE}4ZIr1O7Yb4Y9B2aQ4s%=w1 zu0X{&G(2hEo7a4!SytDssx!oKVP0t;x2C9_pD@5t^#Nx5XJn5x~2nmG1AXX?+yXggKRS;TfC14MfYO57!STs1X(@dGn47Nj? zC^?lY|3iD^Uz#fi;9uafe4dnTs%1vDpP%jL?|tt*KkN57!S|Q?^T)l2kU#un`wK96 zgl3tv;*QKBqHMw&uf{x=YlqXBLEwlkY=OhVE7rtx-;rGZL*N(pH; zR?h8dVdhn(&4XqkzOTD&_6H%&Dd*=?ZL}9fVTGG&QOTJW^+d@3K+I}22YhV7aqKU~ zQ!%b9CyUBrm-l0TT*JjeR<84M%c}?c3;3!pq^IDJb0Pj0)P5r#9y~k{9;(u_n)4@< zZgPto@QMoeslw>7tIYyvrSqsk6o7Mmp~rL2fM}$f?m>sOv8=>M3p=fi)0I}j)xx8= z%gL+PgU@Jg<*6QfzPG;wlgDWGU389|`J7=YP@#&D9mo|&uGlp}?31G<+i#Zf{(gho z+n}gi@#uX*mLDHuE;h{HWJFn5;In%|>4}_|4s;wG41zXvi$=G}jkXP^x@@41n=Z;3 zmrCWEfVRBT<%pC0otRyq>$-nF89u`$cDO8SGty=x=d7)Aq z&3a>mcJn(I#ZZT6-9;q?M=r^8wkB)3qV^p}ea$)M`xRpHHd!+ltk@-82dm<!$`F zY{gdOES|8p$R)+@N-ySr5%ZPf9Sg?`O=-!twj z>ZE%pvNyc^RR28J)_sMypwuQytadp}Wc@C8ay%v)kIEpP@8NGaLo7+b{TO?(Mx;&>B;jgXk8L__|nfdj(qMZvK~Mf SrAW#i3-583Mrn}VOX Date: Thu, 8 Jul 2021 21:27:54 -0700 Subject: [PATCH 05/11] gitignore --- .gitignore | 26 ++++++++++++++++++ pymodel/__pycache__/Analyzer.cpython-38.pyc | Bin 4921 -> 0 bytes .../AnalyzerOptions.cpython-38.pyc | Bin 1961 -> 0 bytes pymodel/__pycache__/Dot.cpython-38.pyc | Bin 2885 -> 0 bytes pymodel/__pycache__/FSM.cpython-38.pyc | Bin 4042 -> 0 bytes .../GraphicsOptions.cpython-38.pyc | Bin 2627 -> 0 bytes .../__pycache__/ModelProgram.cpython-38.pyc | Bin 6524 -> 0 bytes .../ProductModelProgram.cpython-38.pyc | Bin 10753 -> 0 bytes pymodel/__pycache__/TestSuite.cpython-38.pyc | Bin 4184 -> 0 bytes .../__pycache__/TesterOptions.cpython-38.pyc | Bin 3171 -> 0 bytes .../__pycache__/ViewerOptions.cpython-38.pyc | Bin 2152 -> 0 bytes pymodel/__pycache__/__init__.cpython-38.pyc | Bin 234 -> 0 bytes pymodel/__pycache__/model.cpython-38.pyc | Bin 2524 -> 0 bytes .../observation_queue.cpython-38.pyc | Bin 426 -> 0 bytes pymodel/__pycache__/pma.cpython-38.pyc | Bin 882 -> 0 bytes pymodel/__pycache__/pmg.cpython-38.pyc | Bin 715 -> 0 bytes pymodel/__pycache__/pmt.cpython-38.pyc | Bin 5025 -> 0 bytes pymodel/__pycache__/trun.cpython-38.pyc | Bin 300 -> 0 bytes pymodel/__pycache__/wsgirunner.cpython-38.pyc | Bin 1529 -> 0 bytes 19 files changed, 26 insertions(+) create mode 100644 .gitignore delete mode 100644 pymodel/__pycache__/Analyzer.cpython-38.pyc delete mode 100644 pymodel/__pycache__/AnalyzerOptions.cpython-38.pyc delete mode 100644 pymodel/__pycache__/Dot.cpython-38.pyc delete mode 100644 pymodel/__pycache__/FSM.cpython-38.pyc delete mode 100644 pymodel/__pycache__/GraphicsOptions.cpython-38.pyc delete mode 100644 pymodel/__pycache__/ModelProgram.cpython-38.pyc delete mode 100644 pymodel/__pycache__/ProductModelProgram.cpython-38.pyc delete mode 100644 pymodel/__pycache__/TestSuite.cpython-38.pyc delete mode 100644 pymodel/__pycache__/TesterOptions.cpython-38.pyc delete mode 100644 pymodel/__pycache__/ViewerOptions.cpython-38.pyc delete mode 100644 pymodel/__pycache__/__init__.cpython-38.pyc delete mode 100644 pymodel/__pycache__/model.cpython-38.pyc delete mode 100644 pymodel/__pycache__/observation_queue.cpython-38.pyc delete mode 100644 pymodel/__pycache__/pma.cpython-38.pyc delete mode 100644 pymodel/__pycache__/pmg.cpython-38.pyc delete mode 100644 pymodel/__pycache__/pmt.cpython-38.pyc delete mode 100644 pymodel/__pycache__/trun.cpython-38.pyc delete mode 100644 pymodel/__pycache__/wsgirunner.cpython-38.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c65282 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Virtualenv files +bin/ +lib/ +Lib/ +Scripts/ +include/ +Include/ +pip-selfcheck.json +selenium/ +share/ +.env +.python-version + +# Python Files +*.Python +__pycache__/ +*.pyd +*.pyc +.functional.backup +.ipynb_checkpoints +environment.pickle + +# IDE Files +.idea +.cache +.vscode diff --git a/pymodel/__pycache__/Analyzer.cpython-38.pyc b/pymodel/__pycache__/Analyzer.cpython-38.pyc deleted file mode 100644 index 011c5d8981787bad97fb9d9c19a3268eecf365d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4921 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 diff --git a/pymodel/__pycache__/AnalyzerOptions.cpython-38.pyc b/pymodel/__pycache__/AnalyzerOptions.cpython-38.pyc deleted file mode 100644 index f0984c9b4945e4fea3b4f6e12d48610f2609d87c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1961 zcmZuy%Wm676eTHHk{LU8(yrRgtcnPbthAc|K@ijk+MosO1hBJFQK)0iP@0x6%nTii z-FEALNS6I2ZMz8a3*EHm&XBh1lmLh1o%=lZ%;lT?{W!q&A^Gsj!7vE^qL1yXhmUV@ zn{Tj)U>=B2L|K?e^C%2NZ{CxU?1}z$IPZ%cG58~x?}(uo;W-eyXTdmbCh@DvsAoZZ}XaKBQ^gWSyVY^Kb79o&nzc1 z!&n^Ob#c_~DFkGhGM1GoD+|dGFE6#E{W_DZ;JGyH(OYF#aZ$3PYRycYrs`^Z#MpN{ zS#2u{ri#vSUc+Zn7M4TCy>t@W6}L>8r=5gVx?F0WPf#}d{_JEz@j0^+#->(QBFYc2 z7eWy_P>Z(`+m4sO!A-bUoo6IU)w0%{%wtsm&)mH`WZS&DED-FZ&aA3Xc#GX5_v{o5 zJS15yRI&W8=Lscms0y)_ycwgD5}J8c8cEsO5*46kQ70DB&Q}s9zN>603v>~s1AtT& zD4rRMZF0II~h0Xy>KO))V;S4iZi#H&G}vo@JDs zDlW_4klV%DC06B^1f}PnL1(BFDAPG5cIyyID&g0roWO|h0wCuolfNy9Szw$xsBP1JaapKw-o#&D*WWl;dGkw zt8>i@<4(NU^l)lE3Lw|~c*3t#UgxZ+^9!()*mH$TFrX_(`*0!qcd|<3blQ%*0@|BZx$BTxdpNWLh)=UIh+ZbTcvs7t}v2K<> z$FV`!LFK0PJjP60_({EqoooS3^Jk#%BNMm${dD#QgWb#;oKqBh!RvhX0yBNq9;sQi zcH?Q*{TbSS2r|<9@bV>Y^97b5jKk=kLFCsx_wHAx_X%Som^jAn;l&2;!uJ9GW5l%Z zbR21tB58nQ7sP
      OTQ_Vpo_uW%c37KHBmbUfS)7Yk7)i^Y6|>OB~wp?kEDXP1&w c|3AN*(KG)CcpgI6(0{q#-;4SW<7jm6UpLWNqW}N^ diff --git a/pymodel/__pycache__/Dot.cpython-38.pyc b/pymodel/__pycache__/Dot.cpython-38.pyc deleted file mode 100644 index 9a64bd95ffc68d7d6b48a8f39ac15a8ccf1f062f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2885 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 G9p?x5PFwi^ diff --git a/pymodel/__pycache__/GraphicsOptions.cpython-38.pyc b/pymodel/__pycache__/GraphicsOptions.cpython-38.pyc deleted file mode 100644 index 8b8be5eaca174e5a39f87591f2396c7bc26baa8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2627 zcma)8PmdEf6rY)7v&*mx6w0j!SL$I?C0U6>4}cIWDo~{YEA3KMsjAc&JBf?O9@(B` zlXI_p2RQai^n>Ku1L{}kq3zH1WW&N98YxcZ&HMj;?>)adILH%x-WTtG`QuuW{OKP) z{Iu}!7+-r22TO(tOIaq8*;LIt{Io)nW5E86gK}0R+4VI-j6aT)L3q4-bs?GoRXh( z)bul%*fP&QmL8C^87Ee#spJ=iJS!2FXvN7yDB-~2D9(}=Gog5X`1JI7mz0{-kW#ed z5;0W z5PoWO85p3!)23v~6~c4e<`mAszMhammnAY~iBN%=X-B3$KYeEC0>KeU)wHHl9{hnP z1JB~c^MVE$MaG=KtAbm*b<1;s2wg}>)E)9Z7b{ZH;x#mq30%w6$#A#w`<)3n2_a;Z=L|b=S7*V+%Ir;W!kU0I}S&tZQ zD_%G;=W;n9Accr5@gX6ax!?=Va`;Wjf{)4A=mnrL$n#_AxKT7phE#{#_9EQ80~b+y zU8tDXC)8ZOzQ|2pmGcc(k5~;^l+Gfz-ghQ!CFyeG3M~+QC54?udD=iOZ;}F^o@nKa zmZXrigLj*BholmZu8_>WB>lcKR9WF0MSPo9 zilG|rYNN=U%9{7Qbii_y)k;QaURHqEJfClk$fq>G<-I@*-$jUQAj%>L(WJfLif<)B zWz$T^6oNzH$Z=6{M4+a5XfsP@bdGLB9qKUvKGuezY(SKozyh70S5o5v zU_!k^uDJs$$i<6Bvlr4xA8>n!Rs}>$R4Ld)_fEBUVw+x{LF$4f1x)yW6?<0waUVw+mN$)&eZ zV3Z2v5DA)4A!{_wwpvYF^vF!lfjd9ID&)aqFc5&%F#OP+8&p%Ys+LYv(aSB^BIleM zK~cHb3Q#Ca#F#~#Cz2L%Fa#?YUr_R~Nwp-EILDL+lws@w?_`dC$H4eMzHFriIo88Y zZ85D1zbrod?Bd}GzV=5PF8MX}%Zgz=`PKAhCyg|$C(y33`~%u`w$BdO4R({=y5Rea z-3ILr`+P%to4rf0((b-kWqrNc>-PbXs&>PUbp}{lC#%OV10~M#xJx|NfMQuQKf3*> z?qXc>39ThwSn|;Cr%fwA>bBNxEEc}rZCKXr8J86d%GK>phsC-R8+!HNY@62D!S|c# z(F=1R%vs;)muxq}S^s=>tKV1p)MMi;qG1Tkp$kXO)O>y8^1IdX%Ti!HMoz49zt|#N z#`QRWDtdw-*Q+B*aLW9cDpUJha%zDz>z&)IK;SI-Suv4V`+R@-?@Ug z`sxZNwm5-dzao&(P8Dtu!<;fUiqmzS2igdEx4$Rm0OrH-_x&SmI%AJkm_TE0#-5YKfZymq%f9Vi?AIip4F#>_f}f_}VXUBu-$h_I}p>GSBw*{{!1ZOVt1X diff --git a/pymodel/__pycache__/ModelProgram.cpython-38.pyc b/pymodel/__pycache__/ModelProgram.cpython-38.pyc deleted file mode 100644 index 3443baa12cf2889668d81f4f2d96062ea79c115b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6524 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 lc3YvO;5D|?_iv2KG&*AYjN;Wu994h zvZBpLg%teo8UIXSNuvLd3z?$bQ}Lkr4TN&qtPGMoOJa>y&CJt~l2|SiT5|ViW&O@s8LM z7hkZ>1#wAS#z;$iUtGcenz$;q@xLyvK?T>##v@$$fNq?SW@itz1HAX}7MDP@bH*ml zgiYLu_mZ^h95}Doe%n<`V7>pQ$>wUcg(pAPyUEEoNsT zB#fr2hb~gXYcBFN8rqpS$4szOS6!vnu}d`as&NV^ep+(0h04O<>n;k|a0@)Y-&6YeKEq z`S>~Be>{GU86RV=8Pfusq6>z$L=AQe#{X(^Jp1_*u8(5P<20=hTkxa!E6ER~%y@q= z%#{XO<4kb*R3; zJLvb0G|u92w<{B34!;2OyZzos#k!woGpP)tvOx}J{HttIGqUY^oY2}rk>&Q=Yi0c? zE@G{fYG6REgV?PGs0v}hDgCrxXthbr&1yR!Xc<~eHt0SEmSqqRhcXkT--ipA?l5Wl zDx|F*`ki_vpF}hfSVjh)maJisx(q@x6h8uSnk{C2Zi{WY>#XU9=m#uh>#XGlY#s0T z;sTX(A(miC7oGbTM@KuFX;*vJ@yYzhwfg|psf(AvlHIFSv{Gw_A|T%na}|Ug4g3~-+lHQ>Zg!t z0umX{1|C@4-G<3v2IttDuoL&dQN$hr)k|e&K(DS~KwYI#*Wx$8R8JqR2v{~gP7*oP z@Zw)#v9X5ZZ7$(*z9KP^6wBzD!OBQS`UWWI=lKB8!&m zrqj>9id8?(w2`TjQ@saPt3qxJ;u9GmuneWreQ6T9)hP4Hn%fJFuB=>AD7g}u&WKEI zikM6?;#i}jWJ~r9pH}>G)W3fe*zs2AJR~G76 zTKCD4A5aZSrG`=GU(s0TW$MDPh|16md8!A~{u#PJ8`|jA`6JRCTJe6SX$z=_e^ zL#an9;}kr3-ZeFV3167?bCHxMg-?5_3q{qc~G8(pQdKuO2Nh;%PGz6D9M_wG|qZE~qWryN` z5V~?Vs~MXKe*zHfs~e#0;2caj2CT=L0CSP%n-;b8>R6k+en<;=0jbo%Gt0e!Nl1N&`K@i<2HmRFLNCj3S z6gOABIJ}=*|LkG1un}zw_gr79`DcsXh{+UBjPsf#R^6&QORBHjMJj!3Fs=~+^*nTb_} z)T?bnth6He=S8Y!_uK4~_6B{AW${2pQQ3?l`z3~UD~cYE;?&NVPbpK|DynB}ZZshN zF?Dx{+#>P;kzW$|kjO_w7U|QpOg%hH~;_u diff --git a/pymodel/__pycache__/TesterOptions.cpython-38.pyc b/pymodel/__pycache__/TesterOptions.cpython-38.pyc deleted file mode 100644 index 3dac6fa2508094899c94654f73ad57b8d9f2713e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3171 zcmai1TW=dh6ka>FlP*bblxryrmqJ{;NlRZUR3V_E5=cp^CKZw^6egarGwt3wGn>Tw z%xnHb`^aDND-Tuw01v#tcV>3uO>o?56f%e zR&G_68pcz>xSkY=Os!$#OtX8_jZDax?MP##4qpHx=(lFee#bqojnrIpf*?;Udzg7W zzp;ci8OFl!w1PEOe^NkWu`-rrW0s|o0W8V1r0+#6S;`Y>*y^FO`z+1EwS3A2%I4P8XW~`<)2Ai#%Oqs6$__LF-N0f;J zB@f*auO4wulqEr^61Yp1=EKt5Ss1{gpg5Hz3S=BBRESGXWpAdvrjXEItI36g6w!*T zuz7(}bM?s(48srn7jIVRYGt1ND%su-9}bU?P$P;5@ZIS>jT|<8IJN5}%%b+DsRx0b zLsMl?1Hg9|S)>7PNM+LOPH$PAJm>2vz%WZAY>&qAbdK~=o1v|%E^wW{j=D4>nR2Z% zS5+_p#pZEc0#2yGqq`W+ahwl^nT+h7GU|&x8EWaP4z&`aM3A;O<|LtRZuVjok~I)d zw=jGnM?^!-zeT*_PjrtvEidU2?TIrY6mK1OE7`JG5og6Yab8?F z?v7iP^rCnhdoMNiz9Zfh?}_)t2aUZSipx&+QA2h`eC#kjX~>xP)UErhA^Ti>0o<$N zOTw*CzY?q1+ZWdw>%JBPr!Q*A*2KD#U9V+N+s^_J`Hju89r1E0iui~~XV&9+E>ltd z_=9_=vaf0cy=)ljX-fB0N_6sosWFI_MZ8|bcpNjY%JkdN=CyYAWt;!sia6)S|ocQ8BjdNM@}r}in`jy@_laPfC)L~pg(rqjT{(J zeeW;KI(C}N*T8oO2(a@KXC^6LGQVVT5s$GK`{{5QjZHp@nBb zd2_oaN#gc;#`SAP%v@ts1Y2*1I#qU~NENoc{qt$UT&0lai0)8nv{APT2vE3+Z#{ zh))`%4;L^Bjv6!-iaggO&5<)V01u>|g1GbqVZiZ2YQv-*tr4^)=oE1>FI>^D&0|_e z5YwV|V}%^TR36aVWuDBkjH*iI6fSjJ{r51yR+jk#1U)P&&^rrWV?vA z`7_Yx(Vgz{&#m7w*Q>+jdOmeUzP{s5sayUOih|PTpyLXD z<`-OoR@iF)+iNeMT$kD}OPXK*|Jmt%WeBwa;fByM9>m^(?>%oT$0A*>CH!kitc( ztX<;Jxr6@c0>0~uxZJ|ekg}lV-Z%S;v&G$Ak&SkDhf9c+4;`)03v?mP&Lp?|b$Vw@ Yw>?XIN2O}$KZ;Igx!t)GwwEsd2UVc%UH||9 diff --git a/pymodel/__pycache__/ViewerOptions.cpython-38.pyc b/pymodel/__pycache__/ViewerOptions.cpython-38.pyc deleted file mode 100644 index 282f47379b848c17d311a5489b06db6986837564..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2152 zcmai0TW=dh6yA&NBumn!EiJbK0~8b^uTu~b2&#&z5=e+NmEs~~gv{7y91q>ySZ42YY*whv(Vo*)Ml@Jnv8Y zvH67f_!_VI6a#sEk9-OeKMneU?@`zfMIb_IJoftyYEtVruivCC+Qx5-c22x@RDB#B z=Y>?6VLT(o^=X-ktT1e>G`l}LQX~@ggA|X1j-JDZ*Y@YT^^W^o8=?7+f#>Dvl>MY? zJ3q73ZZeET(TfJUjNM^IW~Yf@xmKr|r>vOpg2`;E9ty+qly@+kb{v*(HP0tgSw*A@ zRw(S3BUYf6F6lsYvZH~d=CLaB$ey&>75h+d&x+M^4Pn+vUrv`?4lGF{n_y-HUhyGE~V$X=&6 z=uLWyuG8DAz3fW0^9!{y#5!BY=M?W22D5c6s~j`_$QxOofgiHxd~ zJMP=kBePLblCb5utX^W@oE)K`4MS;zH^VX^NKl8#G0PN-`D&M~l-jmw2QXi|X-q_t z!?mb(;#izH2i50mp9>KiYnA@5SJ+9b{WwnfSx@uKNY^^%q0EZsgiKW59q}`nmMP21 zbcpk0D|fB)=`9EC6^L}Is z;2adX6!Qx&u2pxAQ)yeE(?n~wnKE`W8v)*3))7 zbii>72oSd5aaLWmFxIgwu*|QVBdl(m17gh%9BcK+Ah=!?#j$O;J>;$*0!4M;TDk4J zR?civ*}blzMgv>-sVVdz%|; z%=6v%z4q38YcL=+8Vvf|V5KI>YUt1o*0O`-)X%d!-@XMd%0%4up=<2F6^+Jj(AbND H?d$&n3rvp? diff --git a/pymodel/__pycache__/__init__.cpython-38.pyc b/pymodel/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 62367ec76120b119c893765e815d3b656a1dd488..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 234 zcmYk0F$w}P5Jj^#%7S;8*2WaJB7(IdhzK?@n+)z?OqL{ztT*yXwiX`1&dSls5C47s z6f?6O4oks$n&)B1`S_N96W|Yf4}#qRAn4prO&`q>l(G z0A(=RLxn&-W$heVxKOSkcmzib@{lmM&A92x)u_1lm9oJobrZ42=YDuksDlckI8`DW@<+#yhg{?lKatuTmx+=6{>fDGS*d0P*cUN3(?mBoFo{KI0IetuEC4>B z@g(O#kmQL80>tWP;<*f9UsB{6b_v755O-^lNpeP{Y2#3hEVa-14W8?4 zT=iS%UElWyT+~Q$qO{@K2qQC@!vQ%@jON9!D_8m<1ZnNE;q4*Z`g0g08(R=UyAI&k ziJZuN#l~*bi9C3AqArB8S9QpV5F_7SI7g^Mxb+SU3v0mwwy^9$hiz!Q-C?qo-ObJ({P5nn*PfWi~p5ERCLA(65YU7l16&5sm)rqUyB~z616e zL73}Vo;BYhgJl9jVAb-%Ii8KTHZZ(qXH4Dyoy8VKvQFIAsg= zZ2LEBVVz;(t><0kF04Q77w&OQ!z&wUm4n`cfH(>tZ2v)Ji7QKW0D;J=x-hHy`$TW!LlvclUA{Pcevc=I3CI{>HN#o?Bp`)vKJqk&6l8+RL0 zP-v=dqoivHghhGWT2Iq9BI22>=UF>YPuhFFeOp%7!r4}rL&SIlg8{;wL1nqL^^UPG z=`C=YuQq%gF2A~_gL}`MODxM!j{o%jVis#!&tl6ZYit_)tzDjjptlUpnM$-x>a{7v@k|dpQ<{hoTZX> zst<6xi`rF4l51pI3a;8%`tt>B(Wnx(xoPz+xScI$ey`@xa@WzoBMDdiAb^BGDuSTe z41yP>NSiX$!?WnXk8nn3tp=p9Lf)Y+N#;Gfx6yl#zF|Y|20>JWfa=wWqlMd0s@{I> M!2!Z)DxST60V98PZU6uP diff --git a/pymodel/__pycache__/observation_queue.cpython-38.pyc b/pymodel/__pycache__/observation_queue.cpython-38.pyc deleted file mode 100644 index 2d0074e1e268f066e29c062c24c47120a393d8ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 426 zcmY*WyH3L}6m`8CKk$i)9-WvKWC zrrxF?l`HuiUHkax9(z5TWsGW9?;c(w#=Z{CX~ooB(Q+3Af)z|eB9>7R4ICG-7!@Oo z-ja7#j75CU=E?Rm)0IQ}%zdL3JOvC0!J&Yv1Lx5gv=01+d$8z(Rq!jXBm%Dwm)s)D zR>6~~anLAeyaWBBpw%Lf$Z6Vvnk%Rfn$~FRkM5@RkZ~o3GcHL-0&NbKT*~gB_HU9! z_D#{)-OYT`Ps>v1x-9$iT1$z?S&B2ivB*WE9{UM*ovJteSRm!<$3wUeW~byZz|S&Y zQ+AhcrM4B?iibA8)&lcYcS|I5(@_fIlQ${{n}zB6ba{+;Nq@myN{~#`C>Z{<*Bv1UGzyb|%N6OP?(r7ABWtwAB~}6WWDBIVWvHi9O-oeb4OtUy z-#aBK(Ve>m3A7hs-O=`)ziHNkcI~!nZ-b|FfHoK-i0RAR*wl|m3F19L1GKi_#lzCt z!i@-lMD${HdZYrSK@asj0H`HT)x0jQ_W5KYpw@J@f2l|npNe*8WgjV!Qer4=_&`Eu z5%Y5{l?b_hM;?c}2xyuBA~#nr<^!Eb=&$g7p)=c>6DPW!FKK6^oQP4LxRq zwD~RT{H4U4hDhaPh0Y7jsNx^RkTN%G+EdY*0ZDRZCn-%Zes^o|;HC0E2KBQ|w`n__z<~*>8tD#~<+ktDRW}knZMR1M`9F<9yY~$7FszC1nS(mpG0-exabna|CVlo@Ey@Wggm_Q^a~NJ5TMZz_2b2>Ab!!#-{hY zHx*Lo+=|}x4x4CI_3C-GiF`XePu3W!_Hp~*F6~2Y3ndLe5m$Cnlwu4KdkMe`K~F)A z1Y^+;e2YGLOSHrd)=#Y0;1%ZhhUjN-=ifq)mbgyki47Xy_86`3(reI{P@g#~!!5e> zx!?GY(304wA&rZOK|7$yDgdSkn39XN8`v5YEyAZ}08RD}w8Lmp=h_IC>#1q|Qb+)F zAuYQWW!)0-wXki%SW(qVTgKW@&1^lh+IP7}R_D?bj{Ez3BuYaMr5aio-lbBdEo%Bu zs>^-M4(tasQrm&G#MPz##8k918RbUEyb?5+xZ@|%2T-tW5!%0V;B9XjTqx< s3AZZ%Fb&R-izJOW@EC)%q378dUD3Mw& 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 diff --git a/pymodel/__pycache__/trun.cpython-38.pyc b/pymodel/__pycache__/trun.cpython-38.pyc deleted file mode 100644 index 5825ecc423ab2b7af75ed32e97cd7b0fe90fe7a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 300 zcmYk1Jx;?w5QS&_lPrd$r=y@r!(IR(B&d);NGQz;WE}4ZIr1O7Yb4Y9B2aQ4s%=w1 zu0X{&G(2hEo7a4!SytDssx!oKVP0t;x2C9_pD@5t^#Nx5XJn5x~2nmG1AXX?+yXggKRS;TfC14MfYO57!STs1X(@dGn47Nj? zC^?lY|3iD^Uz#fi;9uafe4dnTs%1vDpP%jL?|tt*KkN57!S|Q?^T)l2kU#un`wK96 zgl3tv;*QKBqHMw&uf{x=YlqXBLEwlkY=OhVE7rtx-;rGZL*N(pH; zR?h8dVdhn(&4XqkzOTD&_6H%&Dd*=?ZL}9fVTGG&QOTJW^+d@3K+I}22YhV7aqKU~ zQ!%b9CyUBrm-l0TT*JjeR<84M%c}?c3;3!pq^IDJb0Pj0)P5r#9y~k{9;(u_n)4@< zZgPto@QMoeslw>7tIYyvrSqsk6o7Mmp~rL2fM}$f?m>sOv8=>M3p=fi)0I}j)xx8= z%gL+PgU@Jg<*6QfzPG;wlgDWGU389|`J7=YP@#&D9mo|&uGlp}?31G<+i#Zf{(gho z+n}gi@#uX*mLDHuE;h{HWJFn5;In%|>4}_|4s;wG41zXvi$=G}jkXP^x@@41n=Z;3 zmrCWEfVRBT<%pC0otRyq>$-nF89u`$cDO8SGty=x=d7)Aq z&3a>mcJn(I#ZZT6-9;q?M=r^8wkB)3qV^p}ea$)M`xRpHHd!+ltk@-82dm<!$`F zY{gdOES|8p$R)+@N-ySr5%ZPf9Sg?`O=-!twj z>ZE%pvNyc^RR28J)_sMypwuQytadp}Wc@C8ay%v)kIEpP@8NGaLo7+b{TO?(Mx;&>B;jgXk8L__|nfdj(qMZvK~Mf SrAW#i3-583Mrn}VOX Date: Thu, 8 Jul 2021 21:37:53 -0700 Subject: [PATCH 06/11] Update README.md --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 929dc27..c827c2b 100644 --- a/README.md +++ b/README.md @@ -84,3 +84,16 @@ Code and documents are copyright (C) 2009-2013, Jonathan Jacky. Revised May 2013 + + +--------------------------------------------------------------------------------- + + +Updates +======= + +Python 3.x support, and additional updates and fixes: +- @philscrace2 +- @zlorb + + From 537bcd6ab12fc2eef0a156e0fe6fdb861a8153a1 Mon Sep 17 00:00:00 2001 From: Zohar Lorberbaum Date: Sat, 10 Jul 2021 22:43:42 -0700 Subject: [PATCH 07/11] Additional Python 3.x fixes --- pymodel/pmt.py | 5 +++-- pymodel/wsgidemo.py | 7 ++++--- samples/Socket/stepper.py | 6 +++--- samples/Socket/stepper_a.py | 6 +++--- samples/Socket/stepper_d.py | 4 ++-- samples/Socket/stepper_o.py | 6 +++--- samples/WebApplication/Stepper.py | 10 +++++----- samples/WebApplication/webapp.py | 4 ++-- 8 files changed, 25 insertions(+), 23 deletions(-) diff --git a/pymodel/pmt.py b/pymodel/pmt.py index d438cc3..03d6380 100755 --- a/pymodel/pmt.py +++ b/pymodel/pmt.py @@ -6,6 +6,7 @@ import os import sys import types +import importlib import pdb # can optionally run in debugger, see main import random import signal @@ -179,7 +180,7 @@ def main(): else: mp = ProductModelProgram(options, args) - stepper = __import__(options.iut) if options.iut else None + stepper = importlib.import_module(options.iut) if options.iut else None if stepper: # recognize PEP-8 style names (all lowercase) if present if hasattr(stepper, 'testaction'): @@ -190,7 +191,7 @@ def main(): stepper.Reset = stepper.reset if options.strategy: - strategy = __import__(options.strategy) + strategy = importlib.import_module( '.'.join(('pymodel', options.strategy)) ) if hasattr(strategy, 'selectaction'): strategy.SelectAction = strategy.selectaction if hasattr(strategy, 'select_action'): diff --git a/pymodel/wsgidemo.py b/pymodel/wsgidemo.py index d6162ad..e551e20 100644 --- a/pymodel/wsgidemo.py +++ b/pymodel/wsgidemo.py @@ -20,9 +20,10 @@ """ def application(environ, start_response): - response_body = body_template % pprint.pformat(environ) + response = body_template % pprint.pformat(environ) + response_body = response.encode() status = '200 OK' - response_headers = [('Content-Type', 'text/plain'), + response_headers = [('Content-type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) - return [response_body] + return [response_body] \ No newline at end of file diff --git a/samples/Socket/stepper.py b/samples/Socket/stepper.py index 40dcfa5..cac516a 100644 --- a/samples/Socket/stepper.py +++ b/samples/Socket/stepper.py @@ -37,7 +37,7 @@ # But we can import these items in this way, they do work after reset -- why? from stepper_util import reset -import observation_queue as observation +import pymodel.observation_queue as observation observation.asynch = True # make pmt wait for asynch observable actions @@ -96,13 +96,13 @@ def call_select(timeout): [],timeout) if inputready and inbuf_size: # print 'inputready %s, inbuf_size %s' % (inputready, inbuf_size) # DEBUG - recv_msg = connection.receiver.recv(inbuf_size) + recv_msg = connection.receiver.recv(inbuf_size).decode() observation.queue.append(('recv_return', (recv_msg,))) inbuf_size = 0 if outputready and send_msg: # print 'outputready %s, send_msg %s' % (outputready, send_msg) # DEBUG - n = connection.sender.send(send_msg) + n = connection.sender.send(send_msg.encode()) observation.queue.append(('send_return', (n,))) send_msg = '' # RECALL n might be less then len(send_msg): "It is your diff --git a/samples/Socket/stepper_a.py b/samples/Socket/stepper_a.py index 8070807..e63ae13 100644 --- a/samples/Socket/stepper_a.py +++ b/samples/Socket/stepper_a.py @@ -34,7 +34,7 @@ # But we can import these items in this way, they do work after reset -- why? from stepper_util import reset -import observation_queue as observation +import pymodel.observation_queue as observation observation.asynch = True # make pmt wait for asynch observable actions @@ -52,7 +52,7 @@ def test_action(aname, args, modelResult): if aname == 'send_call': def wait_for_return(): (msg,) = args # extract msg from args tuple, like msg = args[0] - nchars = connection.sender.send(msg) + nchars = connection.sender.send(msg.encode()) observation.queue.append(('send_return', (nchars,))) event.set() # notify pmt that data has been added to queue t = threading.Thread(target=wait_for_return) @@ -62,7 +62,7 @@ def wait_for_return(): elif aname == 'recv_call': def wait_for_return(): (bufsize,) = args - data = connection.receiver.recv(bufsize) + data = connection.receiver.recv(bufsize).decode() observation.queue.append(('recv_return', (data,))) event.set() # notify pmt that data has been added to queue t = threading.Thread(target=wait_for_return) diff --git a/samples/Socket/stepper_d.py b/samples/Socket/stepper_d.py index 4269b6a..83eeffd 100644 --- a/samples/Socket/stepper_d.py +++ b/samples/Socket/stepper_d.py @@ -53,7 +53,7 @@ def test_action(aname, args, model_result): elif aname == 'send_return': (n,) = args - nchars = connection.sender.send(msg) + nchars = connection.sender.send(msg.encode()) if n != nchars: return 'send returned %s, expected %s ' % (nchars, n) @@ -62,7 +62,7 @@ def test_action(aname, args, model_result): elif aname == 'recv_return': (msg,) = args - data = connection.receiver.recv(bufsize) + data = connection.receiver.recv(bufsize).decode() if data != msg: # now msg is like old modelresult # wrapped failMessage should fit on two 80 char lines, # failMessage prefix from pmt is 20 char, fixed text here is > 32 char diff --git a/samples/Socket/stepper_o.py b/samples/Socket/stepper_o.py index acae755..0b6ae6e 100644 --- a/samples/Socket/stepper_o.py +++ b/samples/Socket/stepper_o.py @@ -32,7 +32,7 @@ # But we can import these items in this way, they do work after reset -- why? from stepper_util import reset -import observation_queue as observation +import pymodel.observation_queue as observation def test_action(aname, args, modelResult): """ @@ -46,13 +46,13 @@ def test_action(aname, args, modelResult): if aname == 'send_call': (msg,) = args # extract msg from args tuple, like msg = args[0] - n = connection.sender.send(msg) + n = connection.sender.send(msg.encode()) observation.queue.append(('send_return', (n,))) return None # pmt will check observation_queue elif aname == 'recv_call': (bufsize,) = args - msg = connection.receiver.recv(bufsize) + msg = connection.receiver.recv(bufsize).decode() observation.queue.append(('recv_return', (msg,))) return None # pmt will check observation_queue diff --git a/samples/WebApplication/Stepper.py b/samples/WebApplication/Stepper.py index 2c53cba..b4c4fc8 100644 --- a/samples/WebApplication/Stepper.py +++ b/samples/WebApplication/Stepper.py @@ -59,11 +59,11 @@ def __init__(self): # like in NModel WebApplication WebTestHelper def loginFailed(page): - return (page.find('Incorrect login') > -1) + return (page.decode().find('Incorrect login') > -1) # not in NModel WebApplication, it has no positive check for login success def loginSuccess(page): - return (page.find('DoStuff') > -1) + return (page.decode().find('DoStuff') > -1) # similar to NModel WebApplication WebTestHelper intPattern = re.compile(r'Number: (\d+)') @@ -98,7 +98,7 @@ def TestAction(aname, args, modelResult): if show_page: print(page) # POST username, password - page = session[user].opener.open(webAppUrl, postArgs).read() + page = session[user].opener.open(webAppUrl, postArgs.encode()).read() if show_page: print(page) # Check conformance, reproduce NModel WebApplication Stepper logic: @@ -119,13 +119,13 @@ def TestAction(aname, args, modelResult): elif aname == 'UpdateInt': user = users[args[0]] numArg = urllib.parse.urlencode({'num':args[1]}) - page = session[user].opener.open("%s?%s" % (webAppUrl,numArg)).read() + page = session[user].opener.open("%s?%s" % (webAppUrl,numArg.encode())).read().decode() if show_page: print(page) elif aname == 'ReadInt': user = users[args[0]] - page = session[user].opener.open(webAppUrl).read() + page = session[user].opener.open(webAppUrl).read().decode() if show_page: print(page) numInPage = intContents(page) diff --git a/samples/WebApplication/webapp.py b/samples/WebApplication/webapp.py index 1b05683..e07c0cf 100644 --- a/samples/WebApplication/webapp.py +++ b/samples/WebApplication/webapp.py @@ -52,7 +52,7 @@ def application(environ, start_response): wd = environ['wsgi.input'] method = environ['REQUEST_METHOD'] length = int(environ['CONTENT_LENGTH']) - request_body = wd.read(length) + request_body = wd.read(length).decode() vars = urllib.parse.parse_qs(request_body) user = vars['username'][0] # vars[x] are lists, get first item passwd = vars['password'][0] @@ -110,7 +110,7 @@ def application(environ, start_response): response_headers += [('Content-Type', 'text/html'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) - return [response_body] + return [response_body.encode()] environ_template = """environ is From 7c949757dfd517e8ca0d82d2182d1314b6c6c864 Mon Sep 17 00:00:00 2001 From: Zohar Lorberbaum Date: Sat, 10 Jul 2021 23:30:04 -0700 Subject: [PATCH 08/11] trun input validation --- pymodel/trun.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pymodel/trun.py b/pymodel/trun.py index 5bba96d..91b0494 100755 --- a/pymodel/trun.py +++ b/pymodel/trun.py @@ -6,7 +6,15 @@ # argv[1] is name of module containing test cases # dir containing this module must be on PYTHONPATH -test = __import__(sys.argv[1]) +if len(sys.argv) != 2: + print('\nUsage: trun \n\n') + sys.exit() + +try: + test = __import__(sys.argv[1]) +except ModuleNotFoundError: + print('\nCould not find tests file "{}".\n\n'.format(sys.argv[1])) + sys.exit() # Test cases are in 'cases', a list of pairs of strings, descrip. and commmand: # cases = [ @@ -14,7 +22,11 @@ # 'pct.py -n 10 PowerSwitch'), # ... ] -for (description, cmd) in test.cases: - print(description) - os.system(cmd) - print() +try: + for (description, cmd) in test.cases: + print(description) + os.system(cmd) + print() +except AttributeError: + print('\nCould not find test cases in "{}".\n\n'.format(sys.argv[1])) + sys.exit() From 2711f96ecb82598fb62cf0717e039ab0fe86d64b Mon Sep 17 00:00:00 2001 From: John Proudlove Date: Tue, 22 Feb 2022 13:23:50 +0000 Subject: [PATCH 09/11] The set type is no longer ordered in Python 3.7+, meaning that pmt actions are no longer reproducible with non-zero seeds. Use fromkeys and list as a workaround (another solution would be ordered-set from PyPi). Note there are multiple other occurrences of sets remaining, which may cause ordering issues elsewhere - the same workaround could be applied to them if necessary. --- pymodel/ModelProgram.py | 8 ++++++ pymodel/ProductModelProgram.py | 50 ++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/pymodel/ModelProgram.py b/pymodel/ModelProgram.py index 706d18e..e5995ed 100644 --- a/pymodel/ModelProgram.py +++ b/pymodel/ModelProgram.py @@ -11,10 +11,15 @@ import itertools from pymodel.model import Model import collections +import pprint + +DEBUG = False class ModelProgram(Model): def __init__(self, module, exclude, include): + if DEBUG: + self.pp = pprint.PrettyPrinter(width=120) Model.__init__(self, module, exclude, include) def post_init(self): @@ -80,6 +85,9 @@ def make_argslist(self, a): argslists = list(zip(*domains)) elif combination == 'all': # Cartesian product argslists = itertools.product(*domains) + if DEBUG: + print('list(itertools.product(*domains)):') + self.pp.pprint(list(itertools.product(*domains))) # might be nice to support 'pairwise' also # return tuple not list, hashable so it can be key in dictionary # also handle special case (None,) indicates empty argslist in domains diff --git a/pymodel/ProductModelProgram.py b/pymodel/ProductModelProgram.py index d7fdb97..d4a6e45 100644 --- a/pymodel/ProductModelProgram.py +++ b/pymodel/ProductModelProgram.py @@ -18,6 +18,7 @@ and translates aname string to action function a: a = getattr(module, aname) """ +import pprint from operator import concat from collections import defaultdict @@ -25,6 +26,8 @@ from pymodel.TestSuite import TestSuite from pymodel.ModelProgram import ModelProgram from functools import reduce + +DEBUG = False class ProductModelProgram(object): @@ -33,6 +36,9 @@ def __init__(self, options, args): 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 @@ -124,7 +130,9 @@ def EnabledTransitions(self, cleanup): enabledScenarioActions = \ dict([(m, self.mp[m].EnabledTransitions(cleanup)) for m in self.mp if (not isinstance(self.mp[m], ModelProgram))]) - # print 'enabledScenarioActions %s' % enabledScenarioActions # DEBUG + + if DEBUG: + print('enabledScenarioActions %s' % enabledScenarioActions) # dict from action to sequence of argslists argslists = defaultdict(list) @@ -135,26 +143,37 @@ def EnabledTransitions(self, cleanup): # 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())]) - # print 'scenarioArgslists %s' % scenarioArgslists + 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)]) - # print 'enabledModelActions %s' % enabledModelActions # DEBUG + 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 - # print 'enabledActions %s' % enabledActions # DEBUG + if DEBUG: + print('enabledActions:') + self.pp.pprint(enabledActions) - # set (with no duplicates) of all (aname, args, result) in enabledActions - transitions = set([(a.__name__, args, result) - for (a,args,result,next,properties) in - reduce(concat,list(enabledActions.values()))]) - # print 'transitions %s' % transitions + # 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 @@ -166,7 +185,10 @@ def EnabledTransitions(self, cleanup): [(a.__name__, argsx, resultx) # argsx,resultx is inner loop for (a,argsx,resultx,next,properties) in enabledActions[m]]])) for (aname, args, result) in transitions ]) - # print 'invocations %s' % invocations # DEBUG + + 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 @@ -180,6 +202,10 @@ def EnabledTransitions(self, cleanup): 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 @@ -200,7 +226,9 @@ def EnabledTransitions(self, cleanup): for m in self.mp ])) for (aname, args, result) in enabledAnameArgs ] - # print 'enabledTs %s' % enabledTs # DEBUG + 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),...] From 2350e790a2d7e690ba98fd12ab42021724d011b2 Mon Sep 17 00:00:00 2001 From: Zohar Lorberbaum Date: Tue, 22 Feb 2022 16:12:40 -0800 Subject: [PATCH 10/11] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c76c011..1b6dc44 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Updates ======= Python 3.x support, and additional updates and fixes: +- @jproudlo - @philscrace2 - @zlorb From 502aa0a3708f549ecd803008ab6a2d63a59a2cd3 Mon Sep 17 00:00:00 2001 From: Zohar Lorberbaum Date: Tue, 22 Feb 2022 16:13:02 -0800 Subject: [PATCH 11/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b6dc44..68c95ef 100644 --- a/README.md +++ b/README.md @@ -92,5 +92,5 @@ Python 3.x support, and additional updates and fixes: - @zlorb -Revised Jul 2021 +Revised Feb 2022