diff --git a/.circleci/config.yml b/.circleci/config.yml index 2878f4d..8bccf54 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: build: docker: - image: circleci/python:3.6.1 - + - image: redislabs/redisgraph:edge working_directory: ~/repo @@ -56,7 +56,7 @@ jobs: name: run tests command: | . venv/bin/activate - coverage run test.py + coverage run tests/functional/test_all.py - early_return_for_forked_pull_requests @@ -69,7 +69,7 @@ jobs: # - store_artifacts: # path: test-reports # destination: test-reports - + workflows: version: 2 commit: @@ -84,4 +84,4 @@ workflows: only: - master jobs: - - build + - build diff --git a/README.md b/README.md index e1461da..5716c9e 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ for record in result.result_set: person_age = record[1] visit_purpose = record[2] country_name = record[3] - + query = """MATCH p = (:person)-[:visited {purpose:"pleasure"}]->(:country) RETURN p""" result = redis_graph.query(query) @@ -88,3 +88,10 @@ pip install redisgraph ``` pip install git+https://github.com/RedisGraph/redisgraph-py.git@master ``` + +### Install for development in env + +``` +tox -e env +source ./tox/env/bin/activate +``` \ No newline at end of file diff --git a/redisgraph/__init__.py b/redisgraph/__init__.py index 0b06119..34fd737 100644 --- a/redisgraph/__init__.py +++ b/redisgraph/__init__.py @@ -1,4 +1,4 @@ -from .node import Node -from .edge import Edge -from .graph import Graph -from .path import Path +from .node import Node # noqa +from .edge import Edge # noqa +from .graph import Graph # noqa +from .path import Path # noqa diff --git a/redisgraph/edge.py b/redisgraph/edge.py index 3dbcf69..f2a80c1 100644 --- a/redisgraph/edge.py +++ b/redisgraph/edge.py @@ -1,12 +1,14 @@ from redisgraph import Node -from .util import * +from .util import quote_string + class Edge: """ An edge connecting two nodes. """ - def __init__(self, src_node, relation, dest_node, edge_id=None, properties=None): + def __init__(self, src_node, relation, dest_node, edge_id=None, + properties=None): """ Create a new edge. """ diff --git a/redisgraph/exceptions.py b/redisgraph/exceptions.py index 1911c78..57df117 100644 --- a/redisgraph/exceptions.py +++ b/redisgraph/exceptions.py @@ -1,4 +1,4 @@ + class VersionMismatchException(Exception): def __init__(self, version): self.version = version - diff --git a/redisgraph/graph.py b/redisgraph/graph.py index 7f15c39..1b80ce8 100644 --- a/redisgraph/graph.py +++ b/redisgraph/graph.py @@ -1,7 +1,9 @@ -from .util import * import redis -from .query_result import QueryResult -from .exceptions import VersionMismatchException + +from redisgraph.util import random_string, quote_string +from redisgraph.query_result import QueryResult +from redisgraph.exceptions import VersionMismatchException + class Graph: """ @@ -173,7 +175,7 @@ def query(self, q, params=None, timeout=None, read_only=False): return QueryResult(self, response) except redis.exceptions.ResponseError as e: if "wrong number of arguments" in str(e): - print ("Note: RedisGraph Python requires server version 2.2.8 or above") + print("Note: RedisGraph Python requires server version 2.2.8 or above") raise e except VersionMismatchException as e: # client view over the graph schema is out of sync diff --git a/redisgraph/node.py b/redisgraph/node.py index c1b2d36..3757012 100644 --- a/redisgraph/node.py +++ b/redisgraph/node.py @@ -1,4 +1,5 @@ -from .util import * +from .util import quote_string + class Node: """ @@ -36,8 +37,8 @@ def __str__(self): def __eq__(self, rhs): # Quick positive check, if both IDs are set. - if self.id is not None and rhs.id is not None and self.id == rhs.id: - return True + if self.id is not None and rhs.id is not None and self.id != rhs.id: + return False # Label should match. if self.label != rhs.label: diff --git a/redisgraph/path.py b/redisgraph/path.py index db40460..903ec7c 100644 --- a/redisgraph/path.py +++ b/redisgraph/path.py @@ -1,10 +1,12 @@ from .node import Node from .edge import Edge -class Path: +class Path: def __init__(self, nodes, edges): - assert(isinstance(nodes, list) and isinstance(edges, list)) + if not (isinstance(nodes, list) and isinstance(edges, list)): + raise TypeError("nodes and edges must be list") + self._nodes = nodes self._edges = edges self.append_type = Node @@ -38,13 +40,15 @@ def nodes_count(self): return len(self._nodes) def add_node(self, node): - assert(type(node) == self.append_type) + if not isinstance(node, self.append_type): + raise AssertionError("Add Edge before adding Node") self._nodes.append(node) self.append_type = Edge return self def add_edge(self, edge): - assert(type(edge) == self.append_type) + if not isinstance(edge, self.append_type): + raise AssertionError("Add Node before adding Edge") self._edges.append(edge) self.append_type = Node return self diff --git a/redisgraph/query_result.py b/redisgraph/query_result.py index 1f0601a..6003802 100644 --- a/redisgraph/query_result.py +++ b/redisgraph/query_result.py @@ -17,8 +17,9 @@ INTERNAL_EXECUTION_TIME = 'internal execution time' STATS = [LABELS_ADDED, NODES_CREATED, PROPERTIES_SET, RELATIONSHIPS_CREATED, - NODES_DELETED, RELATIONSHIPS_DELETED, INDICES_CREATED, INDICES_DELETED, - CACHED_EXECUTION, INTERNAL_EXECUTION_TIME] + NODES_DELETED, RELATIONSHIPS_DELETED, INDICES_CREATED, INDICES_DELETED, + CACHED_EXECUTION, INTERNAL_EXECUTION_TIME] + class ResultSetColumnTypes: COLUMN_UNKNOWN = 0 @@ -26,6 +27,7 @@ class ResultSetColumnTypes: COLUMN_NODE = 2 # Unused as of RedisGraph v2.1.0, retained for backwards compatibility. COLUMN_RELATION = 3 # Unused as of RedisGraph v2.1.0, retained for backwards compatibility. + class ResultSetScalarTypes: VALUE_UNKNOWN = 0 VALUE_NULL = 1 @@ -38,6 +40,7 @@ class ResultSetScalarTypes: VALUE_NODE = 8 VALUE_PATH = 9 + class QueryResult: def __init__(self, graph, response): @@ -288,4 +291,3 @@ def cached_execution(self): @property def run_time_ms(self): return self._get_stat(INTERNAL_EXECUTION_TIME) - diff --git a/redisgraph/util.py b/redisgraph/util.py index 0f32e0d..117c186 100644 --- a/redisgraph/util.py +++ b/redisgraph/util.py @@ -3,12 +3,14 @@ __all__ = ['random_string', 'quote_string'] + def random_string(length=10): """ Returns a random N character long string. """ return ''.join(random.choice(string.ascii_lowercase) for x in range(length)) + def quote_string(v): """ RedisGraph strings must be quoted, @@ -23,6 +25,8 @@ def quote_string(v): if len(v) == 0: return '""' + v = v.replace('"', '\\"') + if v[0] != '"': v = '"' + v diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..71fa7b6 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,8 @@ +flake8 + +pytest +pytest-cov +pytest-html + +testtools>=1.4.0 +mock diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..3a66f87 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,51 @@ +# All Redis Graph client tests + +To keep code working properly and looking good it's critical to cover +all functionality with unit and functional tests and use automated code style +checks. + +Unit tests are tests that focus is to test line by line all code of library +where any of external dependencies need in runtime (like redisgraph) are mocked + +Functional tests on other hand act like black box testing, where we make sure +that client when used with real redisgraph is working properly + +Code style checks are analyzing code style, to make sure code looks like +written by one person. + +Combination of these 3 approaches will ensure high quality of the code. + + +To launch test please use: ```tox -e func``` + +## Requirements to run tests locally + +Redisgraph is using tox, highly popular tool that aims to automate and +standardize testing in Python. + +For more details https://tox.readthedocs.io/en/latest/ + +To install it locally run ```pip install tox``` + + +## Running tests locally + +```bash + +# To run all tests +tox + +# To run py3 tests +tox -e py3 + +# To run code style checks +tox -e pep8 + +# To run unittests with coverage +tox -e cover + +# To run functional test use +# Make sure that redis is running locally on standard port 6379 +tox -e func + +``` diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test.py b/tests/functional/test_all.py similarity index 96% rename from test.py rename to tests/functional/test_all.py index bcbbd65..c7006f8 100644 --- a/test.py +++ b/tests/functional/test_all.py @@ -1,34 +1,40 @@ -import redis import unittest -from redisgraph import * +from tests.utils import base + +import redis + +from redisgraph import Node, Edge, Graph, Path -class TestStringMethods(unittest.TestCase): +class TestStringMethods(base.TestCase): + def setUp(self): + super().setUp() self.r = redis.Redis(host='localhost', port=6379, decode_responses=True) def test_graph_creation(self): redis_graph = Graph('social', self.r) - + john = Node(label='person', properties={'name': 'John Doe', 'age': 33, 'gender': 'male', 'status': 'single'}) redis_graph.add_node(john) japan = Node(label='country', properties={'name': 'Japan'}) - + redis_graph.add_node(japan) edge = Edge(john, 'visited', japan, properties={'purpose': 'pleasure'}) redis_graph.add_edge(edge) - + redis_graph.commit() - - query = """MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) - RETURN p, v, c""" - + + query = ( + 'MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) ' + 'RETURN p, v, c') + result = redis_graph.query(query) - + person = result.result_set[0][0] visit = result.result_set[0][1] country = result.result_set[0][2] - + self.assertEqual(person, john) self.assertEqual(visit.properties, edge.properties) self.assertEqual(country, japan) @@ -179,34 +185,32 @@ def test_optional_match(self): def test_cached_execution(self): redis_graph = Graph('cached', self.r) redis_graph.query("CREATE ()") - + uncached_result = redis_graph.query("MATCH (n) RETURN n, $param", {'param': [0]}) self.assertFalse(uncached_result.cached_execution) - + # loop to make sure the query is cached on each thread on server - for x in range(0, 64): + for x in range(0, 64): cached_result = redis_graph.query("MATCH (n) RETURN n, $param", {'param': [0]}) self.assertEqual(uncached_result.result_set, cached_result.result_set) # should be cached on all threads by now self.assertTrue(cached_result.cached_execution) - + redis_graph.delete() - - + def test_execution_plan(self): redis_graph = Graph('execution_plan', self.r) - create_query = """CREATE (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), - (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), + create_query = """CREATE (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), + (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" redis_graph.query(create_query) - + result = redis_graph.execution_plan("MATCH (r:Rider)-[:rides]->(t:Team) WHERE t.name = $name RETURN r.name, t.name, $params", {'name': 'Yehuda'}) expected = "Results\n Project\n Conditional Traverse | (t:Team)->(r:Rider)\n Filter\n Node By Label Scan | (t:Team)" self.assertEqual(result, expected) - - redis_graph.delete() + redis_graph.delete() def test_query_timeout(self): redis_graph = Graph('timeout', self.r) @@ -229,7 +233,6 @@ def test_query_timeout(self): # Expecting an error. pass - def test_read_only_query(self): redis_graph = Graph('read_only', self.r) @@ -237,11 +240,10 @@ def test_read_only_query(self): # Issue a write query, specifying read-only true, this call should fail. redis_graph.query("CREATE (p:person {name:'a'})", read_only=True) assert(False) - except Exception as e: + except Exception: # Expecting an error. pass - def test_cache_sync(self): # This test verifies that client internal graph schema cache stays # in sync with the graph schema @@ -311,6 +313,6 @@ def test_cache_sync(self): assert(A._relationshipTypes[0] == 'S') assert(A._relationshipTypes[1] == 'R') + if __name__ == '__main__': unittest.main() - diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_edge.py b/tests/unit/test_edge.py new file mode 100644 index 0000000..20544e5 --- /dev/null +++ b/tests/unit/test_edge.py @@ -0,0 +1,76 @@ +from redisgraph import edge +from redisgraph import node +from tests.utils import base + + +class TestEdge(base.TestCase): + + def test_init(self): + + with self.assertRaises(AssertionError): + edge.Edge(None, None, None) + + with self.assertRaises(AssertionError): + edge.Edge(node.Node(), None, None) + + with self.assertRaises(AssertionError): + edge.Edge(None, None, node.Node()) + + self.assertIsInstance( + edge.Edge(node.Node(node_id=1), None, node.Node(node_id=2)), + edge.Edge + ) + + def test_toString(self): + props_result = edge.Edge(node.Node(), None, node.Node(), + properties={"a": "a", "b": 10}).toString() + self.assertEqual(props_result, '{a:"a",b:10}') + + no_props_result = edge.Edge(node.Node(), None, node.Node(), + properties={}).toString() + self.assertEqual(no_props_result, '') + + def test_stringify(self): + john = node.Node(alias='a', label='person', + properties={'name': 'John Doe', + 'age': 33, + 'someArray': [1, 2, 3]}) + japan = node.Node(alias='b', label='country', + properties={'name': 'Japan'}) + edge_with_relation = edge.Edge( + john, 'visited', japan, properties={'purpose': 'pleasure'}) + self.assertEqual( + '(a:person{age:33,name:"John Doe",someArray:[1, 2, 3]})' + '-[:visited{purpose:"pleasure"}]->' + '(b:country{name:"Japan"})', + str(edge_with_relation) + ) + edge_no_relation_no_props = edge.Edge(japan, '', john) + self.assertEqual( + '(b:country{name:"Japan"})' + '-[]->' + '(a:person{age:33,name:"John Doe",someArray:[1, 2, 3]})', + str(edge_no_relation_no_props) + ) + edge_only_props = edge.Edge(john, '', japan, + properties={'a': 'b', 'c': 3}) + self.assertEqual( + '(a:person{age:33,name:"John Doe",someArray:[1, 2, 3]})' + '-[{a:"b",c:3}]->' + '(b:country{name:"Japan"})', + str(edge_only_props) + ) + + def test_comparision(self): + node1 = node.Node(node_id=1) + node2 = node.Node(node_id=2) + node3 = node.Node(node_id=3) + + edge1 = edge.Edge(node1, None, node2) + self.assertEqual(edge1, edge.Edge(node1, None, node2)) + self.assertNotEqual(edge1, edge.Edge(node1, "bla", node2)) + self.assertNotEqual(edge1, edge.Edge(node1, None, node3)) + self.assertNotEqual(edge1, edge.Edge(node3, None, node2)) + self.assertNotEqual(edge1, edge.Edge(node2, None, node1)) + self.assertNotEqual(edge1, edge.Edge(node1, None, node2, + properties={"a": 10})) diff --git a/tests/unit/test_graph.py b/tests/unit/test_graph.py new file mode 100644 index 0000000..1ccf0d4 --- /dev/null +++ b/tests/unit/test_graph.py @@ -0,0 +1,5 @@ +from tests.utils import base + + +class TestGraph(base.TestCase): + pass diff --git a/tests/unit/test_node.py b/tests/unit/test_node.py new file mode 100644 index 0000000..6b46064 --- /dev/null +++ b/tests/unit/test_node.py @@ -0,0 +1,52 @@ +from redisgraph import node +from tests.utils import base + + +class TestNode(base.TestCase): + + def setUp(self): + super().setUp() + self.no_args = node.Node() + self.no_props = node.Node(node_id=1, alias="alias", label="l") + self.props_only = node.Node(properties={"a": "a", "b": 10}) + self.no_label = node.Node(node_id=1, alias="alias", + properties={"a": "a"}) + + def test_toString(self): + self.assertEqual(self.no_args.toString(), "") + self.assertEqual(self.no_props.toString(), "") + self.assertEqual(self.props_only.toString(), '{a:"a",b:10}') + self.assertEqual(self.no_label.toString(), '{a:"a"}') + + def test_stringify(self): + self.assertEqual(str(self.no_args), "()") + self.assertEqual(str(self.no_props), "(alias:l)") + self.assertEqual(str(self.props_only), '({a:"a",b:10})') + self.assertEqual(str(self.no_label), '(alias{a:"a"})') + + def test_comparision(self): + + self.assertEqual(node.Node(), node.Node()) + self.assertEqual(node.Node(node_id=1), node.Node(node_id=1)) + self.assertNotEqual(node.Node(node_id=1), node.Node(node_id=2)) + + self.assertEqual(node.Node(node_id=1, alias="a"), + node.Node(node_id=1, alias="b")) + + self.assertEqual(node.Node(node_id=1, alias="a"), + node.Node(node_id=1, alias="a")) + + self.assertEqual(node.Node(node_id=1, label="a"), + node.Node(node_id=1, label="a")) + self.assertNotEqual(node.Node(node_id=1, label="a"), + node.Node(node_id=1, label="b")) + + self.assertEqual(node.Node(node_id=1, alias="a", label="l"), + node.Node(node_id=1, alias="a", label="l")) + + self.assertNotEqual(node.Node(alias="a", label="l"), + node.Node(alias="a", label="l1")) + + self.assertEqual(node.Node(properties={"a": 10}), + node.Node(properties={"a": 10})) + self.assertNotEqual(node.Node(), node.Node(properties={"a": 10})) diff --git a/tests/unit/test_path.py b/tests/unit/test_path.py new file mode 100644 index 0000000..33d8f22 --- /dev/null +++ b/tests/unit/test_path.py @@ -0,0 +1,90 @@ +from redisgraph import edge +from redisgraph import node +from redisgraph import path +from tests.utils import base + + +class TestPath(base.TestCase): + + def test_init(self): + with self.assertRaises(TypeError): + path.Path(None, None) + + with self.assertRaises(TypeError): + path.Path([], None) + + with self.assertRaises(TypeError): + path.Path(None, []) + + self.assertIsInstance(path.Path([], []), path.Path) + + def test_new_empty_path(self): + new_empty_path = path.Path.new_empty_path() + self.assertIsInstance(new_empty_path, path.Path) + self.assertEqual(new_empty_path._nodes, []) + self.assertEqual(new_empty_path._edges, []) + + def test_wrong_flows(self): + node_1 = node.Node(node_id=1) + node_2 = node.Node(node_id=2) + node_3 = node.Node(node_id=3) + + edge_1 = edge.Edge(node_1, None, node_2) + edge_2 = edge.Edge(node_1, None, node_3) + + p = path.Path.new_empty_path() + with self.assertRaises(AssertionError): + p.add_edge(edge_1) + + p.add_node(node_1) + with self.assertRaises(AssertionError): + p.add_node(node_2) + + p.add_edge(edge_1) + with self.assertRaises(AssertionError): + p.add_edge(edge_2) + + def test_nodes_and_edges(self): + node_1 = node.Node(node_id=1) + node_2 = node.Node(node_id=2) + edge_1 = edge.Edge(node_1, None, node_2) + + p = path.Path.new_empty_path() + self.assertEqual(p.nodes(), []) + p.add_node(node_1) + self.assertEqual([], p.edges()) + self.assertEqual(0, p.edge_count()) + self.assertEqual([node_1], p.nodes()) + self.assertEqual(node_1, p.get_node(0)) + self.assertEqual(node_1, p.first_node()) + self.assertEqual(node_1, p.last_node()) + self.assertEqual(1, p.nodes_count()) + p.add_edge(edge_1) + self.assertEqual([edge_1], p.edges()) + self.assertEqual(1, p.edge_count()) + self.assertEqual(edge_1, p.get_relationship(0)) + p.add_node(node_2) + self.assertEqual([node_1, node_2], p.nodes()) + self.assertEqual(node_1, p.first_node()) + self.assertEqual(node_2, p.last_node()) + self.assertEqual(2, p.nodes_count()) + + def test_compare(self): + node_1 = node.Node(node_id=1) + node_2 = node.Node(node_id=2) + edge_1 = edge.Edge(node_1, None, node_2) + + self.assertEqual(path.Path.new_empty_path(), + path.Path.new_empty_path()) + self.assertEqual(path.Path(nodes=[node_1, node_2], edges=[edge_1]), + path.Path(nodes=[node_1, node_2], edges=[edge_1])) + self.assertNotEqual(path.Path(nodes=[node_1], edges=[]), + path.Path(nodes=[], edges=[])) + self.assertNotEqual(path.Path(nodes=[node_1], edges=[]), + path.Path(nodes=[], edges=[])) + self.assertNotEqual(path.Path(nodes=[node_1], edges=[]), + path.Path(nodes=[node_2], edges=[])) + self.assertNotEqual(path.Path(nodes=[node_1], edges=[edge_1]), + path.Path(nodes=[node_1], edges=[])) + self.assertNotEqual(path.Path(nodes=[node_1], edges=[edge_1]), + path.Path(nodes=[node_2], edges=[edge_1])) diff --git a/tests/unit/test_query_result.py b/tests/unit/test_query_result.py new file mode 100644 index 0000000..aac96ac --- /dev/null +++ b/tests/unit/test_query_result.py @@ -0,0 +1,5 @@ +from tests.utils import base + + +class TestQueryResult(base.TestCase): + pass diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py new file mode 100644 index 0000000..e3b1ede --- /dev/null +++ b/tests/unit/test_util.py @@ -0,0 +1,17 @@ + +from redisgraph import util + +from tests.utils import base + + +class TestUtils(base.TestCase): + + def test_random_string(self): + self.assertEqual(10, len(util.random_string())) + self.assertEqual(5, len(util.random_string(length=5))) + + def test_quote_string(self): + self.assertEqual(util.quote_string(10), 10) + self.assertEqual(util.quote_string("abc"), '"abc"') + self.assertEqual(util.quote_string(""), '""') + self.assertEqual(util.quote_string('a"a'), '"a\\"a"') diff --git a/tests/utils/base.py b/tests/utils/base.py new file mode 100644 index 0000000..0afb3a3 --- /dev/null +++ b/tests/utils/base.py @@ -0,0 +1,18 @@ +import unittest + +import testtools +import mock + + +class TestCase(testtools.TestCase): + + def setUp(self): + super(TestCase, self).setUp() + # NOTE(boris-42): Show all differences in complex objects + self.maxDiff = None + # NOTE(boris-42): Stop all mocks, to avoid hanging tests + self.addCleanup(mock.patch.stopall) + + # NOTE(boris-42): testtools have old version of assertRaises + # which doesn't support usage with "with" context. + assertRaises = unittest.TestCase.assertRaises diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..bbe2c2c --- /dev/null +++ b/tox.ini @@ -0,0 +1,50 @@ +[tox] +minversion = 1.6 +skipsdist = True +envlist = py3,pep8,cover,func + +[testenv] +setenv = VIRTUAL_ENV={envdir} + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C + PYTHONHASHSEED=0 + TOX_ENV_NAME={envname} +whitelist_externals = find +deps = -r{toxinidir}/test-requirements.txt +install_command = pip install -U {opts} {packages} +usedevelop = True +commands_pre = + find . -type f -name "*.pyc" -delete +commands = + py.test -v --html={envdir}/report/index.html --durations=10 {posargs:tests/unit} +distribute = false +basepython = python3 + +[testenv:pep8] +commands = + flake8 --show-source + +[testenv:py37] +basepython = python3.7 + +[testenv:py38] +basepython = python3.8 + +[testenv:cover] +commands = py.test --cov=redisgraph tests/unit/ --cov-report=html:{envdir}/report/ + +[testenv:func] +commands = py.test --cov=redisgraph tests/functional/ --cov-report=html:{envdir}/report/ + +[testenv:venv] +commands = {posargs} + +[flake8] +show-source = true +# TODO(boris-42): Enable E226 and E501 rules +ignore = H102,W503,W504,E241,E126,E226,E501 +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build,setup.py + +[pytest] +addopts = -p no:warnings