# Copyright 2016 Google LLC All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import unittest

import mock


class TestStreamedResultSet(unittest.TestCase):

    def _getTargetClass(self):
        from google.cloud.spanner_v1.streamed import StreamedResultSet

        return StreamedResultSet

    def _make_one(self, *args, **kwargs):
        return self._getTargetClass()(*args, **kwargs)

    def test_ctor_defaults(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        self.assertIs(streamed._response_iterator, iterator)
        self.assertIsNone(streamed._source)
        self.assertEqual(list(streamed), [])
        self.assertIsNone(streamed.metadata)
        self.assertIsNone(streamed.stats)

    def test_ctor_w_source(self):
        iterator = _MockCancellableIterator()
        source = object()
        streamed = self._make_one(iterator, source=source)
        self.assertIs(streamed._response_iterator, iterator)
        self.assertIs(streamed._source, source)
        self.assertEqual(list(streamed), [])
        self.assertIsNone(streamed.metadata)
        self.assertIsNone(streamed.stats)

    def test_fields_unset(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        with self.assertRaises(AttributeError):
            streamed.fields

    @staticmethod
    def _make_scalar_field(name, type_):
        from google.cloud.spanner_v1.proto.type_pb2 import StructType
        from google.cloud.spanner_v1.proto.type_pb2 import Type

        return StructType.Field(name=name, type=Type(code=type_))

    @staticmethod
    def _make_array_field(name, element_type_code=None, element_type=None):
        from google.cloud.spanner_v1.proto.type_pb2 import StructType
        from google.cloud.spanner_v1.proto.type_pb2 import Type

        if element_type is None:
            element_type = Type(code=element_type_code)
        array_type = Type(
            code='ARRAY', array_element_type=element_type)
        return StructType.Field(name=name, type=array_type)

    @staticmethod
    def _make_struct_type(struct_type_fields):
        from google.cloud.spanner_v1.proto.type_pb2 import StructType
        from google.cloud.spanner_v1.proto.type_pb2 import Type

        fields = [
            StructType.Field(name=key, type=Type(code=value))
            for key, value in struct_type_fields
        ]
        struct_type = StructType(fields=fields)
        return Type(code='STRUCT', struct_type=struct_type)

    @staticmethod
    def _make_value(value):
        from google.cloud.spanner_v1._helpers import _make_value_pb

        return _make_value_pb(value)

    @staticmethod
    def _make_list_value(values=(), value_pbs=None):
        from google.protobuf.struct_pb2 import ListValue
        from google.protobuf.struct_pb2 import Value
        from google.cloud.spanner_v1._helpers import _make_list_value_pb

        if value_pbs is not None:
            return Value(list_value=ListValue(values=value_pbs))
        return Value(list_value=_make_list_value_pb(values))

    @staticmethod
    def _make_result_set_metadata(fields=(), transaction_id=None):
        from google.cloud.spanner_v1.proto.result_set_pb2 import (
            ResultSetMetadata)
        metadata = ResultSetMetadata()
        for field in fields:
            metadata.row_type.fields.add().CopyFrom(field)
        if transaction_id is not None:
            metadata.transaction.id = transaction_id
        return metadata

    @staticmethod
    def _make_result_set_stats(query_plan=None, **kw):
        from google.cloud.spanner_v1.proto.result_set_pb2 import (
            ResultSetStats)
        from google.protobuf.struct_pb2 import Struct
        from google.cloud.spanner_v1._helpers import _make_value_pb

        query_stats = Struct(fields={
            key: _make_value_pb(value) for key, value in kw.items()})
        return ResultSetStats(
            query_plan=query_plan,
            query_stats=query_stats,
        )

    @staticmethod
    def _make_partial_result_set(
            values, metadata=None, stats=None, chunked_value=False):
        from google.cloud.spanner_v1.proto.result_set_pb2 import (
            PartialResultSet)
        return PartialResultSet(
            values=values,
            metadata=metadata,
            stats=stats,
            chunked_value=chunked_value,
        )

    def test_properties_set(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
        ]
        metadata = streamed._metadata = self._make_result_set_metadata(FIELDS)
        stats = streamed._stats = self._make_result_set_stats()
        self.assertEqual(list(streamed.fields), FIELDS)
        self.assertIs(streamed.metadata, metadata)
        self.assertIs(streamed.stats, stats)

    def test__merge_chunk_bool(self):
        from google.cloud.spanner_v1.streamed import Unmergeable

        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('registered_voter', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(True)
        chunk = self._make_value(False)

        with self.assertRaises(Unmergeable):
            streamed._merge_chunk(chunk)

    def test__merge_chunk_int64(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('age', 'INT64'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(42)
        chunk = self._make_value(13)

        merged = streamed._merge_chunk(chunk)
        self.assertEqual(merged.string_value, '4213')
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_float64_nan_string(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('weight', 'FLOAT64'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(u'Na')
        chunk = self._make_value(u'N')

        merged = streamed._merge_chunk(chunk)
        self.assertEqual(merged.string_value, u'NaN')

    def test__merge_chunk_float64_w_empty(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('weight', 'FLOAT64'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(3.14159)
        chunk = self._make_value('')

        merged = streamed._merge_chunk(chunk)
        self.assertEqual(merged.number_value, 3.14159)

    def test__merge_chunk_float64_w_float64(self):
        from google.cloud.spanner_v1.streamed import Unmergeable

        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('weight', 'FLOAT64'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(3.14159)
        chunk = self._make_value(2.71828)

        with self.assertRaises(Unmergeable):
            streamed._merge_chunk(chunk)

    def test__merge_chunk_string(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('name', 'STRING'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(u'phred')
        chunk = self._make_value(u'wylma')

        merged = streamed._merge_chunk(chunk)

        self.assertEqual(merged.string_value, u'phredwylma')
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_string_w_bytes(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('image', 'BYTES'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(
            u'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA'
            u'6fptVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\n',
        )
        chunk = self._make_value(
            u'B3RJTUUH4QQGFwsBTL3HMwAAABJpVFh0Q29tbWVudAAAAAAAU0FNUExF'
            u'MG3E+AAAAApJREFUCNdj\nYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\n',
        )

        merged = streamed._merge_chunk(chunk)

        self.assertEqual(
            merged.string_value,
            u'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAsTAAAL'
            u'EwEAmpwYAAAA\nB3RJTUUH4QQGFwsBTL3HMwAAABJpVFh0Q29tbWVudAAAAAAAU0'
            u'FNUExFMG3E+AAAAApJREFUCNdj\nYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\n',
        )
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_bool(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_array_field('name', element_type_code='BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value([True, True])
        chunk = self._make_list_value([False, False, False])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value([True, True, False, False, False])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_int(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_array_field('name', element_type_code='INT64'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value([0, 1, 2])
        chunk = self._make_list_value([3, 4, 5])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value([0, 1, 23, 4, 5])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_float(self):
        import math

        PI = math.pi
        EULER = math.e
        SQRT_2 = math.sqrt(2.0)
        LOG_10 = math.log(10)
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_array_field('name', element_type_code='FLOAT64'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value([PI, SQRT_2])
        chunk = self._make_list_value(['', EULER, LOG_10])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value([PI, SQRT_2, EULER, LOG_10])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_string_with_empty(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_array_field('name', element_type_code='STRING'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value([u'A', u'B', u'C'])
        chunk = self._make_list_value([])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value([u'A', u'B', u'C'])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_string(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_array_field('name', element_type_code='STRING'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value([u'A', u'B', u'C'])
        chunk = self._make_list_value([None, u'D', u'E'])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value([u'A', u'B', u'C', None, u'D', u'E'])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_string_with_null(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_array_field('name', element_type_code='STRING'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value([u'A', u'B', u'C'])
        chunk = self._make_list_value([u'D', u'E'])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value([u'A', u'B', u'CD', u'E'])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_array_of_int(self):
        from google.cloud.spanner_v1.proto.type_pb2 import StructType
        from google.cloud.spanner_v1.proto.type_pb2 import Type

        subarray_type = Type(
            code='ARRAY', array_element_type=Type(code='INT64'))
        array_type = Type(code='ARRAY', array_element_type=subarray_type)
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            StructType.Field(name='loloi', type=array_type)
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value(value_pbs=[
            self._make_list_value([0, 1]),
            self._make_list_value([2]),
        ])
        chunk = self._make_list_value(value_pbs=[
            self._make_list_value([3]),
            self._make_list_value([4, 5]),
        ])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value(value_pbs=[
            self._make_list_value([0, 1]),
            self._make_list_value([23]),
            self._make_list_value([4, 5]),
        ])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_array_of_string(self):
        from google.cloud.spanner_v1.proto.type_pb2 import StructType
        from google.cloud.spanner_v1.proto.type_pb2 import Type

        subarray_type = Type(
            code='ARRAY', array_element_type=Type(code='STRING'))
        array_type = Type(code='ARRAY', array_element_type=subarray_type)
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            StructType.Field(name='lolos', type=array_type)
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_list_value(value_pbs=[
            self._make_list_value([u'A', u'B']),
            self._make_list_value([u'C']),
        ])
        chunk = self._make_list_value(value_pbs=[
            self._make_list_value([u'D']),
            self._make_list_value([u'E', u'F']),
        ])

        merged = streamed._merge_chunk(chunk)

        expected = self._make_list_value(value_pbs=[
            self._make_list_value([u'A', u'B']),
            self._make_list_value([u'CD']),
            self._make_list_value([u'E', u'F']),
        ])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_struct(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        struct_type = self._make_struct_type([
            ('name', 'STRING'),
            ('age', 'INT64'),
        ])
        FIELDS = [
            self._make_array_field('test', element_type=struct_type),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        partial = self._make_list_value([u'Phred '])
        streamed._pending_chunk = self._make_list_value(value_pbs=[partial])
        rest = self._make_list_value([u'Phlyntstone', 31])
        chunk = self._make_list_value(value_pbs=[rest])

        merged = streamed._merge_chunk(chunk)

        struct = self._make_list_value([u'Phred Phlyntstone', 31])
        expected = self._make_list_value(value_pbs=[struct])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test__merge_chunk_array_of_struct_unmergeable(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        struct_type = self._make_struct_type([
            ('name', 'STRING'),
            ('registered', 'BOOL'),
            ('voted', 'BOOL'),
        ])
        FIELDS = [
            self._make_array_field('test', element_type=struct_type),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        partial = self._make_list_value([u'Phred Phlyntstone', True])
        streamed._pending_chunk = self._make_list_value(value_pbs=[partial])
        rest = self._make_list_value([True])
        chunk = self._make_list_value(value_pbs=[rest])

        merged = streamed._merge_chunk(chunk)

        struct = self._make_list_value([u'Phred Phlyntstone', True, True])
        expected = self._make_list_value(value_pbs=[struct])
        self.assertEqual(merged, expected)
        self.assertIsNone(streamed._pending_chunk)

    def test_merge_values_empty_and_empty(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._current_row = []
        streamed._merge_values([])
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, [])

    def test_merge_values_empty_and_partial(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BARE = [u'Phred Phlyntstone', 42]
        VALUES = [self._make_value(bare) for bare in BARE]
        streamed._current_row = []
        streamed._merge_values(VALUES)
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, BARE)

    def test_merge_values_empty_and_filled(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BARE = [u'Phred Phlyntstone', 42, True]
        VALUES = [self._make_value(bare) for bare in BARE]
        streamed._current_row = []
        streamed._merge_values(VALUES)
        self.assertEqual(list(streamed), [BARE])
        self.assertEqual(streamed._current_row, [])

    def test_merge_values_empty_and_filled_plus(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BARE = [
            u'Phred Phlyntstone', 42, True,
            u'Bharney Rhubble', 39, True,
            u'Wylma Phlyntstone',
        ]
        VALUES = [self._make_value(bare) for bare in BARE]
        streamed._current_row = []
        streamed._merge_values(VALUES)
        self.assertEqual(list(streamed), [BARE[0:3], BARE[3:6]])
        self.assertEqual(streamed._current_row, BARE[6:])

    def test_merge_values_partial_and_empty(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BEFORE = [
            u'Phred Phlyntstone'
        ]
        streamed._current_row[:] = BEFORE
        streamed._merge_values([])
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, BEFORE)

    def test_merge_values_partial_and_partial(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BEFORE = [u'Phred Phlyntstone']
        streamed._current_row[:] = BEFORE
        MERGED = [42]
        TO_MERGE = [self._make_value(item) for item in MERGED]
        streamed._merge_values(TO_MERGE)
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, BEFORE + MERGED)

    def test_merge_values_partial_and_filled(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BEFORE = [
            u'Phred Phlyntstone'
        ]
        streamed._current_row[:] = BEFORE
        MERGED = [42, True]
        TO_MERGE = [self._make_value(item) for item in MERGED]
        streamed._merge_values(TO_MERGE)
        self.assertEqual(list(streamed), [BEFORE + MERGED])
        self.assertEqual(streamed._current_row, [])

    def test_merge_values_partial_and_filled_plus(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        BEFORE = [
            self._make_value(u'Phred Phlyntstone')
        ]
        streamed._current_row[:] = BEFORE
        MERGED = [
            42, True,
            u'Bharney Rhubble', 39, True,
            u'Wylma Phlyntstone',
        ]
        TO_MERGE = [self._make_value(item) for item in MERGED]
        VALUES = BEFORE + MERGED
        streamed._merge_values(TO_MERGE)
        self.assertEqual(list(streamed), [VALUES[0:3], VALUES[3:6]])
        self.assertEqual(streamed._current_row, VALUES[6:])

    def test_one_or_none_no_value(self):
        streamed = self._make_one(_MockCancellableIterator())
        with mock.patch.object(streamed, '_consume_next') as consume_next:
            consume_next.side_effect = StopIteration
            self.assertIsNone(streamed.one_or_none())

    def test_one_or_none_single_value(self):
        streamed = self._make_one(_MockCancellableIterator())
        streamed._rows = ['foo']
        with mock.patch.object(streamed, '_consume_next') as consume_next:
            consume_next.side_effect = StopIteration
            self.assertEqual(streamed.one_or_none(), 'foo')

    def test_one_or_none_multiple_values(self):
        streamed = self._make_one(_MockCancellableIterator())
        streamed._rows = ['foo', 'bar']
        with self.assertRaises(ValueError):
            streamed.one_or_none()

    def test_one_or_none_consumed_stream(self):
        streamed = self._make_one(_MockCancellableIterator())
        streamed._metadata = object()
        with self.assertRaises(RuntimeError):
            streamed.one_or_none()

    def test_one_single_value(self):
        streamed = self._make_one(_MockCancellableIterator())
        streamed._rows = ['foo']
        with mock.patch.object(streamed, '_consume_next') as consume_next:
            consume_next.side_effect = StopIteration
            self.assertEqual(streamed.one(), 'foo')

    def test_one_no_value(self):
        from google.cloud import exceptions

        iterator = _MockCancellableIterator(['foo'])
        streamed = self._make_one(iterator)
        with mock.patch.object(streamed, '_consume_next') as consume_next:
            consume_next.side_effect = StopIteration
            with self.assertRaises(exceptions.NotFound):
                streamed.one()

    def test_consume_next_empty(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        with self.assertRaises(StopIteration):
            streamed._consume_next()

    def test_consume_next_first_set_partial(self):
        TXN_ID = b'DEADBEEF'
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        metadata = self._make_result_set_metadata(
            FIELDS, transaction_id=TXN_ID)
        BARE = [u'Phred Phlyntstone', 42]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set = self._make_partial_result_set(VALUES, metadata=metadata)
        iterator = _MockCancellableIterator(result_set)
        source = mock.Mock(_transaction_id=None, spec=['_transaction_id'])
        streamed = self._make_one(iterator, source=source)
        streamed._consume_next()
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, BARE)
        self.assertEqual(streamed.metadata, metadata)
        self.assertEqual(source._transaction_id, TXN_ID)

    def test_consume_next_first_set_partial_existing_txn_id(self):
        TXN_ID = b'DEADBEEF'
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        metadata = self._make_result_set_metadata(
            FIELDS, transaction_id=b'')
        BARE = [u'Phred Phlyntstone', 42]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set = self._make_partial_result_set(VALUES, metadata=metadata)
        iterator = _MockCancellableIterator(result_set)
        source = mock.Mock(_transaction_id=TXN_ID, spec=['_transaction_id'])
        streamed = self._make_one(iterator, source=source)
        streamed._consume_next()
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, BARE)
        self.assertEqual(streamed.metadata, metadata)
        self.assertEqual(source._transaction_id, TXN_ID)

    def test_consume_next_w_partial_result(self):
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        VALUES = [
            self._make_value(u'Phred '),
        ]
        result_set = self._make_partial_result_set(VALUES, chunked_value=True)
        iterator = _MockCancellableIterator(result_set)
        streamed = self._make_one(iterator)
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._consume_next()
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, [])
        self.assertEqual(streamed._pending_chunk, VALUES[0])

    def test_consume_next_w_pending_chunk(self):
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        BARE = [
            u'Phlyntstone', 42, True,
            u'Bharney Rhubble', 39, True,
            u'Wylma Phlyntstone',
        ]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set = self._make_partial_result_set(VALUES)
        iterator = _MockCancellableIterator(result_set)
        streamed = self._make_one(iterator)
        streamed._metadata = self._make_result_set_metadata(FIELDS)
        streamed._pending_chunk = self._make_value(u'Phred ')
        streamed._consume_next()
        self.assertEqual(list(streamed), [
            [u'Phred Phlyntstone', BARE[1], BARE[2]],
            [BARE[3], BARE[4], BARE[5]],
        ])
        self.assertEqual(streamed._current_row, [BARE[6]])
        self.assertIsNone(streamed._pending_chunk)

    def test_consume_next_last_set(self):
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        metadata = self._make_result_set_metadata(FIELDS)
        stats = self._make_result_set_stats(
            rows_returned="1",
            elapsed_time="1.23 secs",
            cpu_time="0.98 secs",
        )
        BARE = [u'Phred Phlyntstone', 42, True]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set = self._make_partial_result_set(VALUES, stats=stats)
        iterator = _MockCancellableIterator(result_set)
        streamed = self._make_one(iterator)
        streamed._metadata = metadata
        streamed._consume_next()
        self.assertEqual(list(streamed), [BARE])
        self.assertEqual(streamed._current_row, [])
        self.assertEqual(streamed._stats, stats)

    def test___iter___empty(self):
        iterator = _MockCancellableIterator()
        streamed = self._make_one(iterator)
        found = list(streamed)
        self.assertEqual(found, [])

    def test___iter___one_result_set_partial(self):
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        metadata = self._make_result_set_metadata(FIELDS)
        BARE = [u'Phred Phlyntstone', 42]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set = self._make_partial_result_set(VALUES, metadata=metadata)
        iterator = _MockCancellableIterator(result_set)
        streamed = self._make_one(iterator)
        found = list(streamed)
        self.assertEqual(found, [])
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, BARE)
        self.assertEqual(streamed.metadata, metadata)

    def test___iter___multiple_result_sets_filled(self):
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        metadata = self._make_result_set_metadata(FIELDS)
        BARE = [
            u'Phred Phlyntstone', 42, True,
            u'Bharney Rhubble', 39, True,
            u'Wylma Phlyntstone', 41, True,
        ]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set1 = self._make_partial_result_set(
            VALUES[:4], metadata=metadata)
        result_set2 = self._make_partial_result_set(VALUES[4:])
        iterator = _MockCancellableIterator(result_set1, result_set2)
        streamed = self._make_one(iterator)
        found = list(streamed)
        self.assertEqual(found, [
            [BARE[0], BARE[1], BARE[2]],
            [BARE[3], BARE[4], BARE[5]],
            [BARE[6], BARE[7], BARE[8]],
        ])
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, [])
        self.assertIsNone(streamed._pending_chunk)

    def test___iter___w_existing_rows_read(self):
        FIELDS = [
            self._make_scalar_field('full_name', 'STRING'),
            self._make_scalar_field('age', 'INT64'),
            self._make_scalar_field('married', 'BOOL'),
        ]
        metadata = self._make_result_set_metadata(FIELDS)
        ALREADY = [
            [u'Pebbylz Phlyntstone', 4, False],
            [u'Dino Rhubble', 4, False],
        ]
        BARE = [
            u'Phred Phlyntstone', 42, True,
            u'Bharney Rhubble', 39, True,
            u'Wylma Phlyntstone', 41, True,
        ]
        VALUES = [self._make_value(bare) for bare in BARE]
        result_set1 = self._make_partial_result_set(
            VALUES[:4], metadata=metadata)
        result_set2 = self._make_partial_result_set(VALUES[4:])
        iterator = _MockCancellableIterator(result_set1, result_set2)
        streamed = self._make_one(iterator)
        streamed._rows[:] = ALREADY
        found = list(streamed)
        self.assertEqual(found, ALREADY + [
            [BARE[0], BARE[1], BARE[2]],
            [BARE[3], BARE[4], BARE[5]],
            [BARE[6], BARE[7], BARE[8]],
        ])
        self.assertEqual(list(streamed), [])
        self.assertEqual(streamed._current_row, [])
        self.assertIsNone(streamed._pending_chunk)


class _MockCancellableIterator(object):

    cancel_calls = 0

    def __init__(self, *values):
        self.iter_values = iter(values)

    def next(self):
        return next(self.iter_values)

    def __next__(self):  # pragma: NO COVER Py3k
        return self.next()


class TestStreamedResultSet_JSON_acceptance_tests(unittest.TestCase):

    _json_tests = None

    def _getTargetClass(self):
        from google.cloud.spanner_v1.streamed import StreamedResultSet

        return StreamedResultSet

    def _make_one(self, *args, **kwargs):
        return self._getTargetClass()(*args, **kwargs)

    def _load_json_test(self, test_name):
        import os

        if self.__class__._json_tests is None:
            dirname = os.path.dirname(__file__)
            filename = os.path.join(
                dirname, 'streaming-read-acceptance-test.json')
            raw = _parse_streaming_read_acceptance_tests(filename)
            tests = self.__class__._json_tests = {}
            for (name, partial_result_sets, results) in raw:
                tests[name] = partial_result_sets, results
        return self.__class__._json_tests[test_name]

    # Non-error cases

    def _match_results(self, testcase_name, assert_equality=None):
        partial_result_sets, expected = self._load_json_test(testcase_name)
        iterator = _MockCancellableIterator(*partial_result_sets)
        partial = self._make_one(iterator)
        if assert_equality is not None:
            assert_equality(list(partial), expected)
        else:
            self.assertEqual(list(partial), expected)

    def test_basic(self):
        self._match_results('Basic Test')

    def test_string_chunking(self):
        self._match_results('String Chunking Test')

    def test_string_array_chunking(self):
        self._match_results('String Array Chunking Test')

    def test_string_array_chunking_with_nulls(self):
        self._match_results('String Array Chunking Test With Nulls')

    def test_string_array_chunking_with_empty_strings(self):
        self._match_results('String Array Chunking Test With Empty Strings')

    def test_string_array_chunking_with_one_large_string(self):
        self._match_results('String Array Chunking Test With One Large String')

    def test_int64_array_chunking(self):
        self._match_results('INT64 Array Chunking Test')

    def test_float64_array_chunking(self):
        import math

        def assert_float_equality(lhs, rhs):
            # NaN, +Inf, and -Inf can't be tested for equality
            if lhs is None:
                self.assertIsNone(rhs)
            elif math.isnan(lhs):
                self.assertTrue(math.isnan(rhs))
            elif math.isinf(lhs):
                self.assertTrue(math.isinf(rhs))
                # but +Inf and -Inf can be tested for magnitude
                self.assertTrue((lhs > 0) == (rhs > 0))
            else:
                self.assertEqual(lhs, rhs)

        def assert_rows_equality(lhs, rhs):
            self.assertEqual(len(lhs), len(rhs))
            for l_rows, r_rows in zip(lhs, rhs):
                self.assertEqual(len(l_rows), len(r_rows))
                for l_row, r_row in zip(l_rows, r_rows):
                    self.assertEqual(len(l_row), len(r_row))
                    for l_cell, r_cell in zip(l_row, r_row):
                        assert_float_equality(l_cell, r_cell)

        self._match_results(
            'FLOAT64 Array Chunking Test', assert_rows_equality)

    def test_struct_array_chunking(self):
        self._match_results('Struct Array Chunking Test')

    def test_nested_struct_array(self):
        self._match_results('Nested Struct Array Test')

    def test_nested_struct_array_chunking(self):
        self._match_results('Nested Struct Array Chunking Test')

    def test_struct_array_and_string_chunking(self):
        self._match_results('Struct Array And String Chunking Test')

    def test_multiple_row_single_chunk(self):
        self._match_results('Multiple Row Single Chunk')

    def test_multiple_row_multiple_chunks(self):
        self._match_results('Multiple Row Multiple Chunks')

    def test_multiple_row_chunks_non_chunks_interleaved(self):
        self._match_results('Multiple Row Chunks/Non Chunks Interleaved')


def _generate_partial_result_sets(prs_text_pbs):
    from google.protobuf.json_format import Parse
    from google.cloud.spanner_v1.proto.result_set_pb2 import PartialResultSet

    partial_result_sets = []

    for prs_text_pb in prs_text_pbs:
        prs = PartialResultSet()
        partial_result_sets.append(Parse(prs_text_pb, prs))

    return partial_result_sets


def _normalize_int_array(cell):
    normalized = []
    for subcell in cell:
        if subcell is not None:
            subcell = int(subcell)
        normalized.append(subcell)
    return normalized


def _normalize_float(cell):
    if cell == u'Infinity':
        return float('inf')
    if cell == u'-Infinity':
        return float('-inf')
    if cell == u'NaN':
        return float('nan')
    if cell is not None:
        return float(cell)


def _normalize_results(rows_data, fields):
    """Helper for _parse_streaming_read_acceptance_tests"""
    from google.cloud.spanner_v1.proto import type_pb2

    normalized = []
    for row_data in rows_data:
        row = []
        assert len(row_data) == len(fields)
        for cell, field in zip(row_data, fields):
            if field.type.code == type_pb2.INT64:
                cell = int(cell)
            if field.type.code == type_pb2.FLOAT64:
                cell = _normalize_float(cell)
            elif field.type.code == type_pb2.BYTES:
                cell = cell.encode('utf8')
            elif field.type.code == type_pb2.ARRAY:
                if field.type.array_element_type.code == type_pb2.INT64:
                    cell = _normalize_int_array(cell)
                elif field.type.array_element_type.code == type_pb2.FLOAT64:
                    cell = [_normalize_float(subcell) for subcell in cell]
            row.append(cell)
        normalized.append(row)
    return normalized


def _parse_streaming_read_acceptance_tests(filename):
    """Parse acceptance tests from JSON

    See streaming-read-acceptance-test.json
    """
    import json

    with open(filename) as json_file:
        test_json = json.load(json_file)

    for test in test_json['tests']:
        name = test['name']
        partial_result_sets = _generate_partial_result_sets(test['chunks'])
        fields = partial_result_sets[0].metadata.row_type.fields
        result = _normalize_results(test['result']['value'], fields)
        yield name, partial_result_sets, result
