Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL generation scripts for snapshot tests DB #105

Merged
merged 20 commits into from Jul 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions .coveragerc
@@ -1,6 +1,13 @@
[run]
# We don't want to include the tests themselves in the coverage report
omit = graphql_compiler/tests/*
omit =
# No coverage for tests
graphql_compiler/tests/*

# No coverage for SQL command generation script
scripts/generate_test_sql/*

# No coverage for snapshots.
**/snap_*.py

[report]
# Regexes for lines to exclude from consideration
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Expand Up @@ -43,3 +43,8 @@ tools/sphinx_docgen/docs_html
# Python package publishing directories
build/
dist/

# vim and swap files
*.vim
*.swp
*.swo
7 changes: 6 additions & 1 deletion .travis.yml
Expand Up @@ -4,16 +4,21 @@ cache: pip
python:
- "2.7"
- "3.6"
services:
- docker
install:
- pip install -r dev-requirements.txt
- pip install -e .
before_script:
- docker-compose up -d
script:
- ./scripts/copyright_line_check.sh
- isort --check-only --verbose --recursive graphql_compiler/
- flake8 graphql_compiler/
- flake8 --exclude **/snap_*.py graphql_compiler/
- pydocstyle graphql_compiler/
- pylint graphql_compiler/
- bandit -r graphql_compiler/
- py.test --cov=graphql_compiler graphql_compiler/tests
after_success:
- docker-compose down
- coveralls
2 changes: 2 additions & 0 deletions dev-requirements.txt
Expand Up @@ -4,5 +4,7 @@ flake8==3.5.0
isort==4.3.4
pydocstyle==2.1.1
pylint==1.8.2
pyorient==1.5.5
pytest==3.4.1
pytest-cov==2.5.1
snapshottest==0.5.0
10 changes: 10 additions & 0 deletions docker-compose.yml
@@ -0,0 +1,10 @@
version: '3'
services:
orientdb:
image: orientdb:2.2.30
command: server.sh
ports:
- "127.0.0.1:2480:2480"
- "127.0.0.1:2424:2424"
environment:
ORIENTDB_ROOT_PASSWORD: root
40 changes: 40 additions & 0 deletions graphql_compiler/tests/conftest.py
@@ -0,0 +1,40 @@
# Copyright 2018-present Kensho Technologies, LLC.
import sys
import time

import pytest

from .test_data_tools.graph import get_test_graph


# Pytest fixtures depend on name redefinitions to work,
# so this check generates tons of false-positives here.
# pylint: disable=redefined-outer-name


@pytest.fixture(scope='session')
def init_graph():
"""Return a client for an initialized db, with all test data imported."""
graph_name = 'animals'

# Try to set up the database for the test up to 20 times before giving up.
set_up_successfully = False
for _ in range(20):
try:
graph_client = get_test_graph(graph_name)
set_up_successfully = True
break
except Exception as e: # pylint: disable=broad-except
sys.stderr.write(u'Failed to set up test DB: {}'.format(e))
time.sleep(1)

if not set_up_successfully:
raise AssertionError(u'Failed to set up database without raising an exception!')

return graph_client


@pytest.fixture(scope='class')
def graph_client(request, init_graph):
"""Get a client for an initialized db, with all test data imported."""
request.cls.graph_client = init_graph
1 change: 1 addition & 0 deletions graphql_compiler/tests/snapshot_tests/__init__.py
@@ -0,0 +1 @@
# Copyright 2018-present Kensho Technologies, LLC.
@@ -0,0 +1 @@
# Copyright 2018-present Kensho Technologies, LLC.

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions graphql_compiler/tests/snapshot_tests/test_orientdb_match_query.py
@@ -0,0 +1,125 @@
# Copyright 2018-present Kensho Technologies, LLC.
from collections import Counter

import pytest
import six
from snapshottest import TestCase

from .. import test_input_data
from ... import graphql_to_match
from ..test_helpers import get_schema


def execute_graphql(schema, test_data, client, sample_parameters):
"""Compile the GraphQL query to MATCH, execute it agains the test_db, and return the results."""
if test_data.type_equivalence_hints:
# For test convenience, we accept the type equivalence hints in string form.
# Here, we convert them to the required GraphQL types.
schema_based_type_equivalence_hints = {
schema.get_type(key): schema.get_type(value)
for key, value in six.iteritems(test_data.type_equivalence_hints)
}
else:
schema_based_type_equivalence_hints = None

result = graphql_to_match(schema, test_data.graphql_input, sample_parameters,
type_equivalence_hints=schema_based_type_equivalence_hints)

# We need to preprocess the results to be agnostic of the returned order.
# For this we perform the following steps
# - convert lists (returned from @fold scopes) to tuples to make them hashable
# - convert each row dict to a frozenset of its items
# - create a Counter (multi-set) of the row frozensets
# - convert the multi-set to a frozenset of its items
row_dicts = [row.oRecordData for row in client.command(result.query)]
row_dicts_using_tuples = [
{
column_name: tuple(value) if isinstance(value, list) else value
for column_name, value in row.items()
}
for row in row_dicts
]
row_frozensets = [frozenset(row_dict.items()) for row_dict in row_dicts_using_tuples]
rows_multiset = Counter(row_frozensets)
row_counters_frozenset = frozenset(rows_multiset.items())

return row_counters_frozenset


# The following TestCase class uses the 'graph_client' fixture
# which pylint does not recognize as a class member.
# pylint: disable=no-member


class OrientDBMatchQueryTests(TestCase):

def setUp(self):
"""Initialize the test schema once for all tests, and disable max diff limits."""
self.maxDiff = None
self.schema = get_schema()

@pytest.mark.usefixtures('graph_client')
def test_immediate_output(self):
test_data = test_input_data.immediate_output()
sample_parameters = {}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)

@pytest.mark.usefixtures('graph_client')
def test_traverse_and_output(self):
test_data = test_input_data.traverse_and_output()
sample_parameters = {}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)

@pytest.mark.usefixtures('graph_client')
def test_traverse_filter_and_output(self):
test_data = test_input_data.traverse_filter_and_output()
sample_parameters = {'wanted': 'Nazgul__2'}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)

@pytest.mark.usefixtures('graph_client')
def test_filter_in_optional_block(self):
test_data = test_input_data.filter_in_optional_block()
sample_parameters = {'name': 'Nazgul__2'}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)

@pytest.mark.usefixtures('graph_client')
def test_optional_traverse_after_mandatory_traverse(self):
test_data = test_input_data.optional_traverse_after_mandatory_traverse()
sample_parameters = {}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)

@pytest.mark.usefixtures('graph_client')
def test_optional_and_deep_traverse(self):
test_data = test_input_data.optional_and_deep_traverse()
sample_parameters = {}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)

@pytest.mark.usefixtures('graph_client')
def test_fold_on_output_variable(self):
test_data = test_input_data.fold_on_output_variable()
sample_parameters = {}

rows = execute_graphql(self.schema, test_data, self.graph_client, sample_parameters)

self.assertMatchSnapshot(rows)


# pylint: enable=no-member
28 changes: 17 additions & 11 deletions graphql_compiler/tests/test_compiler.py
Expand Up @@ -501,40 +501,46 @@ def test_filter_in_optional_block(self):

expected_match = '''
SELECT
if(eval("(Animal__out_Animal_FedAt___1 IS NOT null)"),
Animal__out_Animal_FedAt___1.uuid, null) AS `uuid`
Animal___1.name AS `animal_name`,
if(eval("(Animal__out_Animal_ParentOf___1 IS NOT null)"),
Animal__out_Animal_ParentOf___1.name, null) AS `parent_name`,
if(eval("(Animal__out_Animal_ParentOf___1 IS NOT null)"),
Animal__out_Animal_ParentOf___1.uuid, null) AS `uuid`
FROM (
MATCH {{
class: Animal,
as: Animal___1
}}.out('Animal_FedAt') {{
}}.out('Animal_ParentOf') {{
where: ((name = {name})),
optional: true,
as: Animal__out_Animal_FedAt___1
as: Animal__out_Animal_ParentOf___1
}}
RETURN $matches
)
WHERE (
(
(Animal___1.out_Animal_FedAt IS null)
(Animal___1.out_Animal_ParentOf IS null)
OR
(Animal___1.out_Animal_FedAt.size() = 0)
(Animal___1.out_Animal_ParentOf.size() = 0)
)
OR
(Animal__out_Animal_FedAt___1 IS NOT null)
(Animal__out_Animal_ParentOf___1 IS NOT null)
)
'''
expected_gremlin = '''
g.V('@class', 'Animal')
.as('Animal___1')
.ifThenElse{it.out_Animal_FedAt == null}{null}{it.out('Animal_FedAt')}
.ifThenElse{it.out_Animal_ParentOf == null}{null}{it.out('Animal_ParentOf')}
.filter{it, m -> ((it == null) || (it.name == $name))}
.as('Animal__out_Animal_FedAt___1')
.as('Animal__out_Animal_ParentOf___1')
.optional('Animal___1')
.as('Animal___2')
.transform{it, m -> new com.orientechnologies.orient.core.record.impl.ODocument([
uuid: ((m.Animal__out_Animal_FedAt___1 != null) ?
m.Animal__out_Animal_FedAt___1.uuid : null)
animal_name: m.Animal___1.name,
parent_name: ((m.Animal__out_Animal_ParentOf___1 != null) ?
m.Animal__out_Animal_ParentOf___1.name : null),
uuid: ((m.Animal__out_Animal_ParentOf___1 != null) ?
m.Animal__out_Animal_ParentOf___1.uuid : null)
])}
'''

Expand Down
1 change: 1 addition & 0 deletions graphql_compiler/tests/test_data_tools/__init__.py
@@ -0,0 +1 @@
# Copyright 2018-present Kensho Technologies, LLC.