0x1949 Team - FAZEMRX - MANAGER
Edit File: test_visualize.py
from __future__ import print_function import functools import os import subprocess from unittest import TestCase, skipIf import attr from .._methodical import MethodicalMachine from .test_discover import isTwistedInstalled def isGraphvizModuleInstalled(): """ Is the graphviz Python module installed? """ try: __import__("graphviz") except ImportError: return False else: return True def isGraphvizInstalled(): """ Are the graphviz tools installed? """ r, w = os.pipe() os.close(w) try: return not subprocess.call("dot", stdin=r, shell=True) finally: os.close(r) def sampleMachine(): """ Create a sample L{MethodicalMachine} with some sample states. """ mm = MethodicalMachine() class SampleObject(object): @mm.state(initial=True) def begin(self): "initial state" @mm.state() def end(self): "end state" @mm.input() def go(self): "sample input" @mm.output() def out(self): "sample output" begin.upon(go, end, [out]) so = SampleObject() so.go() return mm @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.") class ElementMakerTests(TestCase): """ L{elementMaker} generates HTML representing the specified element. """ def setUp(self): from .._visualize import elementMaker self.elementMaker = elementMaker def test_sortsAttrs(self): """ L{elementMaker} orders HTML attributes lexicographically. """ expected = r'<div a="1" b="2" c="3"></div>' self.assertEqual(expected, self.elementMaker("div", b='2', a='1', c='3')) def test_quotesAttrs(self): """ L{elementMaker} quotes HTML attributes according to DOT's quoting rule. See U{http://www.graphviz.org/doc/info/lang.html}, footnote 1. """ expected = r'<div a="1" b="a \" quote" c="a string"></div>' self.assertEqual(expected, self.elementMaker("div", b='a " quote', a=1, c="a string")) def test_noAttrs(self): """ L{elementMaker} should render an element with no attributes. """ expected = r'<div ></div>' self.assertEqual(expected, self.elementMaker("div")) @attr.s class HTMLElement(object): """Holds an HTML element, as created by elementMaker.""" name = attr.ib() children = attr.ib() attributes = attr.ib() def findElements(element, predicate): """ Recursively collect all elements in an L{HTMLElement} tree that match the optional predicate. """ if predicate(element): return [element] elif isLeaf(element): return [] return [result for child in element.children for result in findElements(child, predicate)] def isLeaf(element): """ This HTML element is actually leaf node. """ return not isinstance(element, HTMLElement) @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.") class TableMakerTests(TestCase): """ Tests that ensure L{tableMaker} generates HTML tables usable as labels in DOT graphs. For more information, read the "HTML-Like Labels" section of U{http://www.graphviz.org/doc/info/shapes.html}. """ def fakeElementMaker(self, name, *children, **attributes): return HTMLElement(name=name, children=children, attributes=attributes) def setUp(self): from .._visualize import tableMaker self.inputLabel = "input label" self.port = "the port" self.tableMaker = functools.partial(tableMaker, _E=self.fakeElementMaker) def test_inputLabelRow(self): """ The table returned by L{tableMaker} always contains the input symbol label in its first row, and that row contains one cell with a port attribute set to the provided port. """ def hasPort(element): return (not isLeaf(element) and element.attributes.get("port") == self.port) for outputLabels in ([], ["an output label"]): table = self.tableMaker(self.inputLabel, outputLabels, port=self.port) self.assertGreater(len(table.children), 0) inputLabelRow = table.children[0] portCandidates = findElements(table, hasPort) self.assertEqual(len(portCandidates), 1) self.assertEqual(portCandidates[0].name, "td") self.assertEqual(findElements(inputLabelRow, isLeaf), [self.inputLabel]) def test_noOutputLabels(self): """ L{tableMaker} does not add a colspan attribute to the input label's cell or a second row if there no output labels. """ table = self.tableMaker("input label", (), port=self.port) self.assertEqual(len(table.children), 1) (inputLabelRow,) = table.children self.assertNotIn("colspan", inputLabelRow.attributes) def test_withOutputLabels(self): """ L{tableMaker} adds a colspan attribute to the input label's cell equal to the number of output labels and a second row that contains the output labels. """ table = self.tableMaker(self.inputLabel, ("output label 1", "output label 2"), port=self.port) self.assertEqual(len(table.children), 2) inputRow, outputRow = table.children def hasCorrectColspan(element): return (not isLeaf(element) and element.name == "td" and element.attributes.get('colspan') == "2") self.assertEqual(len(findElements(inputRow, hasCorrectColspan)), 1) self.assertEqual(findElements(outputRow, isLeaf), ["output label 1", "output label 2"]) @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.") @skipIf(not isGraphvizInstalled(), "Graphviz tools are not installed.") class IntegrationTests(TestCase): """ Tests which make sure Graphviz can understand the output produced by Automat. """ def test_validGraphviz(self): """ L{graphviz} emits valid graphviz data. """ p = subprocess.Popen("dot", stdin=subprocess.PIPE, stdout=subprocess.PIPE) out, err = p.communicate("".join(sampleMachine().asDigraph()) .encode("utf-8")) self.assertEqual(p.returncode, 0) @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.") class SpotChecks(TestCase): """ Tests to make sure that the output contains salient features of the machine being generated. """ def test_containsMachineFeatures(self): """ The output of L{graphviz} should contain the names of the states, inputs, outputs in the state machine. """ gvout = "".join(sampleMachine().asDigraph()) self.assertIn("begin", gvout) self.assertIn("end", gvout) self.assertIn("go", gvout) self.assertIn("out", gvout) class RecordsDigraphActions(object): """ Records calls made to L{FakeDigraph}. """ def __init__(self): self.reset() def reset(self): self.renderCalls = [] self.saveCalls = [] class FakeDigraph(object): """ A fake L{graphviz.Digraph}. Instantiate it with a L{RecordsDigraphActions}. """ def __init__(self, recorder): self._recorder = recorder def render(self, **kwargs): self._recorder.renderCalls.append(kwargs) def save(self, **kwargs): self._recorder.saveCalls.append(kwargs) class FakeMethodicalMachine(object): """ A fake L{MethodicalMachine}. Instantiate it with a L{FakeDigraph} """ def __init__(self, digraph): self._digraph = digraph def asDigraph(self): return self._digraph @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.") @skipIf(not isGraphvizInstalled(), "Graphviz tools are not installed.") @skipIf(not isTwistedInstalled(), "Twisted is not installed.") class VisualizeToolTests(TestCase): def setUp(self): self.digraphRecorder = RecordsDigraphActions() self.fakeDigraph = FakeDigraph(self.digraphRecorder) self.fakeProgname = 'tool-test' self.fakeSysPath = ['ignored'] self.collectedOutput = [] self.fakeFQPN = 'fake.fqpn' def collectPrints(self, *args): self.collectedOutput.append(' '.join(args)) def fakeFindMachines(self, fqpn): yield fqpn, FakeMethodicalMachine(self.fakeDigraph) def tool(self, progname=None, argv=None, syspath=None, findMachines=None, print=None): from .._visualize import tool return tool( _progname=progname or self.fakeProgname, _argv=argv or [self.fakeFQPN], _syspath=syspath or self.fakeSysPath, _findMachines=findMachines or self.fakeFindMachines, _print=print or self.collectPrints) def test_checksCurrentDirectory(self): """ L{tool} adds '' to sys.path to ensure L{automat._discover.findMachines} searches the current directory. """ self.tool(argv=[self.fakeFQPN]) self.assertEqual(self.fakeSysPath[0], '') def test_quietHidesOutput(self): """ Passing -q/--quiet hides all output. """ self.tool(argv=[self.fakeFQPN, '--quiet']) self.assertFalse(self.collectedOutput) self.tool(argv=[self.fakeFQPN, '-q']) self.assertFalse(self.collectedOutput) def test_onlySaveDot(self): """ Passing an empty string for --image-directory/-i disables rendering images. """ for arg in ('--image-directory', '-i'): self.digraphRecorder.reset() self.collectedOutput = [] self.tool(argv=[self.fakeFQPN, arg, '']) self.assertFalse(any("image" in line for line in self.collectedOutput)) self.assertEqual(len(self.digraphRecorder.saveCalls), 1) (call,) = self.digraphRecorder.saveCalls self.assertEqual("{}.dot".format(self.fakeFQPN), call['filename']) self.assertFalse(self.digraphRecorder.renderCalls) def test_saveOnlyImage(self): """ Passing an empty string for --dot-directory/-d disables saving dot files. """ for arg in ('--dot-directory', '-d'): self.digraphRecorder.reset() self.collectedOutput = [] self.tool(argv=[self.fakeFQPN, arg, '']) self.assertFalse(any("dot" in line for line in self.collectedOutput)) self.assertEqual(len(self.digraphRecorder.renderCalls), 1) (call,) = self.digraphRecorder.renderCalls self.assertEqual("{}.dot".format(self.fakeFQPN), call['filename']) self.assertTrue(call['cleanup']) self.assertFalse(self.digraphRecorder.saveCalls) def test_saveDotAndImagesInDifferentDirectories(self): """ Passing different directories to --image-directory and --dot-directory writes images and dot files to those directories. """ imageDirectory = 'image' dotDirectory = 'dot' self.tool(argv=[self.fakeFQPN, '--image-directory', imageDirectory, '--dot-directory', dotDirectory]) self.assertTrue(any("image" in line for line in self.collectedOutput)) self.assertTrue(any("dot" in line for line in self.collectedOutput)) self.assertEqual(len(self.digraphRecorder.renderCalls), 1) (renderCall,) = self.digraphRecorder.renderCalls self.assertEqual(renderCall["directory"], imageDirectory) self.assertTrue(renderCall['cleanup']) self.assertEqual(len(self.digraphRecorder.saveCalls), 1) (saveCall,) = self.digraphRecorder.saveCalls self.assertEqual(saveCall["directory"], dotDirectory) def test_saveDotAndImagesInSameDirectory(self): """ Passing the same directory to --image-directory and --dot-directory writes images and dot files to that one directory. """ directory = 'imagesAndDot' self.tool(argv=[self.fakeFQPN, '--image-directory', directory, '--dot-directory', directory]) self.assertTrue(any("image and dot" in line for line in self.collectedOutput)) self.assertEqual(len(self.digraphRecorder.renderCalls), 1) (renderCall,) = self.digraphRecorder.renderCalls self.assertEqual(renderCall["directory"], directory) self.assertFalse(renderCall['cleanup']) self.assertFalse(len(self.digraphRecorder.saveCalls))