Package: graphwalker
IntroPython-Graphwalker is a tool for testing based on finite state machine graphs.
Graphwalker reads FSMs specified by graphs, plans paths, calls model methods
by name from graph labels and reports progress and results.While conceptually derived from the Graphwalker project, (implemented in java)
this is a complete reimplementation from that initial concept.Notably, there are a few differences:In the original, nodes are considered states to be verified and edges
actions to be taken, but this version has no ambition to enforce this
convention in any way, even though it is quite useful.Python Graphwalker does not understand extended FSM labels. It should ignore
them, but proceed at your own risk until this is definitively dealt with one
way or the other.Python Graphwalker is quite promiscuous about letting you load and combine
code to implement the different components of the design. Some combinations
don’t make sense.The graph for the self-test of the Interactive planner.Overall designThe idea that has driven the design is that the graph-problems are quite
orthogonal to the testing actions and that the problem of reporting the
results are orthogonal to both. The graph-problems are further decomposable
into path planning, stop conditions and of course loading graph files.The added feature request to be able save and replay the path of a run
dissolve into the path-recorder reporting class and the plain text graph
loader.The design is separated into these parts:Model, (normally) supplied by the user as a graph file.Stop condition, which bool-converts to true if its conditions are met.Planner, which uses the model and stop condition to provide an iterable of
plan steps as (id, name, …) tuples.Reporter, which is called on execution events.Taps, installed by the reporter system to capture side-effects. (currently
stdout/stderr and logging)Actor supplied by the user as an object with function attributes, normally
an object instance.Executor that, for each step in the plan, calls the reporter and looks up
and calls the named method on the actor. In addition to the step methods, it
also calls a few other methods, if present on the actor.Code loaderThere is a common code-loader interface, so it’s easy to load custom code and
supply arguments (if any, if callable) from the command line:–foo=module.module–foo=module.module.function–foo=module.module.class:argument,…,keyword=value,…If the object found is callable, it will be called, with any arguments
supplied, and the result used.FormatsCurrently, Python Graphwalker understands a few simple file formats:graphmlGraphs for the original Graphwalker are typically drawn usingyEd, which
normally produces graphml files, so support for these have been a priority.dot/graphvizPlain graphviz files can also be written, which turns out to be useful: The
Cartographer reporter uses dot to generate highlighted maps as it goes.plain textPlain text word lists are interpreted as a linear list of nodes to visit.
Comments of the familiar “/* … */” form are respected, as are line
comments of both the “#” and “//” varieties. If the first node isn’t labeled
“Start”, such a node is added.other formatsOther formats are easy enough to add. All that you need to supply for a reader
is an iterable of vertex (id, label) pairs and an iterable of (id, label,
from-id, to-id) quadruples. Graphwalker will convert these to its internal
formats. For write-support, you need to take a similar pair of sequences, but
with the difference that for the vertex and edge tuples might be longer.PlannersThe steps to be executed by the executor are determined by one or more
planners. Normally, planners are expected to examine the supplied graph and
plan a traversal of it, but the lack of enforcement creates a few special
opportunities.Planners are instantiated through the common code-loader interface, so it’s
easy to plug in your own planner. They’re called with a graph and a StopCond
instance to supply an iterable containing tuples of at least two elements, as
the executor expects id and label.To generate repeatable plans, use the seed keyword argument as planners keep
their own random number generators.RandomThe simplest planner, Random, traverses the graph by randomly choosing an edge
and visiting that edge and the target vertex until the StopCond is satisfied.
It does not check the StopCond between edge and vertex.Examplegraphwalker--stopcond=Coverage--planner=Random:seed=1337model.dotGotoTo visit specific vertices, name them as arguments to the Goto planner. In
addition to names and ids, ‘random’ will pick a vertex at random. If there is
more than one candidate, the one closest to the current vertex will be chosen.
(So this does not, currently, minimize the total path.)An integer for the keyword argument ‘repeat’ will repeat the name list. (but
not, nota bene, the specific vertices.) A repeat of zero will be taken to mean
infinity.Examplegraphwalker--planner=Goto:happy,random,sad,repeat=10model.dotEulerTo visit all edges in the graph most efficiently, we’d like to generate anEulerian trail. Since the graph is not necessarily even (semi-)Eulerian,
the Euler planner copies the graph and modifies it. First, by cutting out the
forced steps from the Start vertex source subgraph. The graph is then
‘eulerized’ by adding edges to make it Eulerian. (in-degree equal to
out-degree for all vertices) After the plan is created it run through the
StopCond, to get rid of extraneous steps at the end.Examplegraphwalker--planner=Eulermodel.dotInteractiveThere’s often a wish to choose paths as the test is running when developing or
debugging models. When run, Interactive lists the edges of the current vertex
and prompts for input. You can choose a listed edge by entering it’s number,
or you can use one of the special commands:nameg, go toGoes to the specified vertex[1]f, forceSend some arbitrary name(s) as plan stepsj, jumpSet some new vertex[1]as the current oned, debugEnter the pdb debuggerq, quitEnd the plan[1](1,2)asks if there’s more than oneNotes about entering the debuggerIf you quit from the debugger, you quit from the whole program. Catching
BdbQuit exceptions doesn’t seem to work, instead, use c/continueYou can set breakpoints in, for instance, other planners, that will
drop you back into the debugger after you’ve left it.StopConds - When to stopSome planners have inherent stopping conditions, others don’t, so there are
independent conditions that can be applied to the plans. It’s up to the
planner to consult them, to they don’t always cut the test off optimally, or
at all.CoverageThe default stop condition is coverage of 100% of edges, which means that it
will signal completion when it’s seen all the edges in the graph. It can also
require some percentage of vertices, or some percentage of each. The
percentages are given as keywords arguments named ‘edges’ and ‘verts’ or
‘vertices’.Examplesgraphwalker--stopcond=Coverage:edges=100,verts=50model.dotgraphwalker--stopcond=Coverage:vertices=25model.dotSeenStepsIgnoring the difference between edges and vertices, SeenSteps will simply be
done when it has seen all the steps it’s looking for. The steps are given as
an argument list.Examplesgraphwalker--stopcond=SeenSteps:a,e_once,bmodel.dotCountStepsAgain ignoring the difference between edges and vertices, simply counts the
test steps and signals when some number of steps have been taken. The
number of steps is the first argument, or the keyword argument ‘steps’,
defaulting to 100.Examplesgraphwalker--stopcond=CountSteps:52model.dotgraphwalker--stopcond=CountSteps:steps=52model.dotActorThe test executor simply uses getattr to look up callables by the names
supplied by the planner, so you can implement the test code as a module, a
class, or, using the programmatic interface, basically any object you like.The callables on the test object are called without arguments for now.In addition to the labels in the graph, a few administrative methods are
also called, if present:setupis called at the start of the test session with a dictionary
containing the other instances involved in the test: the reporter, the
model, and so on. Notably, if you want to save attachments from the test
methods, you should use the reporter instance here.step_beginis called before each step with the step definition. The stepdefinition is an iterable where the first is the id and the second the name
of the step.step_endis called before after each step like step_begin, but with theaddition of a failure, usually None. If the test failed, or there was some
other exception, step_end is called with that exception, typically an
AssertionError. The step_end method can permit the testing to continue by
returning the exact string “RECOVER”.teardownis called the same way as setup, at the end.ReportersTo report the results of the tests, the reporters are all called for each
event, notably step_begin and step_end.PrintSimply print to stdout (default) or stderr, controlled with the keyword
argument output. If you are using the programming interface, you can send any
file-like, writable object. Note that combinations of Log and Print quickly
get really confusing.Examplesgraphwalker--reporter=Print:output=stderrmodel.dotLogEmits to the standard python logger. The name of the logger defaults to the
name of the reporting module, but can be set via the keyword argument
‘logger’. The level can also be set with the keyword argument ‘level’. Note
that combinations of Log and Print quickly get really confusing.Examplesgraphwalker--reporter=Log:logger=moo,level=WARNmodel.dotPathRecorderThe PathRecorder simply saves the plan step names to a text file, so that the
run can be replicated by feeding recording to the plain-text graph reader. The
directory where the file is saved defaults to ‘.’ but can be given as the
keyword argument ‘path’. Likewise name defaults to the test name but can be
set with the keyword argument ‘name’. The ‘attach’ keyword argument, if set
(at all) makes it try to attach it.Examplesgraphwalker--reporter=PathRecorder:path=/tmp,name=stepsmodel.dotgraphwalker--reporter=PathRecorder:attach=true,name=stepsmodel.dotCartographerTo map the progress of the test graphically, the Cartographer reporter emits
graphviz files with the current step highlighted. The keyword arguments
‘dotpath’ and ‘imgpath’ control where the graphviz input and output files go,
respectively, bot defaulting to ‘.’. The image type defaults to PNG but can be
set using the keyword argument ‘imgtype’. The ‘attach’ keyword argument, if
set (at all) makes it try to attach it.Examplesgraphwalker--reporter=Cartographermodel.dotgraphwalker--reporter=Cartographer:imgtype=jpg,attach=1model.dotgraphwalker--reporter=Cartographer:dotpath=/tmp,imgpath=./wwwmodel.dotTapsCurrently, the there are only taps for streams and the logging system. Both
the logging tap and taps of standard out & error are included by default.FutureGraphwalker itself needs a lot more, and a lot more devious tests.AuthorsThe first iteration of the Python port of Graphwalker was written by Viktor
Holmberg, Harald Hartwig and Chongyang Sun under the direction of Nils
Österling (tester) and Anders Eurenius (developer).This iteration was rewritten from scratch by Anders Eurenius to incorporate
everything we learned from the first.LicenseThe license we have chosen is the Apache License, version 2.0. You should find
the full text in the file named “LICENSE.txt”.