From ea5b22f3d8d7f1d86134763ccedf2c73ac41a527 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 23 Oct 2018 15:21:03 -0400 Subject: [PATCH] WIP: Refactor conformance tests for firestore. See #6290. Use 'pytest.mark.parametrize' to create a testcase per textproto file. Note that a *bunch* of them fail. :( --- firestore/tests/unit/test_cross_language.py | 266 ++++++++++++-------- 1 file changed, 159 insertions(+), 107 deletions(-) diff --git a/firestore/tests/unit/test_cross_language.py b/firestore/tests/unit/test_cross_language.py index b83f717de538..75aef04a3407 100644 --- a/firestore/tests/unit/test_cross_language.py +++ b/firestore/tests/unit/test_cross_language.py @@ -19,117 +19,169 @@ import unittest import mock -from google.cloud.firestore_v1beta1.proto import test_pb2 +import pytest + from google.protobuf import text_format +from google.cloud.firestore_v1beta1.proto import firestore_pb2 +from google.cloud.firestore_v1beta1.proto import test_pb2 +from google.cloud.firestore_v1beta1.proto import write_pb2 + + +def _load_testproto(filename): + + with open(filename, 'r') as tp_file: + tp_text = tp_file.read() + test_proto = test_pb2.Test() + text_format.Merge(tp_text, test_proto) + return test_proto + + +_ALL_TESTPROTOS = [ + _load_testproto(filename) for filename in sorted( + glob.glob('tests/unit/testdata/*.textproto'))] + +_CREATE_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'create'] + +_GET_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'get'] + +_SET_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'set'] + +_UPDATE_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'update'] + +_UPDATE_PATHS_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'update_paths'] + +_DELETE_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'delete'] + +_LISTEN_TESTPROTOS = [ + test_proto for test_proto in _ALL_TESTPROTOS + if test_proto.WhichOneof('test') == 'listen'] + +def _mock_firestore_api(): + firestore_api = mock.Mock(spec=['commit']) + commit_response = firestore_pb2.CommitResponse( + write_results=[write_pb2.WriteResult()], + ) + firestore_api.commit.return_value = commit_response + return firestore_api -class TestCrossLanguage(unittest.TestCase): - - def test_cross_language(self): - filenames = sorted(glob.glob('tests/unit/testdata/*.textproto')) - failed = 0 - descs = [] - for test_filename in filenames: - bytes = open(test_filename, 'r').read() - test_proto = test_pb2.Test() - text_format.Merge(bytes, test_proto) - desc = '%s (%s)' % ( - test_proto.description, - os.path.splitext(os.path.basename(test_filename))[0]) - try: - self.run_write_test(test_proto, desc) - except Exception as error: - failed += 1 - # print(desc, test_proto) # for debugging - # print(error.args[0]) # for debugging - descs.append(desc) - # for desc in descs: # for debugging - # print(desc) # for debugging - # print(str(failed) + "/" + str(len(filenames))) # for debugging - - def run_write_test(self, test_proto, desc): - from google.cloud.firestore_v1beta1.proto import firestore_pb2 - from google.cloud.firestore_v1beta1.proto import write_pb2 - - # Create a minimal fake GAPIC with a dummy result. - firestore_api = mock.Mock(spec=['commit']) - commit_response = firestore_pb2.CommitResponse( - write_results=[write_pb2.WriteResult()], - ) - firestore_api.commit.return_value = commit_response - - kind = test_proto.WhichOneof("test") - call = None - if kind == "create": - tp = test_proto.create - client, doc = self.setup(firestore_api, tp) - data = convert_data(json.loads(tp.json_data)) - call = functools.partial(doc.create, data) - elif kind == "get": - tp = test_proto.get - client, doc = self.setup(firestore_api, tp) - call = functools.partial(doc.get, None, None) - try: - tp.is_error - except AttributeError: - return - elif kind == "set": - tp = test_proto.set - client, doc = self.setup(firestore_api, tp) - data = convert_data(json.loads(tp.json_data)) - if tp.HasField("option"): - merge = True - else: - merge = False - call = functools.partial(doc.set, data, merge) - elif kind == "update": - tp = test_proto.update - client, doc = self.setup(firestore_api, tp) - data = convert_data(json.loads(tp.json_data)) - if tp.HasField("precondition"): - option = convert_precondition(tp.precondition) - else: - option = None - call = functools.partial(doc.update, data, option) - elif kind == "update_paths": - # Python client doesn't have a way to call update with - # a list of field paths. - return - else: - assert kind == "delete" - tp = test_proto.delete - client, doc = self.setup(firestore_api, tp) - if tp.HasField("precondition"): - option = convert_precondition(tp.precondition) - else: - option = None - call = functools.partial(doc.delete, option) - - if tp.is_error: - # TODO: is there a subclass of Exception we can check for? - with self.assertRaises(Exception): - call() - else: + +def _make_client_document(firestore_api, testcase): + from google.cloud.firestore_v1beta1 import Client + from google.cloud.firestore_v1beta1.client import DEFAULT_DATABASE + import google.auth.credentials + + _, project, _, database, _, doc_path = testcase.doc_ref_path.split('/', 5) + assert database == DEFAULT_DATABASE + + # Attach the fake GAPIC to a real client. + credentials = mock.Mock(spec=google.auth.credentials.Credentials) + client = Client(project=project, credentials=credentials) + client._firestore_api_internal = firestore_api + return client, client.document(doc_path) + + +def _run_testcase(testcase, call, firestore_api, client): + if testcase.is_error: + # TODO: is there a subclass of Exception we can check for? + with pytest.raises(Exception): call() - firestore_api.commit.assert_called_once_with( - client._database_string, - list(tp.request.writes), - transaction=None, - metadata=client._rpc_metadata) - - def setup(self, firestore_api, proto): - from google.cloud.firestore_v1beta1 import Client - from google.cloud.firestore_v1beta1.client import DEFAULT_DATABASE - import google.auth.credentials - - _, project, _, database, _, doc_path = proto.doc_ref_path.split('/', 5) - self.assertEqual(database, DEFAULT_DATABASE) - - # Attach the fake GAPIC to a real client. - credentials = mock.Mock(spec=google.auth.credentials.Credentials) - client = Client(project=project, credentials=credentials) - client._firestore_api_internal = firestore_api - return client, client.document(doc_path) + else: + call() + firestore_api.commit.assert_called_once_with( + client._database_string, + list(testcase.request.writes), + transaction=None, + metadata=client._rpc_metadata) + + +@pytest.mark.parametrize('test_proto', _CREATE_TESTPROTOS) +def test_create_testprotos(test_proto): + testcase = test_proto.create + firestore_api = _mock_firestore_api() + client, document = _make_client_document(firestore_api, testcase) + data = convert_data(json.loads(testcase.json_data)) + call = functools.partial(document.create, data) + _run_testcase(testcase, call, firestore_api, client) + + +@pytest.mark.parametrize('test_proto', _GET_TESTPROTOS) +def test_get_testprotos(test_proto): + testcase = test_proto.get + try: + testcase.is_error + except AttributeError: + return + firestore_api = _mock_firestore_api() + client, document = _make_client_document(firestore_api, testcase) + call = functools.partial(document.get, None, None) + _run_testcase(testcase, call, firestore_api, client) + + +@pytest.mark.parametrize('test_proto', _SET_TESTPROTOS) +def test_set_testprotos(test_proto): + testcase = test_proto.set + firestore_api = _mock_firestore_api() + client, document = _make_client_document(firestore_api, testcase) + data = convert_data(json.loads(testcase.json_data)) + if testcase.HasField("option"): + merge = True + else: + merge = False + call = functools.partial(document.set, data, merge) + _run_testcase(testcase, call, firestore_api, client) + + +@pytest.mark.parametrize('test_proto', _UPDATE_TESTPROTOS) +def test_update_testprotos(test_proto): + testcase = test_proto.update + firestore_api = _mock_firestore_api() + client, document = _make_client_document(firestore_api, testcase) + data = convert_data(json.loads(testcase.json_data)) + if testcase.HasField("precondition"): + option = convert_precondition(testcase.precondition) + else: + option = None + call = functools.partial(document.update, data, option) + _run_testcase(testcase, call, firestore_api, client) + + +@pytest.mark.skip( + reason="Python has no way to call update with a list of field paths.") +@pytest.mark.parametrize('test_proto', _UPDATE_PATHS_TESTPROTOS) +def test_update_paths_testprotos(test_proto): + pass + + +@pytest.mark.parametrize('test_proto', _DELETE_TESTPROTOS) +def test_delete_testprotos(test_proto): + testcase = test_proto.delete + firestore_api = _mock_firestore_api() + client, document = _make_client_document(firestore_api, testcase) + if testcase.HasField("precondition"): + option = convert_precondition(testcase.precondition) + else: + option = None + call = functools.partial(document.delete, option) + _run_testcase(testcase, call, firestore_api, client) + + +@pytest.mark.skip(reason="Watch aka listen not yet implemented in Python.") +@pytest.mark.parametrize('test_proto', _LISTEN_TESTPROTOS) +def test_listen_paths_testprotos(test_proto): + pass def convert_data(v):