diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..96fd5c0 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,9 @@ +[run] +branch = True +source = algorithm_visualizer + +[report] +exclude_lines = + pragma: no cover + if TYPE_CHECKING: + @atexit diff --git a/.gitignore b/.gitignore index 4398feb..3d2f3ea 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,9 @@ ENV/ env.bak/ venv.bak/ +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +visualization.json diff --git a/algorithm_visualizer/__init__.py b/algorithm_visualizer/__init__.py index cbe3aa7..d18ca23 100644 --- a/algorithm_visualizer/__init__.py +++ b/algorithm_visualizer/__init__.py @@ -1,11 +1,15 @@ import atexit import json import os +import webbrowser +from pathlib import Path +from urllib.request import HTTPError, Request, urlopen from . import randomize as Randomize from .commander import Commander from .layouts import * from .tracers import * +from .types import PathLike __all__ = ( "Randomize", "Commander", @@ -14,23 +18,43 @@ ) +def create_json_file(path: PathLike = "./visualization.json"): + commands = json.dumps(Commander.commands, separators=(",", ":")) + with Path(path).open("w", encoding="UTF-8") as file: + file.write(commands) + + +def get_url() -> str: + url = "https://algorithm-visualizer.org/api/visualizations" + commands = json.dumps(Commander.commands, separators=(",", ":")).encode('utf-8') + request = Request( + url, + method="POST", + data=commands, + headers={ + "Content-type": "application/json; charset=utf-8", + "Content-Length": len(commands) + } + ) + response = urlopen(request) + + if response.status == 200: + return response.read().decode('utf-8') + else: + raise HTTPError( + url=url, + code=response.status, + msg="Failed to retrieve the scratch URL: non-200 response", + hdrs=dict(response.info()), + fp=None + ) + + @atexit.register def execute(): - commands = json.dumps(Commander.commands, separators=(",", ":")) if os.getenv("ALGORITHM_VISUALIZER"): - with open("visualization.json", "w", encoding="UTF-8") as file: - file.write(commands) + create_json_file() else: - import requests - import webbrowser - - response = requests.post( - "https://algorithm-visualizer.org/api/visualizations", - headers={"Content-type": "application/json"}, - data=commands - ) + url = get_url() + webbrowser.open(url) - if response.status_code == 200: - webbrowser.open(response.text) - else: - raise requests.HTTPError(response=response) diff --git a/algorithm_visualizer/commander.py b/algorithm_visualizer/commander.py index c1e47ce..59c756a 100644 --- a/algorithm_visualizer/commander.py +++ b/algorithm_visualizer/commander.py @@ -15,7 +15,7 @@ class Commander: commands: List[Dict[str, Serializable]] = [] def __init__(self, *args: Serializable): - self._objectCount += 1 + Commander._objectCount += 1 self.key = self._keyRandomizer.create() self.command(self.__class__.__name__, *args) @@ -38,5 +38,5 @@ def command(self, method: str, *args): self._command(self.key, method, *args) def destroy(self): - self._objectCount -= 1 + Commander._objectCount -= 1 self.command("destroy") diff --git a/algorithm_visualizer/randomize.py b/algorithm_visualizer/randomize.py index b56ca08..cb51511 100644 --- a/algorithm_visualizer/randomize.py +++ b/algorithm_visualizer/randomize.py @@ -9,7 +9,7 @@ class _Randomizer(metaclass=abc.ABCMeta): @abc.abstractmethod def create(self) -> NoReturn: - raise NotImplementedError + raise NotImplementedError # pragma: no cover class Integer(_Randomizer): @@ -60,15 +60,16 @@ def create(self) -> List: class Array2D(Array1D): def __init__(self, N: int = 10, M: int = 10, randomizer: _Randomizer = Integer()): - super().__init__(N, randomizer) - self._M = M + super().__init__(M, randomizer) + self._M = N def sorted(self, sorted: bool = True) -> "Array2D": self._sorted = sorted return self def create(self) -> List[List]: - return [super().create() for _ in range(self._N)] + # Explicitly pass args to super() to avoid a TypeError (BPO 26495). + return [super(Array2D, self).create() for _ in range(self._M)] class Graph(_Randomizer): @@ -92,15 +93,19 @@ def create(self) -> List[List]: for i in range(self._N): for j in range(self._N): if i == j: + # Vertex can't have an edge to itself (no loops) graph[i][j] = 0 elif self._directed or i < j: if random.random() >= self._ratio: + # Don't create an edge if the ratio is exceeded graph[i][j] = 0 elif self._weighted: graph[i][j] = self._randomizer.create() else: graph[i][j] = 1 else: + # Edge is the same for both its vertices if it is not directed + # In such case the initial weight for the edge is set above when i < j graph[i][j] = graph[j][i] return graph diff --git a/algorithm_visualizer/tracers/array1d.py b/algorithm_visualizer/tracers/array1d.py index b0c1cee..4b9c14c 100644 --- a/algorithm_visualizer/tracers/array1d.py +++ b/algorithm_visualizer/tracers/array1d.py @@ -1,7 +1,11 @@ -from . import chart +from typing import TYPE_CHECKING + from .array2d import Array2DTracer from algorithm_visualizer.types import Serializable, SerializableSequence, UNDEFINED +if TYPE_CHECKING: + from .chart import ChartTracer + class Array1DTracer(Array2DTracer): def set(self, array1d: SerializableSequence[Serializable] = UNDEFINED): @@ -19,5 +23,5 @@ def select(self, sx: int, ex: int = UNDEFINED): def deselect(self, sx: int, ex: int = UNDEFINED): self.command("deselect", sx, ex) - def chart(self, chartTracer: "chart.ChartTracer"): + def chart(self, chartTracer: "ChartTracer"): self.command("chart", chartTracer.key) diff --git a/algorithm_visualizer/tracers/graph.py b/algorithm_visualizer/tracers/graph.py index 522f002..318498e 100644 --- a/algorithm_visualizer/tracers/graph.py +++ b/algorithm_visualizer/tracers/graph.py @@ -8,6 +8,7 @@ def set(self, array2d: SerializableSequence[SerializableSequence[Serializable]] def directed(self, isDirected: bool = UNDEFINED): self.command("directed", isDirected) + return self def weighted(self, isWeighted: bool = UNDEFINED) -> "GraphTracer": self.command("weighted", isWeighted) diff --git a/algorithm_visualizer/types.py b/algorithm_visualizer/types.py index 0308b6c..88a8a8d 100644 --- a/algorithm_visualizer/types.py +++ b/algorithm_visualizer/types.py @@ -1,10 +1,19 @@ -from typing import Union +from pathlib import PurePath +from typing import Any, Dict, List, Tuple, TypeVar, Union -# Types which are serializable by the default JSONEncoder -Serializable = Union[dict, list, tuple, str, int, float, bool, None] -SerializableSequence = Union[list, tuple] + +PathLike = Union[str, bytes, PurePath] Number = Union[int, float] +# Types which are serializable by the default JSONEncoder +# Recursive types aren't supported yet. See https://github.com/python/mypy/issues/731 +_Keys = Union[str, int, float, bool, None] +_Collections = Union[Dict[_Keys, Any], List[Any], Tuple[Any]] +Serializable = Union[_Collections, _Keys] + +_T = TypeVar("_T", _Collections, _Keys) +SerializableSequence = Union[List[_T], Tuple[_T]] + class Undefined: pass diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..719e477 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +pip >= 19.1.1 + +# Packaging and Publishing +# Minimum versions for Markdown support +setuptools >= 38.6.0 +twine >= 1.12.0 # For twine check +wheel >= 0.31.0 + +coverage ~= 4.5 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..086071f --- /dev/null +++ b/setup.py @@ -0,0 +1,50 @@ +import setuptools +from pathlib import Path + +readme_path = Path(__file__).with_name("README.md") +with open(readme_path, encoding="utf-8") as f: + long_description = f.read() + +setuptools.setup( + name="algorithm-visualizer", + version="0.1.0", + license="MIT", + + author="Example Author", + author_email="author@example.com", + + description="A visualization library for Python.", + long_description=long_description, + long_description_content_type="text/markdown", + + keywords="algorithm data-structure visualization animation", + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Environment :: Web Environment", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent" + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7" + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Python :: Implementation :: Stackless", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering :: Visualization" + ], + + url="https://algorithm-visualizer.org", + project_urls={ + "Documentation": "https://github.com/algorithm-visualizer/algorithm-visualizer/wiki", + "Issue Tracker": "https://github.com/algorithm-visualizer/tracers.py/issues", + "Source": "https://github.com/algorithm-visualizer/tracers.py" + }, + + packages=setuptools.find_packages(), + python_requires=">=3.5" +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..80c0069 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,22 @@ +import unittest +from typing import Optional, Union + +from algorithm_visualizer import Commander +from algorithm_visualizer.types import Serializable, Undefined + + +class CommanderTestCase(unittest.TestCase): + def assertCommandEqual( + self, + method: str, + *args: Union[Serializable, Undefined], + key: Optional[str] = None + ): + cmd = Commander.commands[-1] + expected_cmd = { + "key": key, + "method": method, + "args": list(args), + } + + self.assertEqual(expected_cmd, cmd) diff --git a/tests/test_commander.py b/tests/test_commander.py new file mode 100644 index 0000000..5eeaca4 --- /dev/null +++ b/tests/test_commander.py @@ -0,0 +1,50 @@ +from algorithm_visualizer import commander +from algorithm_visualizer import Commander +from algorithm_visualizer.types import UNDEFINED + +from tests import CommanderTestCase + + +class CommanderTests(CommanderTestCase): + def setUp(self): + self.commander = Commander() + + def test_commander_create(self): + old_count = Commander._objectCount + args = [1, 2, 3] + cmder = Commander(*args) + + self.assertEqual(old_count + 1, Commander._objectCount) + self.assertCommandEqual("Commander", *args, key=cmder.key) + + def test_commander_max_commands(self): + old_cmds = Commander.commands + Commander.commands = [None for _ in range(commander._MAX_COMMANDS)] + + with self.assertRaisesRegex(RuntimeError, "Too Many Commands"): + self.commander.command("foo") + + Commander.commands = old_cmds + + def test_commander_max_objects(self): + old_count = Commander._objectCount + Commander._objectCount = 200 + + with self.assertRaisesRegex(RuntimeError, "Too Many Objects"): + Commander() + + Commander._objectCount = old_count + + def test_commander_command(self): + method = "foo" + args = [["bar", "baz"], 12] + self.commander.command(method, *args, UNDEFINED) + + self.assertCommandEqual(method, *args, key=self.commander.key) + + def test_commander_destroy(self): + old_count = Commander._objectCount + self.commander.destroy() + + self.assertEqual(old_count - 1, Commander._objectCount) + self.assertCommandEqual("destroy", key=self.commander.key) diff --git a/tests/test_execute.py b/tests/test_execute.py new file mode 100644 index 0000000..481ade4 --- /dev/null +++ b/tests/test_execute.py @@ -0,0 +1,55 @@ +import json +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest import mock +from urllib.error import HTTPError + +import algorithm_visualizer + + +class TestExecute(unittest.TestCase): + @mock.patch.object(algorithm_visualizer.Commander, "commands", new_callable=mock.PropertyMock) + def test_create_json_file(self, cmd_mock): + expected_cmds = [ + { + "key": "abc123", + "method": "foo.bar", + "args": [1, 2, 3], + } + ] + cmd_mock.return_value = expected_cmds + + with TemporaryDirectory() as temp_dir: + path = Path(temp_dir, "visualization.json") + algorithm_visualizer.create_json_file(path) + self.assertTrue(path.is_file()) + + with path.open("r", encoding="UTF-8") as f: + actual_cmds = json.load(f) + + self.assertEqual(expected_cmds, actual_cmds) + + @mock.patch.object(algorithm_visualizer, "urlopen") + def test_get_url(self, mock_urlopen): + expected_url = "https://foo.bar" + + response = mock.Mock() + type(response).status = mock.PropertyMock(return_value=200) + response.read = mock.Mock(return_value=expected_url.encode("utf-8")) + + mock_urlopen.return_value = response + + url = algorithm_visualizer.get_url() + self.assertEqual(url, expected_url) + + @mock.patch.object(algorithm_visualizer, "urlopen") + def test_get_url_non_200(self, mock_urlopen): + response = mock.Mock() + type(response).status = mock.PropertyMock(return_value=201) + response.info = mock.Mock(return_value={}) + + mock_urlopen.return_value = response + + self.assertRaises(HTTPError, algorithm_visualizer.get_url) + diff --git a/tests/test_layouts.py b/tests/test_layouts.py new file mode 100644 index 0000000..0f99a1a --- /dev/null +++ b/tests/test_layouts.py @@ -0,0 +1,36 @@ +from algorithm_visualizer import Commander +from algorithm_visualizer import Layout + +from tests import CommanderTestCase + + +class LayoutsTests(CommanderTestCase): + def setUp(self): + self.child = Commander() + self.layout = Layout([self.child]) + + def test_layout_create(self): + layout = Layout([self.child]) + + self.assertCommandEqual("Layout", [self.child.key], key=layout.key) + + def test_layout_setRoot(self): + self.layout.setRoot(self.child) + + self.assertCommandEqual("setRoot", self.child.key) + + def test_layout_add(self): + index = 1 + self.layout.add(self.child, index) + + self.assertCommandEqual("add", self.child.key, index, key=self.layout.key) + + def test_layout_remove(self): + self.layout.remove(self.child) + + self.assertCommandEqual("remove", self.child.key, key=self.layout.key) + + def test_layout_removeAll(self): + self.layout.removeAll() + + self.assertCommandEqual("removeAll", key=self.layout.key) diff --git a/tests/test_randomize.py b/tests/test_randomize.py new file mode 100644 index 0000000..431d626 --- /dev/null +++ b/tests/test_randomize.py @@ -0,0 +1,193 @@ +import unittest + +from algorithm_visualizer import Randomize + +ITERATIONS = 100 + + +class IntegerTests(unittest.TestCase): + def test_integer_type(self): + rand = Randomize.Integer() + value = rand.create() + + self.assertIs(type(value), int) + + def test_integer_range(self): + rand_range = (1, 2) + rand = Randomize.Integer(*rand_range) + + for _ in range(ITERATIONS): + value = rand.create() + + self.assertIn(value, rand_range) + + +class DoubleTests(unittest.TestCase): + def test_double_type(self): + rand = Randomize.Double() + value = rand.create() + + self.assertIs(type(value), float) + + def test_double_range(self): + rand_range = (1.0, 2.0) + rand = Randomize.Double(*rand_range) + + for _ in range(ITERATIONS): + value = rand.create() + + self.assertGreaterEqual(value, 1.0) + self.assertLessEqual(value, 2.0) + + +class StringTests(unittest.TestCase): + def test_string_type(self): + rand = Randomize.String() + value = rand.create() + + self.assertIs(type(value), str) + + def test_string_length(self): + rand = Randomize.String(10) + value = rand.create() + + self.assertEqual(len(value), 10) + + def test_string_letters(self): + letters = ('a', 'b', 'c') + rand = Randomize.String(letters=letters) + + for _ in range(ITERATIONS): + for char in rand.create(): + self.assertIn(char, letters) + + +class Array1DTests(unittest.TestCase): + def test_array1d_length(self): + rand = Randomize.Array1D(5) + array = rand.create() + + self.assertEqual(len(array), 5) + + def test_array1d_randomizer(self): + rand = Randomize.Array1D(ITERATIONS, randomizer=Randomize.Double(2, 4)) + array = rand.create() + + for value in array: + self.assertIs(type(value), float) + self.assertGreaterEqual(value, 2) + self.assertLessEqual(value, 4) + + def test_array1d_sorted(self): + rand = Randomize.Array1D(ITERATIONS) + + sort_ret = rand.sorted(True) + self.assertIs(sort_ret, rand) + self.assertTrue(rand._sorted) + + array = rand.create() + + for i in range(len(array) - 1): + self.assertLessEqual(array[i], array[i + 1]) + + +class Array2DTests(unittest.TestCase): + def test_array2d_length(self): + rand = Randomize.Array2D(5, 3) + arrays = rand.create() + + self.assertEqual(len(arrays), 5) + + for array in arrays: + self.assertEqual(len(array), 3) + + def test_array2d_randomizer(self): + rand = Randomize.Array2D(ITERATIONS, ITERATIONS, randomizer=Randomize.Double(2, 4)) + arrays = rand.create() + + for array in arrays: + for value in array: + self.assertIs(type(value), float) + self.assertGreaterEqual(value, 2) + self.assertLessEqual(value, 4) + + def test_array2d_sorted(self): + rand = Randomize.Array2D(ITERATIONS, ITERATIONS) + + sort_ret = rand.sorted(True) + self.assertIs(sort_ret, rand) + self.assertTrue(rand._sorted) + + arrays = rand.create() + + for array in arrays: + for i in range(len(array) - 1): + self.assertLessEqual(array[i], array[i + 1]) + + +class GraphTests(unittest.TestCase): + def test_graph_length(self): + rand = Randomize.Graph(3) + graph = rand.create() + + self.assertEqual(len(graph), 3) + + for edges in graph: + self.assertEqual(len(edges), 3) + + def test_graph_no_loops(self): + rand = Randomize.Graph(ITERATIONS, ratio=1) + graph = rand.create() + + for i, edges in enumerate(graph): + for j, edge in enumerate(edges): + if i == j: + self.assertEqual(edge, 0) + + def test_graph_ratio_1(self): + rand = Randomize.Graph(ITERATIONS, ratio=1) + graph = rand.create() + + for i, edges in enumerate(graph): + for j, edge in enumerate(edges): + if i != j: + self.assertEqual(edge, 1) + + def test_graph_ratio_0(self): + rand = Randomize.Graph(ITERATIONS, ratio=0) + graph = rand.create() + + for i, edges in enumerate(graph): + for j, edge in enumerate(edges): + if i != j: + self.assertEqual(edge, 0) + + def test_graph_undirected(self): + rand = Randomize.Graph(ITERATIONS) + + directed_ret = rand.directed(False) + self.assertIs(directed_ret, rand) + self.assertFalse(rand._directed) + + graph = rand.create() + + for i, edges in enumerate(graph): + for j, edge in enumerate(edges): + if i != j: + self.assertEqual(edge, graph[j][i]) + + def test_graph_weighted(self): + rand = Randomize.Graph(ITERATIONS, ratio=1, randomizer=Randomize.Double(2, 4)) + + weighted_ret = rand.weighted() + self.assertIs(weighted_ret, rand) + self.assertTrue(rand._weighted) + + graph = rand.create() + + for i, edges in enumerate(graph): + for j, edge in enumerate(edges): + if i != j: + self.assertIs(type(edge), float) + self.assertGreaterEqual(edge, 2) + self.assertLessEqual(edge, 4) diff --git a/tests/tracers/__init__.py b/tests/tracers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tracers/test_array1d.py b/tests/tracers/test_array1d.py new file mode 100644 index 0000000..1d69bc9 --- /dev/null +++ b/tests/tracers/test_array1d.py @@ -0,0 +1,45 @@ +from algorithm_visualizer import Array1DTracer +from algorithm_visualizer import ChartTracer + +from tests import CommanderTestCase + + +class Array1DTests(CommanderTestCase): + def setUp(self): + self.tracer = Array1DTracer() + + def test_array1d_set(self): + args = [[1, 2, 3]] + self.tracer.set(*args) + + self.assertCommandEqual("set", *args, key=self.tracer.key) + + def test_array1d_patch(self): + args = [1, "foo"] + self.tracer.patch(*args) + + self.assertCommandEqual("patch", *args, key=self.tracer.key) + + def test_array1d_depatch(self): + args = [1] + self.tracer.depatch(*args) + + self.assertCommandEqual("depatch", *args, key=self.tracer.key) + + def test_array1d_select(self): + args = [1, 2] + self.tracer.select(*args) + + self.assertCommandEqual("select", *args, key=self.tracer.key) + + def test_array1d_deselect(self): + args = [1, 2] + self.tracer.deselect(*args) + + self.assertCommandEqual("deselect", *args, key=self.tracer.key) + + def test_array1d_chart(self): + chart = ChartTracer() + self.tracer.chart(chart) + + self.assertCommandEqual("chart", chart.key, key=self.tracer.key) diff --git a/tests/tracers/test_array2d.py b/tests/tracers/test_array2d.py new file mode 100644 index 0000000..5e97c91 --- /dev/null +++ b/tests/tracers/test_array2d.py @@ -0,0 +1,68 @@ +from algorithm_visualizer import Array2DTracer + +from tests import CommanderTestCase + + +class Array2DTests(CommanderTestCase): + def setUp(self): + self.tracer = Array2DTracer() + + def test_array2d_set(self): + args = [ + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + ] + self.tracer.set(*args) + + self.assertCommandEqual("set", *args, key=self.tracer.key) + + def test_array2d_patch(self): + args = [1, 2, "foo"] + self.tracer.patch(*args) + + self.assertCommandEqual("patch", *args, key=self.tracer.key) + + def test_array2d_depatch(self): + args = [1, 2] + self.tracer.depatch(*args) + + self.assertCommandEqual("depatch", *args, key=self.tracer.key) + + def test_array2d_select(self): + args = [1, 2, 3, 4] + self.tracer.select(*args) + + self.assertCommandEqual("select", *args, key=self.tracer.key) + + def test_array2d_selectRow(self): + args = [1, 2, 3] + self.tracer.selectRow(*args) + + self.assertCommandEqual("selectRow", *args, key=self.tracer.key) + + def test_array2d_selectCol(self): + args = [1, 2, 3] + self.tracer.selectCol(*args) + + self.assertCommandEqual("selectCol", *args, key=self.tracer.key) + + def test_array2d_deselect(self): + args = [1, 2, 3, 4] + self.tracer.deselect(*args) + + self.assertCommandEqual("deselect", *args, key=self.tracer.key) + + def test_array2d_deselectRow(self): + args = [1, 2, 3] + self.tracer.deselectRow(*args) + + self.assertCommandEqual("deselectRow", *args, key=self.tracer.key) + + def test_array2d_deselectCol(self): + args = [1, 2, 3] + self.tracer.deselectCol(*args) + + self.assertCommandEqual("deselectCol", *args, key=self.tracer.key) diff --git a/tests/tracers/test_graph.py b/tests/tracers/test_graph.py new file mode 100644 index 0000000..7f1e7f6 --- /dev/null +++ b/tests/tracers/test_graph.py @@ -0,0 +1,120 @@ +from algorithm_visualizer import GraphTracer +from algorithm_visualizer import LogTracer + +from tests import CommanderTestCase + + +class GraphTests(CommanderTestCase): + def setUp(self): + self.tracer = GraphTracer() + + def test_graph_set(self): + args = [ + [ + [0, 1, 0], + [1, 0, 1], + [0, 1, 0], + ], + ] + self.tracer.set(*args) + + self.assertCommandEqual("set", *args, key=self.tracer.key) + + def test_graph_directed(self): + args = [True] + ret = self.tracer.directed(*args) + + self.assertIs(ret, self.tracer) + self.assertCommandEqual("directed", *args, key=self.tracer.key) + + def test_graph_weighted(self): + args = [True] + ret = self.tracer.weighted(*args) + + self.assertIs(ret, self.tracer) + self.assertCommandEqual("weighted", *args, key=self.tracer.key) + + def test_graph_layoutCircle(self): + ret = self.tracer.layoutCircle() + + self.assertIs(ret, self.tracer) + self.assertCommandEqual("layoutCircle", key=self.tracer.key) + + def test_graph_layoutTree(self): + args = [True] + ret = self.tracer.layoutTree(*args) + + self.assertIs(ret, self.tracer) + self.assertCommandEqual("layoutTree", *args, key=self.tracer.key) + + def test_graph_layoutRandom(self): + ret = self.tracer.layoutRandom() + + self.assertIs(ret, self.tracer) + self.assertCommandEqual("layoutRandom", key=self.tracer.key) + + def test_graph_addNode(self): + args = ["foo", 12.34, 1, 2, 3, 4] + self.tracer.addNode(*args) + + self.assertCommandEqual("addNode", *args, key=self.tracer.key) + + def test_graph_updateNode(self): + args = ["foo", 12.34, 1, 2, 3, 4] + self.tracer.updateNode(*args) + + self.assertCommandEqual("updateNode", *args, key=self.tracer.key) + + def test_graph_removeNode(self): + args = ["foo"] + self.tracer.removeNode(*args) + + self.assertCommandEqual("removeNode", *args, key=self.tracer.key) + + def test_graph_addEdge(self): + args = ["source", "target", 12.34, 1, 2] + self.tracer.addEdge(*args) + + self.assertCommandEqual("addEdge", *args, key=self.tracer.key) + + def test_graph_updateEdge(self): + args = ["source", "target", 12.34, 1, 2] + self.tracer.updateEdge(*args) + + self.assertCommandEqual("updateEdge", *args, key=self.tracer.key) + + def test_graph_removeEdge(self): + args = ["source", "target"] + self.tracer.removeEdge(*args) + + self.assertCommandEqual("removeEdge", *args, key=self.tracer.key) + + def test_graph_visit(self): + args = ["foo", "bar", 12.34] + self.tracer.visit(*args) + + self.assertCommandEqual("visit", *args, key=self.tracer.key) + + def test_graph_leave(self): + args = ["foo", "bar", 12.34] + self.tracer.leave(*args) + + self.assertCommandEqual("leave", *args, key=self.tracer.key) + + def test_graph_select(self): + args = ["foo", "bar"] + self.tracer.select(*args) + + self.assertCommandEqual("select", *args, key=self.tracer.key) + + def test_graph_deselect(self): + args = ["foo", "bar"] + self.tracer.deselect(*args) + + self.assertCommandEqual("deselect", *args, key=self.tracer.key) + + def test_graph_log(self): + log = LogTracer() + self.tracer.log(log) + + self.assertCommandEqual("log", log.key, key=self.tracer.key) diff --git a/tests/tracers/test_log.py b/tests/tracers/test_log.py new file mode 100644 index 0000000..8631332 --- /dev/null +++ b/tests/tracers/test_log.py @@ -0,0 +1,32 @@ +from algorithm_visualizer import LogTracer + +from tests import CommanderTestCase + + +class LogTests(CommanderTestCase): + def setUp(self): + self.logger = LogTracer() + + def test_log_set(self): + args = [1] + self.logger.set(*args) + + self.assertCommandEqual("set", *args, key=self.logger.key) + + def test_log_print(self): + args = ["hello"] + self.logger.print(*args) + + self.assertCommandEqual("print", *args, key=self.logger.key) + + def test_log_println(self): + args = ["hello"] + self.logger.println(*args) + + self.assertCommandEqual("println", *args, key=self.logger.key) + + def test_log_printf(self): + args = ["%s", "hello"] + self.logger.printf(*args) + + self.assertCommandEqual("printf", *args, key=self.logger.key) diff --git a/tests/tracers/test_tracer.py b/tests/tracers/test_tracer.py new file mode 100644 index 0000000..0ee9db7 --- /dev/null +++ b/tests/tracers/test_tracer.py @@ -0,0 +1,30 @@ +from algorithm_visualizer import Tracer + +from tests import CommanderTestCase + + +class TracerTests(CommanderTestCase): + def setUp(self): + self.tracer = Tracer() + + def test_tracer_create(self): + args = ["foo"] + tracer = Tracer(*args) + + self.assertCommandEqual("Tracer", *args, key=tracer.key) + + def test_tracer_delay(self): + args = [5] + self.tracer.delay(*args) + + self.assertCommandEqual("delay", *args) + + def test_tracer_set(self): + self.tracer.set() + + self.assertCommandEqual("set", key=self.tracer.key) + + def test_tracer_reset(self): + self.tracer.reset() + + self.assertCommandEqual("reset", key=self.tracer.key)