Repository: projectmesa/mesa
Pull request #692 (2019-05-22 14:43:22)

Increase test coverage and change to codecov
I think mesa can do better in terms of test coverage. I started to write some additional tests for the base model class (coverage now at 100%) and the space module.

I then took a look at the poor coverage of the visualization module and provided two updates:
1) I changed the test_examples to also create (but not launch!) the server and render the elements. This gives us coverage for the `__init__` and `render` methods of the viz modules. Now of course this does not tell us if they work correctly, but we already know that something is happening without errors.
2) I started a tornado TestCase. It currently tests if a webpage is being created without errors and checks if the Server responds correctly so some websocket messages. 

The other file changes of this PR are some minor changes to let the tests run properly. 

Coverage is now at 90%. Please feel free to provide additional tests to this PR!!

You can check uncovered lines [here](https://coveralls.io/builds/23540554)


Patch:
diff --git a/.coveragerc b/.coveragerc
index 2a7684c0..31085cb1 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,9 +1,7 @@
+[run]
+source = mesa
+branch = True
+
 [report]
 omit =
-    mesa/tests/*
-    bin/*
-    develop-eggs/*
-    eggs/*
-    */site-packages/*
-    */python3.4/distutils/*
     tests/*
diff --git a/.travis.yml b/.travis.yml
index c1b43414..41a523be 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
 dist: xenial
 language: python
 sudo: false
+dist: xenial
 os:
   - linux
 cache:
@@ -11,7 +12,7 @@ python:
   - "3.7"
   - "nightly"
 install:
-  - pip install coveralls
+  - pip install codecov
   - pip install .[dev]
 script:
   - export PYTHONPATH=$PYTHONPATH:$(pwd)
@@ -19,7 +20,7 @@ script:
   # * E123 - indentation on data structures
   # * W504 - line break after binary operator
   - flake8 . --ignore=F403,E501,E123,E128,W504 --exclude=docs,build
-  - py.test --cov=mesa tests/
+  - py.test --cov=mesa tests/ --cov-report=xml
   # - ./tests/test_end_to_end_viz.sh  # needs to be investigated on why this took forever
 after_success:
-  - coveralls
+  - codecov
diff --git a/README.rst b/README.rst
index f1e24b8c..a5722898 100644
--- a/README.rst
+++ b/README.rst
@@ -4,8 +4,8 @@ Mesa: Agent-based modeling in Python 3+
 .. image:: https://api.travis-ci.org/projectmesa/mesa.svg?branch=master
         :target: https://travis-ci.org/projectmesa/mesa
 
-.. image:: https://coveralls.io/repos/projectmesa/mesa/badge.svg
-    :target: https://coveralls.io/r/projectmesa/mesa
+.. image:: https://codecov.io/gh/projectmesa/mesa/branch/master/graph/badge.svg
+  :target: https://codecov.io/gh/projectmesa/mesa
 
 `Mesa`_ is an Apache2 licensed agent-based modeling (or ABM) framework in Python.
 
diff --git a/docs/tutorials/adv_tutorial.rst b/docs/tutorials/adv_tutorial.rst
index 73665214..45e1b3bc 100644
--- a/docs/tutorials/adv_tutorial.rst
+++ b/docs/tutorials/adv_tutorial.rst
@@ -96,7 +96,7 @@ Finally, when you're ready to run the visualization, use the server's
     server = ModularServer(MoneyModel,
                            [grid],
                            "Money Model",
-                           {"N":100, "width":10, "height":10})
+                           100, 10, 10)
     server.port = 8521 # The default
     server.launch()
 
@@ -121,7 +121,7 @@ The full code should now look like:
     server = ModularServer(MoneyModel,
                            [grid],
                            "Money Model",
-                           {"N":100, "width":10, "height":10})
+                           100, 10, 10)
     server.port = 8521 # The default
     server.launch()
 
@@ -228,7 +228,7 @@ chart will appear underneath the grid.
     server = ModularServer(MoneyModel,
                            [grid, chart],
                            "Money Model",
-                           {"N":100, "width":10, "height":10})
+                           100, 10, 10)
 
 Launch the visualization and start a model run, and you'll see a line
 chart underneath the grid. Every step of the model, the line chart
@@ -497,7 +497,7 @@ Now, you can create your new HistogramModule and add it to the server:
         server = ModularServer(MoneyModel,
                                [grid, histogram, chart],
                                "Money Model",
-                               {"N":100, "width":10, "height":10})
+                               100, 10, 10)
         server.launch()
 
 Run this code, and you should see your brand-new histogram added to the
diff --git a/examples/schelling/server.py b/examples/schelling/server.py
index f9dbf508..e1825bf7 100644
--- a/examples/schelling/server.py
+++ b/examples/schelling/server.py
@@ -49,4 +49,3 @@ model_params = {
 server = ModularServer(Schelling,
                        [canvas_element, happy_element, happy_chart],
                        "Schelling", model_params)
-server.launch()
diff --git a/examples/shape_example/shape_example/server.py b/examples/shape_example/shape_example/server.py
index c2fd66bd..82e6ac29 100644
--- a/examples/shape_example/shape_example/server.py
+++ b/examples/shape_example/shape_example/server.py
@@ -12,34 +12,32 @@ def agent_draw(agent):
         pass
     elif isinstance(agent, Walker):
         print("Uid: {0}, Heading: {1}".format(agent.unique_id, agent.heading))
-        portrayal = {"Shape": "arrowHead",
-                     "Filled": "true",
-                     "Layer": 2,
-                     "Color": ["#00FF00", "#99FF99"],
-                     "stroke_color": "#666666",
-                     "Filled": "true",
-                     "heading_x": agent.heading[0],
-                     "heading_y": agent.heading[1],
-                     "text": agent.unique_id,
-                     "text_color": "white",
-                     "scale": 0.8,
-                     }
+        portrayal = {
+            "Shape": "arrowHead",
+            "Filled": "true",
+            "Layer": 2,
+            "Color": ["#00FF00", "#99FF99"],
+            "stroke_color": "#666666",
+            "Filled": "true",
+            "heading_x": agent.heading[0],
+            "heading_y": agent.heading[1],
+            "text": agent.unique_id,
+            "text_color": "white",
+            "scale": 0.8,
+        }
     return portrayal
 
 
-def launch_shape_model():
-    width = 15
-    height = 10
-    num_agents = 2
-    pixel_ratio = 50
-    grid = CanvasGrid(agent_draw, width, height,
-                      width * pixel_ratio, height * pixel_ratio)
-    server = ModularServer(ShapeExample, [grid], "Shape Model Example",
-                           {"N": num_agents, "width": width, "height": height})
-    server.max_steps = 0
-    server.port = 8521
-    server.launch()
-
-
-if __name__ == "__main__":
-    launch_shape_model()
+width = 15
+height = 10
+num_agents = 2
+pixel_ratio = 50
+grid = CanvasGrid(agent_draw, width, height, width * pixel_ratio, height * pixel_ratio)
+server = ModularServer(
+    ShapeExample,
+    [grid],
+    "Shape Model Example",
+    {"N": num_agents, "width": width, "height": height},
+)
+server.max_steps = 0
+server.port = 8521
diff --git a/mesa/model.py b/mesa/model.py
index 5f3e9188..78dc010f 100644
--- a/mesa/model.py
+++ b/mesa/model.py
@@ -22,7 +22,7 @@ class Model:
         model.random = random.Random(model._seed)
         return model
 
-    def __init__(self):
+    def __init__(self, *args, **kwargs):
         """ Create a new model. Overload this method with the actual code to
         start the model.
 


Test patch:
diff --git a/.mypy_cache/3.6/test_tornado.data.json b/.mypy_cache/3.6/test_tornado.data.json
new file mode 100644
index 00000000..c03abb41
--- /dev/null
+++ b/.mypy_cache/3.6/test_tornado.data.json
@@ -0,0 +1,1 @@
+{".class": "MypyFile", "_fullname": "test_tornado", "is_partial_stub_package": false, "is_stub": false, "names": {".class": "SymbolTable", "AsyncHTTPTestCase": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "Var", "flags": ["is_suppressed_import"], "fullname": "test_tornado.AsyncHTTPTestCase", "name": "AsyncHTTPTestCase", "type": {".class": "AnyType", "missing_import_name": "test_tornado.AsyncHTTPTestCase", "source_any": null, "type_of_any": 3}}}, "Model": {".class": "SymbolTableNode", "cross_ref": "mesa.model.Model", "kind": "Gdef"}, "ModularServer": {".class": "SymbolTableNode", "cross_ref": "mesa.visualization.ModularVisualization.ModularServer", "kind": "Gdef"}, "TestServer": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "TypeInfo", "_promote": null, "abstract_attributes": [], "bases": ["builtins.object"], "declared_metaclass": null, "defn": {".class": "ClassDef", "fullname": "test_tornado.TestServer", "name": "TestServer", "type_vars": []}, "flags": ["fallback_to_any"], "fullname": "test_tornado.TestServer", "metaclass_type": null, "metadata": {}, "module_name": "test_tornado", "mro": ["test_tornado.TestServer", "builtins.object"], "names": {".class": "SymbolTable", "get_app": {".class": "SymbolTableNode", "kind": "Mdef", "node": {".class": "FuncDef", "arg_kinds": [0], "arg_names": ["self"], "flags": [], "fullname": "test_tornado.TestServer.get_app", "name": "get_app", "type": null}}, "test_homepage": {".class": "SymbolTableNode", "kind": "Mdef", "node": {".class": "FuncDef", "arg_kinds": [0], "arg_names": ["self"], "flags": [], "fullname": "test_tornado.TestServer.test_homepage", "name": "test_homepage", "type": null}}, "test_websocket": {".class": "SymbolTableNode", "kind": "Mdef", "node": {".class": "Decorator", "func": {".class": "FuncDef", "arg_kinds": [0], "arg_names": ["self"], "flags": ["is_generator", "is_decorated"], "fullname": "test_tornado.TestServer.test_websocket", "name": "test_websocket", "type": null}, "is_overload": false, "var": {".class": "Var", "flags": ["is_initialized_in_class"], "fullname": null, "name": "test_websocket", "type": {".class": "AnyType", "missing_import_name": "test_tornado.tornado", "source_any": {".class": "AnyType", "missing_import_name": "test_tornado.tornado", "source_any": null, "type_of_any": 3}, "type_of_any": 7}}}}}, "tuple_type": null, "type_vars": [], "typeddict_type": null}}, "__doc__": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "Var", "flags": [], "fullname": "test_tornado.__doc__", "name": "__doc__", "type": "builtins.str"}}, "__file__": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "Var", "flags": [], "fullname": "test_tornado.__file__", "name": "__file__", "type": "builtins.str"}}, "__name__": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "Var", "flags": [], "fullname": "test_tornado.__name__", "name": "__name__", "type": "builtins.str"}}, "__package__": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "Var", "flags": [], "fullname": "test_tornado.__package__", "name": "__package__", "type": "builtins.str"}}, "json": {".class": "SymbolTableNode", "cross_ref": "json", "kind": "ModuleRef"}, "tornado": {".class": "SymbolTableNode", "kind": "Gdef", "node": {".class": "Var", "flags": ["is_suppressed_import"], "fullname": "test_tornado.tornado", "name": "tornado", "type": {".class": "AnyType", "missing_import_name": "test_tornado.tornado", "source_any": null, "type_of_any": 3}}}}, "path": "c:\\Users\\Vincent\\OneDrive\\mesa\\tests\\test_tornado.py"}
\ No newline at end of file
diff --git a/.mypy_cache/3.6/test_tornado.meta.json b/.mypy_cache/3.6/test_tornado.meta.json
new file mode 100644
index 00000000..90824a61
--- /dev/null
+++ b/.mypy_cache/3.6/test_tornado.meta.json
@@ -0,0 +1,1 @@
+{"child_modules": [], "data_mtime": 1564998707, "dep_lines": [3, 4, 5, 1, 1, 1, 1, 1, 1, 2], "dep_prios": [5, 5, 10, 5, 30, 30, 30, 30, 5, 10], "dependencies": ["mesa", "mesa.visualization.ModularVisualization", "json", "builtins", "abc", "mesa.model", "mesa.visualization", "typing"], "deps_mtime": null, "hash": "2e464cdf7fefc1c16552c8481d6680bd", "id": "test_tornado", "ignore_all": false, "interface_hash": "f3a005d50a7f3b5cad1829cb7deac5c0", "mtime": 1564999144, "options": {"allow_untyped_globals": false, "always_false": [], "always_true": [], "bazel": false, "check_untyped_defs": false, "disallow_any_decorated": false, "disallow_any_explicit": false, "disallow_any_expr": false, "disallow_any_generics": false, "disallow_any_unimported": false, "disallow_incomplete_defs": false, "disallow_subclassing_any": false, "disallow_untyped_calls": false, "disallow_untyped_decorators": false, "disallow_untyped_defs": false, "follow_imports": "silent", "follow_imports_for_stubs": false, "ignore_errors": false, "ignore_missing_imports": true, "local_partial_types": false, "mypyc": false, "no_implicit_optional": false, "platform": "win32", "plugins": [], "show_none_errors": true, "strict_optional": true, "strict_optional_whitelist": null, "warn_no_return": true, "warn_return_any": false, "warn_unused_ignores": false}, "path": "c:\\Users\\Vincent\\OneDrive\\mesa\\tests\\test_tornado.py", "size": 1309, "suppressed": ["tornado.testing", "tornado"], "version_id": "0.660"}
\ No newline at end of file
diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py
index 419c9ab2..740f9c63 100644
--- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py
+++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py
@@ -8,7 +8,7 @@ from mesa.space import MultiGrid
 from mesa.time import RandomActivation
 from mesa.visualization.TextVisualization import TextVisualization, TextGrid
 
-from random_walk import RandomWalker
+from wolf_sheep.random_walk import RandomWalker
 
 
 class WalkerAgent(RandomWalker):
diff --git a/tests/test_examples.py b/tests/test_examples.py
index b2fdb4d8..e715b693 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -50,10 +50,14 @@ class TestExamples(unittest.TestCase):
             with self.active_example_dir(example):
                 try:
                     # model.py at the top level
-                    mod = importlib.import_module('model'.format(example))
+                    mod = importlib.import_module('model')
+                    server = importlib.import_module('server')
+                    server.server.render_model()
                 except ImportError:
                     # <example>/model.py
                     mod = importlib.import_module('{}.model'.format(example.replace('-', '_')))
+                    server = importlib.import_module('{}.server'.format(example.replace('-', '_')))
+                    server.server.render_model()
                 Model = getattr(mod, classcase(example))
                 model = Model()
                 (model.step() for _ in range(100))
diff --git a/tests/test_model.py b/tests/test_model.py
new file mode 100644
index 00000000..a326978d
--- /dev/null
+++ b/tests/test_model.py
@@ -0,0 +1,39 @@
+from mesa.model import Model
+
+
+def test_model_set_up():
+    model = Model()
+    assert model.running is True
+    assert model.schedule is None
+    assert model.current_id == 0
+    assert model.current_id + 1 == model.next_id()
+    assert model.current_id == 1
+    model.step()
+
+
+def test_running():
+    class TestModel(Model):
+        steps = 0
+
+        def step(self):
+            """Increase steps until 10."""
+            self.steps += 1
+            if self.steps == 10:
+                self.running = False
+
+    model = TestModel()
+    model.run_model()
+
+
+def test_seed(seed=23):
+    model = Model(seed=seed)
+    assert model._seed == seed
+
+
+def test_reset_randomizer(newseed=42):
+    model = Model()
+    oldseed = model._seed
+    model.reset_randomizer()
+    assert model._seed == oldseed
+    model.reset_randomizer(seed=newseed)
+    assert model._seed == newseed
diff --git a/tests/test_space.py b/tests/test_space.py
index 6af7058c..be1d79c7 100644
--- a/tests/test_space.py
+++ b/tests/test_space.py
@@ -2,6 +2,7 @@ import unittest
 
 import networkx as nx
 import numpy as np
+import pytest
 
 from mesa.space import ContinuousSpace
 from mesa.space import SingleGrid
@@ -273,6 +274,16 @@ class TestSingleGrid(unittest.TestCase):
             assert a.pos is None
             assert self.space.grid[pos[0]][pos[1]] is None
 
+    def test_empty_cells(self):
+        if self.space.exists_empty_cells():
+            pytest.deprecated_call(self.space.find_empty)
+            for i, pos in enumerate(list(self.space.empties)):
+                a = MockAgent(-i, pos)
+                self.space.position_agent(a, x=pos[0], y=pos[1])
+        assert self.space.find_empty() is None
+        with self.assertRaises(Exception):
+            self.space.move_to_empty(a)
+
     def move_agent(self):
         agent_number = 0
         initial_pos = TEST_AGENTS_GRID[agent_number]
diff --git a/tests/test_tornado.py b/tests/test_tornado.py
new file mode 100644
index 00000000..113e779d
--- /dev/null
+++ b/tests/test_tornado.py
@@ -0,0 +1,40 @@
+from tornado.testing import AsyncHTTPTestCase
+import tornado
+from mesa import Model
+from mesa.visualization.ModularVisualization import ModularServer
+import json
+
+
+class TestServer(AsyncHTTPTestCase):
+
+    def get_app(self):
+        app = ModularServer(Model, [])
+        return app
+
+    def test_homepage(self):
+        response = self.fetch('/')
+        assert response.error is None
+
+    @tornado.testing.gen_test
+    def test_websocket(self):
+        ws_url = "ws://localhost:" + str(self.get_http_port()) + "/ws"
+        ws_client = yield tornado.websocket.websocket_connect(ws_url)
+
+        # Now we can run a test on the WebSocket.
+        response = yield ws_client.read_message()
+        msg = json.loads(response)
+        assert msg["type"] == "model_params"
+
+        ws_client.write_message('{"type": "get_step"}')
+        response = yield ws_client.read_message()
+        msg = json.loads(response)
+        assert msg["type"] == "viz_state"
+
+        ws_client.write_message('{"type": "reset"}')
+        response = yield ws_client.read_message()
+        msg = json.loads(response)
+        assert msg["type"] == "viz_state"
+
+        ws_client.write_message("Unknown message!")
+        response = yield ws_client.read_message()
+        assert response is None