# Copyright 2015 Google LLC
#
# 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
from ._testing import _make_credentials
from google.api_core.exceptions import DeadlineExceeded


class Test___mutate_rows_request(unittest.TestCase):
    def _call_fut(self, table_name, rows):
        from google.cloud.bigtable.table import _mutate_rows_request

        return _mutate_rows_request(table_name, rows)

    @mock.patch("google.cloud.bigtable.table._MAX_BULK_MUTATIONS", new=3)
    def test__mutate_rows_too_many_mutations(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable.table import TooManyMutationsError

        table = mock.Mock(name="table", spec=["name"])
        table.name = "table"
        rows = [
            DirectRow(row_key=b"row_key", table=table),
            DirectRow(row_key=b"row_key_2", table=table),
        ]
        rows[0].set_cell("cf1", b"c1", 1)
        rows[0].set_cell("cf1", b"c1", 2)
        rows[1].set_cell("cf1", b"c1", 3)
        rows[1].set_cell("cf1", b"c1", 4)
        with self.assertRaises(TooManyMutationsError):
            self._call_fut("table", rows)

    def test__mutate_rows_request(self):
        from google.cloud.bigtable.row import DirectRow

        table = mock.Mock(name="table", spec=["name"])
        table.name = "table"
        rows = [
            DirectRow(row_key=b"row_key", table=table),
            DirectRow(row_key=b"row_key_2"),
        ]
        rows[0].set_cell("cf1", b"c1", b"1")
        rows[1].set_cell("cf1", b"c1", b"2")
        result = self._call_fut("table", rows)

        expected_result = _mutate_rows_request_pb(table_name="table")
        entry1 = expected_result.entries.add()
        entry1.row_key = b"row_key"
        mutations1 = entry1.mutations.add()
        mutations1.set_cell.family_name = "cf1"
        mutations1.set_cell.column_qualifier = b"c1"
        mutations1.set_cell.timestamp_micros = -1
        mutations1.set_cell.value = b"1"
        entry2 = expected_result.entries.add()
        entry2.row_key = b"row_key_2"
        mutations2 = entry2.mutations.add()
        mutations2.set_cell.family_name = "cf1"
        mutations2.set_cell.column_qualifier = b"c1"
        mutations2.set_cell.timestamp_micros = -1
        mutations2.set_cell.value = b"2"

        self.assertEqual(result, expected_result)


class Test__check_row_table_name(unittest.TestCase):
    def _call_fut(self, table_name, row):
        from google.cloud.bigtable.table import _check_row_table_name

        return _check_row_table_name(table_name, row)

    def test_wrong_table_name(self):
        from google.cloud.bigtable.table import TableMismatchError
        from google.cloud.bigtable.row import DirectRow

        table = mock.Mock(name="table", spec=["name"])
        table.name = "table"
        row = DirectRow(row_key=b"row_key", table=table)
        with self.assertRaises(TableMismatchError):
            self._call_fut("other_table", row)

    def test_right_table_name(self):
        from google.cloud.bigtable.row import DirectRow

        table = mock.Mock(name="table", spec=["name"])
        table.name = "table"
        row = DirectRow(row_key=b"row_key", table=table)
        result = self._call_fut("table", row)
        self.assertFalse(result)


class Test__check_row_type(unittest.TestCase):
    def _call_fut(self, row):
        from google.cloud.bigtable.table import _check_row_type

        return _check_row_type(row)

    def test_test_wrong_row_type(self):
        from google.cloud.bigtable.row import ConditionalRow

        row = ConditionalRow(row_key=b"row_key", table="table", filter_=None)
        with self.assertRaises(TypeError):
            self._call_fut(row)

    def test_right_row_type(self):
        from google.cloud.bigtable.row import DirectRow

        row = DirectRow(row_key=b"row_key", table="table")
        result = self._call_fut(row)
        self.assertFalse(result)


class TestTable(unittest.TestCase):

    PROJECT_ID = "project-id"
    INSTANCE_ID = "instance-id"
    INSTANCE_NAME = "projects/" + PROJECT_ID + "/instances/" + INSTANCE_ID
    TABLE_ID = "table-id"
    TABLE_NAME = INSTANCE_NAME + "/tables/" + TABLE_ID
    ROW_KEY = b"row-key"
    ROW_KEY_1 = b"row-key-1"
    ROW_KEY_2 = b"row-key-2"
    ROW_KEY_3 = b"row-key-3"
    FAMILY_NAME = u"family"
    QUALIFIER = b"qualifier"
    TIMESTAMP_MICROS = 100
    VALUE = b"value"
    _json_tests = None

    @staticmethod
    def _get_target_class():
        from google.cloud.bigtable.table import Table

        return Table

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

    @staticmethod
    def _get_target_client_class():
        from google.cloud.bigtable.client import Client

        return Client

    def _make_client(self, *args, **kwargs):
        return self._get_target_client_class()(*args, **kwargs)

    def test_constructor_w_admin(self):
        credentials = _make_credentials()
        client = self._make_client(
            project=self.PROJECT_ID, credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)
        self.assertEqual(table.table_id, self.TABLE_ID)
        self.assertIs(table._instance._client, client)
        self.assertEqual(table.name, self.TABLE_NAME)

    def test_constructor_wo_admin(self):
        credentials = _make_credentials()
        client = self._make_client(
            project=self.PROJECT_ID, credentials=credentials, admin=False
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)
        self.assertEqual(table.table_id, self.TABLE_ID)
        self.assertIs(table._instance._client, client)
        self.assertEqual(table.name, self.TABLE_NAME)

    def test_row_factory_direct(self):
        from google.cloud.bigtable.row import DirectRow

        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)
        row_key = b"row_key"
        row = table.row(row_key)

        self.assertIsInstance(row, DirectRow)
        self.assertEqual(row._row_key, row_key)
        self.assertEqual(row._table, table)

    def test_row_factory_conditional(self):
        from google.cloud.bigtable.row import ConditionalRow

        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)
        row_key = b"row_key"
        filter_ = object()
        row = table.row(row_key, filter_=filter_)

        self.assertIsInstance(row, ConditionalRow)
        self.assertEqual(row._row_key, row_key)
        self.assertEqual(row._table, table)

    def test_row_factory_append(self):
        from google.cloud.bigtable.row import AppendRow

        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)
        row_key = b"row_key"
        row = table.row(row_key, append=True)

        self.assertIsInstance(row, AppendRow)
        self.assertEqual(row._row_key, row_key)
        self.assertEqual(row._table, table)

    def test_row_factory_failure(self):
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)
        with self.assertRaises(ValueError):
            table.row(b"row_key", filter_=object(), append=True)

    def test___eq__(self):
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table1 = self._make_one(self.TABLE_ID, instance)
        table2 = self._make_one(self.TABLE_ID, instance)
        self.assertEqual(table1, table2)

    def test___eq__type_differ(self):
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table1 = self._make_one(self.TABLE_ID, instance)
        table2 = object()
        self.assertNotEqual(table1, table2)

    def test___ne__same_value(self):
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table1 = self._make_one(self.TABLE_ID, instance)
        table2 = self._make_one(self.TABLE_ID, instance)
        comparison_val = table1 != table2
        self.assertFalse(comparison_val)

    def test___ne__(self):
        table1 = self._make_one("table_id1", None)
        table2 = self._make_one("table_id2", None)
        self.assertNotEqual(table1, table2)

    def _create_test_helper(self, split_keys=[], column_families={}):
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
        from google.cloud.bigtable_admin_v2.proto import table_pb2
        from google.cloud.bigtable_admin_v2.proto import (
            bigtable_table_admin_pb2 as table_admin_messages_v2_pb2,
        )
        from google.cloud.bigtable.column_family import ColumnFamily

        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        # Patch API calls
        client._table_admin_client = table_api

        # Perform the method and check the result.
        table.create(column_families=column_families, initial_split_keys=split_keys)

        families = {
            id: ColumnFamily(id, self, rule).to_pb()
            for (id, rule) in column_families.items()
        }

        split = table_admin_messages_v2_pb2.CreateTableRequest.Split
        splits = [split(key=split_key) for split_key in split_keys]

        table_api.create_table.assert_called_once_with(
            parent=self.INSTANCE_NAME,
            table=table_pb2.Table(column_families=families),
            table_id=self.TABLE_ID,
            initial_splits=splits,
        )

    def test_create(self):
        self._create_test_helper()

    def test_create_with_families(self):
        from google.cloud.bigtable.column_family import MaxVersionsGCRule

        families = {"family": MaxVersionsGCRule(5)}
        self._create_test_helper(column_families=families)

    def test_create_with_split_keys(self):
        self._create_test_helper(split_keys=[b"split1", b"split2", b"split3"])

    def test_exists(self):
        from google.cloud.bigtable_admin_v2.proto import table_pb2 as table_data_v2_pb2
        from google.cloud.bigtable_admin_v2.proto import (
            bigtable_table_admin_pb2 as table_messages_v1_pb2,
        )
        from google.cloud.bigtable_admin_v2.gapic import (
            bigtable_instance_admin_client,
            bigtable_table_admin_client,
        )
        from google.api_core.exceptions import NotFound
        from google.api_core.exceptions import BadRequest

        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        instance_api = bigtable_instance_admin_client.BigtableInstanceAdminClient(
            mock.Mock()
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        # Create response_pb
        response_pb = table_messages_v1_pb2.ListTablesResponse(
            tables=[table_data_v2_pb2.Table(name=self.TABLE_NAME)]
        )

        # Patch API calls
        client._table_admin_client = table_api
        client._instance_admin_client = instance_api
        bigtable_table_stub = client._table_admin_client.transport
        bigtable_table_stub.get_table.side_effect = [
            response_pb,
            NotFound("testing"),
            BadRequest("testing"),
        ]

        # Perform the method and check the result.
        table1 = instance.table(self.TABLE_ID)
        table2 = instance.table("table-id2")

        result = table1.exists()
        self.assertEqual(True, result)

        result = table2.exists()
        self.assertEqual(False, result)

        with self.assertRaises(BadRequest):
            table2.exists()

    def test_delete(self):
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        # Patch API calls
        client._table_admin_client = table_api

        # Create expected_result.
        expected_result = None  # delete() has no return value.

        # Perform the method and check the result.
        result = table.delete()
        self.assertEqual(result, expected_result)

    def _list_column_families_helper(self):
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        # Create response_pb
        COLUMN_FAMILY_ID = "foo"
        column_family = _ColumnFamilyPB()
        response_pb = _TablePB(column_families={COLUMN_FAMILY_ID: column_family})

        # Patch the stub used by the API method.
        client._table_admin_client = table_api
        bigtable_table_stub = client._table_admin_client.transport
        bigtable_table_stub.get_table.side_effect = [response_pb]

        # Create expected_result.
        expected_result = {COLUMN_FAMILY_ID: table.column_family(COLUMN_FAMILY_ID)}

        # Perform the method and check the result.
        result = table.list_column_families()
        self.assertEqual(result, expected_result)

    def test_list_column_families(self):
        self._list_column_families_helper()

    def test_get_cluster_states(self):
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
        from google.cloud.bigtable.enums import Table as enum_table
        from google.cloud.bigtable.table import ClusterState

        INITIALIZING = enum_table.ReplicationState.INITIALIZING
        PLANNED_MAINTENANCE = enum_table.ReplicationState.PLANNED_MAINTENANCE
        READY = enum_table.ReplicationState.READY

        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        response_pb = _TablePB(
            cluster_states={
                "cluster-id1": _ClusterStatePB(INITIALIZING),
                "cluster-id2": _ClusterStatePB(PLANNED_MAINTENANCE),
                "cluster-id3": _ClusterStatePB(READY),
            }
        )

        # Patch the stub used by the API method.
        client._table_admin_client = table_api
        bigtable_table_stub = client._table_admin_client.transport
        bigtable_table_stub.get_table.side_effect = [response_pb]

        # build expected result
        expected_result = {
            u"cluster-id1": ClusterState(INITIALIZING),
            u"cluster-id2": ClusterState(PLANNED_MAINTENANCE),
            u"cluster-id3": ClusterState(READY),
        }

        # Perform the method and check the result.
        result = table.get_cluster_states()
        self.assertEqual(result, expected_result)

    def _read_row_helper(self, chunks, expected_result, app_profile_id=None):

        from google.cloud._testing import _Monkey
        from google.cloud.bigtable import table as MUT
        from google.cloud.bigtable.row_set import RowSet
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
        from google.cloud.bigtable.row_filters import RowSampleFilter

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance, app_profile_id=app_profile_id)

        # Create request_pb
        request_pb = object()  # Returned by our mock.
        mock_created = []

        def mock_create_row_request(table_name, **kwargs):
            mock_created.append((table_name, kwargs))
            return request_pb

        # Create response_iterator
        if chunks is None:
            response_iterator = iter(())  # no responses at all
        else:
            response_pb = _ReadRowsResponsePB(chunks=chunks)
            response_iterator = iter([response_pb])

        # Patch the stub used by the API method.
        client._table_data_client = data_api
        client._table_admin_client = table_api
        client._table_data_client.transport.read_rows = mock.Mock(
            side_effect=[response_iterator]
        )

        # Perform the method and check the result.
        filter_obj = RowSampleFilter(0.33)
        result = None
        with _Monkey(MUT, _create_row_request=mock_create_row_request):
            result = table.read_row(self.ROW_KEY, filter_=filter_obj)
        row_set = RowSet()
        row_set.add_row_key(self.ROW_KEY)
        expected_request = [
            (
                table.name,
                {
                    "end_inclusive": False,
                    "row_set": row_set,
                    "app_profile_id": app_profile_id,
                    "end_key": None,
                    "limit": None,
                    "start_key": None,
                    "filter_": filter_obj,
                },
            )
        ]
        self.assertEqual(result, expected_result)
        self.assertEqual(mock_created, expected_request)

    def test_read_row_miss_no__responses(self):
        self._read_row_helper(None, None)

    def test_read_row_miss_no_chunks_in_response(self):
        chunks = []
        self._read_row_helper(chunks, None)

    def test_read_row_complete(self):
        from google.cloud.bigtable.row_data import Cell
        from google.cloud.bigtable.row_data import PartialRowData

        app_profile_id = "app-profile-id"
        chunk = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )
        chunks = [chunk]
        expected_result = PartialRowData(row_key=self.ROW_KEY)
        family = expected_result._cells.setdefault(self.FAMILY_NAME, {})
        column = family.setdefault(self.QUALIFIER, [])
        column.append(Cell.from_pb(chunk))
        self._read_row_helper(chunks, expected_result, app_profile_id)

    def test_read_row_more_than_one_row_returned(self):
        app_profile_id = "app-profile-id"
        chunk_1 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )
        chunk_2 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_2,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        chunks = [chunk_1, chunk_2]
        with self.assertRaises(ValueError):
            self._read_row_helper(chunks, None, app_profile_id)

    def test_read_row_still_partial(self):
        chunk = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
        )
        # No "commit row".
        chunks = [chunk]
        with self.assertRaises(ValueError):
            self._read_row_helper(chunks, None)

    def test_mutate_rows(self):
        from google.rpc.status_pb2 import Status
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        instance = client.instance(instance_id=self.INSTANCE_ID)
        client._table_admin_client = table_api
        table = self._make_one(self.TABLE_ID, instance)

        response = [Status(code=0), Status(code=1)]

        mock_worker = mock.Mock(return_value=response)
        with mock.patch(
            "google.cloud.bigtable.table._RetryableMutateRowsWorker",
            new=mock.MagicMock(return_value=mock_worker),
        ):
            statuses = table.mutate_rows([mock.MagicMock(), mock.MagicMock()])
        result = [status.code for status in statuses]
        expected_result = [0, 1]

        self.assertEqual(result, expected_result)

    def test_read_rows(self):
        from google.cloud._testing import _Monkey
        from google.cloud.bigtable.row_data import PartialRowsData
        from google.cloud.bigtable import table as MUT
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        app_profile_id = "app-profile-id"
        table = self._make_one(self.TABLE_ID, instance, app_profile_id=app_profile_id)

        # Create request_pb
        request = retry = object()  # Returned by our mock.
        mock_created = []

        def mock_create_row_request(table_name, **kwargs):
            mock_created.append((table_name, kwargs))
            return request

        # Create expected_result.
        expected_result = PartialRowsData(
            client._table_data_client.transport.read_rows, request, retry
        )

        # Perform the method and check the result.
        start_key = b"start-key"
        end_key = b"end-key"
        filter_obj = object()
        limit = 22
        with _Monkey(MUT, _create_row_request=mock_create_row_request):
            result = table.read_rows(
                start_key=start_key,
                end_key=end_key,
                filter_=filter_obj,
                limit=limit,
                retry=retry,
            )

        self.assertEqual(result.rows, expected_result.rows)
        self.assertEqual(result.retry, expected_result.retry)
        created_kwargs = {
            "start_key": start_key,
            "end_key": end_key,
            "filter_": filter_obj,
            "limit": limit,
            "end_inclusive": False,
            "app_profile_id": app_profile_id,
            "row_set": None,
        }
        self.assertEqual(mock_created, [(table.name, created_kwargs)])

    def test_read_retry_rows(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
        from google.api_core import retry

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        retry_read_rows = retry.Retry(predicate=_read_rows_retry_exception)

        # Create response_iterator
        chunk_1 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_1,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        chunk_2 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_2,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        response_1 = _ReadRowsResponseV2([chunk_1])
        response_2 = _ReadRowsResponseV2([chunk_2])
        response_failure_iterator_1 = _MockFailureIterator_1()
        response_failure_iterator_2 = _MockFailureIterator_2([response_1])
        response_iterator = _MockReadRowsIterator(response_2)

        # Patch the stub used by the API method.
        client._table_data_client.transport.read_rows = mock.Mock(
            side_effect=[
                response_failure_iterator_1,
                response_failure_iterator_2,
                response_iterator,
            ]
        )

        rows = []
        for row in table.read_rows(
            start_key=self.ROW_KEY_1, end_key=self.ROW_KEY_2, retry=retry_read_rows
        ):
            rows.append(row)

        result = rows[1]
        self.assertEqual(result.row_key, self.ROW_KEY_2)

    def test_yield_retry_rows(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        # Create response_iterator
        chunk_1 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_1,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        chunk_2 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_2,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        response_1 = _ReadRowsResponseV2([chunk_1])
        response_2 = _ReadRowsResponseV2([chunk_2])
        response_failure_iterator_1 = _MockFailureIterator_1()
        response_failure_iterator_2 = _MockFailureIterator_2([response_1])
        response_iterator = _MockReadRowsIterator(response_2)

        # Patch the stub used by the API method.
        client._table_data_client.transport.read_rows = mock.Mock(
            side_effect=[
                response_failure_iterator_1,
                response_failure_iterator_2,
                response_iterator,
            ]
        )

        rows = []
        for row in table.yield_rows(start_key=self.ROW_KEY_1, end_key=self.ROW_KEY_2):
            rows.append(row)

        result = rows[1]
        self.assertEqual(result.row_key, self.ROW_KEY_2)

    def test_yield_rows_with_row_set(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
        from google.cloud.bigtable.row_set import RowSet
        from google.cloud.bigtable.row_set import RowRange

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        # Create response_iterator
        chunk_1 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_1,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        chunk_2 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_2,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        chunk_3 = _ReadRowsResponseCellChunkPB(
            row_key=self.ROW_KEY_3,
            family_name=self.FAMILY_NAME,
            qualifier=self.QUALIFIER,
            timestamp_micros=self.TIMESTAMP_MICROS,
            value=self.VALUE,
            commit_row=True,
        )

        response_1 = _ReadRowsResponseV2([chunk_1])
        response_2 = _ReadRowsResponseV2([chunk_2])
        response_3 = _ReadRowsResponseV2([chunk_3])
        response_iterator = _MockReadRowsIterator(response_1, response_2, response_3)

        # Patch the stub used by the API method.
        client._table_data_client.transport.read_rows = mock.Mock(
            side_effect=[response_iterator]
        )

        rows = []
        row_set = RowSet()
        row_set.add_row_range(
            RowRange(start_key=self.ROW_KEY_1, end_key=self.ROW_KEY_2)
        )
        row_set.add_row_key(self.ROW_KEY_3)
        for row in table.yield_rows(row_set=row_set):
            rows.append(row)

        self.assertEqual(rows[0].row_key, self.ROW_KEY_1)
        self.assertEqual(rows[1].row_key, self.ROW_KEY_2)
        self.assertEqual(rows[2].row_key, self.ROW_KEY_3)

    def test_sample_row_keys(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        # Create response_iterator
        response_iterator = object()  # Just passed to a mock.

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["sample_row_keys"] = mock.Mock(
            side_effect=[[response_iterator]]
        )

        # Create expected_result.
        expected_result = response_iterator

        # Perform the method and check the result.
        result = table.sample_row_keys()
        self.assertEqual(result[0], expected_result)

    def test_truncate(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = mock.create_autospec(bigtable_client.BigtableClient)
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        expected_result = None  # truncate() has no return value.
        with mock.patch("google.cloud.bigtable.table.Table.name", new=self.TABLE_NAME):
            result = table.truncate()

        table_api.drop_row_range.assert_called_once_with(
            name=self.TABLE_NAME, delete_all_data_from_table=True
        )

        self.assertEqual(result, expected_result)

    def test_truncate_w_timeout(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = mock.create_autospec(bigtable_client.BigtableClient)
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        expected_result = None  # truncate() has no return value.

        timeout = 120
        result = table.truncate(timeout=timeout)

        self.assertEqual(result, expected_result)

    def test_drop_by_prefix(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = mock.create_autospec(bigtable_client.BigtableClient)
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        expected_result = None  # drop_by_prefix() has no return value.

        row_key_prefix = "row-key-prefix"

        result = table.drop_by_prefix(row_key_prefix=row_key_prefix)

        self.assertEqual(result, expected_result)

    def test_drop_by_prefix_w_timeout(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = mock.create_autospec(bigtable_client.BigtableClient)
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_one(self.TABLE_ID, instance)

        expected_result = None  # drop_by_prefix() has no return value.

        row_key_prefix = "row-key-prefix"

        timeout = 120
        result = table.drop_by_prefix(row_key_prefix=row_key_prefix, timeout=timeout)

        self.assertEqual(result, expected_result)

    def test_mutations_batcher_factory(self):
        flush_count = 100
        max_row_bytes = 1000
        table = self._make_one(self.TABLE_ID, None)
        mutation_batcher = table.mutations_batcher(
            flush_count=flush_count, max_row_bytes=max_row_bytes
        )

        self.assertEqual(mutation_batcher.table.table_id, self.TABLE_ID)
        self.assertEqual(mutation_batcher.flush_count, flush_count)
        self.assertEqual(mutation_batcher.max_row_bytes, max_row_bytes)


class Test__RetryableMutateRowsWorker(unittest.TestCase):
    from grpc import StatusCode

    PROJECT_ID = "project-id"
    INSTANCE_ID = "instance-id"
    INSTANCE_NAME = "projects/" + PROJECT_ID + "/instances/" + INSTANCE_ID
    TABLE_ID = "table-id"

    # RPC Status Codes
    SUCCESS = StatusCode.OK.value[0]
    RETRYABLE_1 = StatusCode.DEADLINE_EXCEEDED.value[0]
    RETRYABLE_2 = StatusCode.ABORTED.value[0]
    NON_RETRYABLE = StatusCode.CANCELLED.value[0]

    @staticmethod
    def _get_target_class_for_worker():
        from google.cloud.bigtable.table import _RetryableMutateRowsWorker

        return _RetryableMutateRowsWorker

    def _make_worker(self, *args, **kwargs):
        return self._get_target_class_for_worker()(*args, **kwargs)

    @staticmethod
    def _get_target_class_for_table():
        from google.cloud.bigtable.table import Table

        return Table

    def _make_table(self, *args, **kwargs):
        return self._get_target_class_for_table()(*args, **kwargs)

    @staticmethod
    def _get_target_client_class():
        from google.cloud.bigtable.client import Client

        return Client

    def _make_client(self, *args, **kwargs):
        return self._get_target_client_class()(*args, **kwargs)

    def _make_responses_statuses(self, codes):
        from google.rpc.status_pb2 import Status

        response = [Status(code=code) for code in codes]
        return response

    def _make_responses(self, codes):
        import six
        from google.cloud.bigtable_v2.proto.bigtable_pb2 import MutateRowsResponse
        from google.rpc.status_pb2 import Status

        entries = [
            MutateRowsResponse.Entry(index=i, status=Status(code=codes[i]))
            for i in six.moves.xrange(len(codes))
        ]
        return MutateRowsResponse(entries=entries)

    def test_callable_empty_rows(self):
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = mock.create_autospec(bigtable_client.BigtableClient)
        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        worker = self._make_worker(client, table.name, [])
        statuses = worker()

        self.assertEqual(len(statuses), 0)

    def test_callable_no_retry_strategy(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 3 rows.
        # Action:
        #   - Attempt to mutate the rows w/o any retry strategy.
        # Expectation:
        #   - Since no retry, should return statuses as they come back.
        #   - Even if there are retryable errors, no retry attempt is made.
        #   - State of responses_statuses should be
        #       [success, retryable, non-retryable]

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")
        row_3 = DirectRow(row_key=b"row_key_3", table=table)
        row_3.set_cell("cf", b"col", b"value3")

        response = self._make_responses(
            [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE]
        )

        with mock.patch("google.cloud.bigtable.table.wrap_method") as patched:
            patched.return_value = mock.Mock(return_value=[response])

            worker = self._make_worker(client, table.name, [row_1, row_2, row_3])
            statuses = worker(retry=None)

        result = [status.code for status in statuses]
        expected_result = [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE]

        client._table_data_client._inner_api_calls["mutate_rows"].assert_called_once()
        self.assertEqual(result, expected_result)

    def test_callable_retry(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable.table import DEFAULT_RETRY
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 3 rows.
        # Action:
        #   - Initial attempt will mutate all 3 rows.
        # Expectation:
        #   - First attempt will result in one retryable error.
        #   - Second attempt will result in success for the retry-ed row.
        #   - Check MutateRows is called twice.
        #   - State of responses_statuses should be
        #       [success, success, non-retryable]

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")
        row_3 = DirectRow(row_key=b"row_key_3", table=table)
        row_3.set_cell("cf", b"col", b"value3")

        response_1 = self._make_responses(
            [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE]
        )
        response_2 = self._make_responses([self.SUCCESS])

        # Patch the stub used by the API method.
        client._table_data_client._inner_api_calls["mutate_rows"] = mock.Mock(
            side_effect=[[response_1], [response_2]]
        )

        retry = DEFAULT_RETRY.with_delay(initial=0.1)
        worker = self._make_worker(client, table.name, [row_1, row_2, row_3])
        statuses = worker(retry=retry)

        result = [status.code for status in statuses]
        expected_result = [self.SUCCESS, self.SUCCESS, self.NON_RETRYABLE]

        self.assertEqual(
            client._table_data_client._inner_api_calls["mutate_rows"].call_count, 2
        )
        self.assertEqual(result, expected_result)

    def test_callable_retry_timeout(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable.table import DEFAULT_RETRY
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 2 rows.
        # Action:
        #   - Initial attempt will mutate all 2 rows.
        # Expectation:
        #   - Both rows always return retryable errors.
        #   - google.api_core.Retry should keep retrying.
        #   - Check MutateRows is called multiple times.
        #   - By the time deadline is reached, statuses should be
        #       [retryable, retryable]

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")

        response = self._make_responses([self.RETRYABLE_1, self.RETRYABLE_1])

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["mutate_rows"] = mock.Mock(return_value=[response])

        retry = DEFAULT_RETRY.with_delay(
            initial=0.1, maximum=0.2, multiplier=2.0
        ).with_deadline(0.5)
        worker = self._make_worker(client, table.name, [row_1, row_2])
        statuses = worker(retry=retry)

        result = [status.code for status in statuses]
        expected_result = [self.RETRYABLE_1, self.RETRYABLE_1]

        self.assertTrue(
            client._table_data_client._inner_api_calls["mutate_rows"].call_count > 1
        )
        self.assertEqual(result, expected_result)

    def test_do_mutate_retryable_rows_empty_rows(self):
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        worker = self._make_worker(client, table.name, [])
        statuses = worker._do_mutate_retryable_rows()

        self.assertEqual(len(statuses), 0)

    def test_do_mutate_retryable_rows(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 2 rows.
        # Action:
        #   - Initial attempt will mutate all 2 rows.
        # Expectation:
        #   - Expect [success, non-retryable]

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")

        response = self._make_responses([self.SUCCESS, self.NON_RETRYABLE])

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["mutate_rows"] = mock.Mock(side_effect=[[response]])

        worker = self._make_worker(client, table.name, [row_1, row_2])
        statuses = worker._do_mutate_retryable_rows()

        result = [status.code for status in statuses]
        expected_result = [self.SUCCESS, self.NON_RETRYABLE]

        self.assertEqual(result, expected_result)

    def test_do_mutate_retryable_rows_retry(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable.table import _BigtableRetryableError
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 3 rows.
        # Action:
        #   - Initial attempt will mutate all 3 rows.
        # Expectation:
        #   - Second row returns retryable error code, so expect a raise.
        #   - State of responses_statuses should be
        #       [success, retryable, non-retryable]

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")
        row_3 = DirectRow(row_key=b"row_key_3", table=table)
        row_3.set_cell("cf", b"col", b"value3")

        response = self._make_responses(
            [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE]
        )

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["mutate_rows"] = mock.Mock(side_effect=[[response]])

        worker = self._make_worker(client, table.name, [row_1, row_2, row_3])

        with self.assertRaises(_BigtableRetryableError):
            worker._do_mutate_retryable_rows()

        statuses = worker.responses_statuses
        result = [status.code for status in statuses]
        expected_result = [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE]

        self.assertEqual(result, expected_result)

    def test_do_mutate_retryable_rows_second_retry(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable.table import _BigtableRetryableError
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 4 rows.
        #   - First try results:
        #       [success, retryable, non-retryable, retryable]
        # Action:
        #   - Second try should re-attempt the 'retryable' rows.
        # Expectation:
        #   - After second try:
        #       [success, success, non-retryable, retryable]
        #   - One of the rows tried second time returns retryable error code,
        #     so expect a raise.
        #   - Exception contains response whose index should be '3' even though
        #     only two rows were retried.

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")
        row_3 = DirectRow(row_key=b"row_key_3", table=table)
        row_3.set_cell("cf", b"col", b"value3")
        row_4 = DirectRow(row_key=b"row_key_4", table=table)
        row_4.set_cell("cf", b"col", b"value4")

        response = self._make_responses([self.SUCCESS, self.RETRYABLE_1])

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["mutate_rows"] = mock.Mock(side_effect=[[response]])

        worker = self._make_worker(client, table.name, [row_1, row_2, row_3, row_4])
        worker.responses_statuses = self._make_responses_statuses(
            [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE, self.RETRYABLE_2]
        )

        with self.assertRaises(_BigtableRetryableError):
            worker._do_mutate_retryable_rows()

        statuses = worker.responses_statuses
        result = [status.code for status in statuses]
        expected_result = [
            self.SUCCESS,
            self.SUCCESS,
            self.NON_RETRYABLE,
            self.RETRYABLE_1,
        ]

        self.assertEqual(result, expected_result)

    def test_do_mutate_retryable_rows_second_try(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 4 rows.
        #   - First try results:
        #       [success, retryable, non-retryable, retryable]
        # Action:
        #   - Second try should re-attempt the 'retryable' rows.
        # Expectation:
        #   - After second try:
        #       [success, non-retryable, non-retryable, success]

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")
        row_3 = DirectRow(row_key=b"row_key_3", table=table)
        row_3.set_cell("cf", b"col", b"value3")
        row_4 = DirectRow(row_key=b"row_key_4", table=table)
        row_4.set_cell("cf", b"col", b"value4")

        response = self._make_responses([self.NON_RETRYABLE, self.SUCCESS])

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["mutate_rows"] = mock.Mock(side_effect=[[response]])

        worker = self._make_worker(client, table.name, [row_1, row_2, row_3, row_4])
        worker.responses_statuses = self._make_responses_statuses(
            [self.SUCCESS, self.RETRYABLE_1, self.NON_RETRYABLE, self.RETRYABLE_2]
        )

        statuses = worker._do_mutate_retryable_rows()

        result = [status.code for status in statuses]
        expected_result = [
            self.SUCCESS,
            self.NON_RETRYABLE,
            self.NON_RETRYABLE,
            self.SUCCESS,
        ]

        self.assertEqual(result, expected_result)

    def test_do_mutate_retryable_rows_second_try_no_retryable(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        # Setup:
        #   - Mutate 2 rows.
        #   - First try results: [success, non-retryable]
        # Action:
        #   - Second try has no row to retry.
        # Expectation:
        #   - After second try: [success, non-retryable]

        table_api = mock.create_autospec(
            bigtable_table_admin_client.BigtableTableAdminClient
        )
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")

        worker = self._make_worker(client, table.name, [row_1, row_2])
        worker.responses_statuses = self._make_responses_statuses(
            [self.SUCCESS, self.NON_RETRYABLE]
        )

        statuses = worker._do_mutate_retryable_rows()

        result = [status.code for status in statuses]
        expected_result = [self.SUCCESS, self.NON_RETRYABLE]

        self.assertEqual(result, expected_result)

    def test_do_mutate_retryable_rows_mismatch_num_responses(self):
        from google.cloud.bigtable.row import DirectRow
        from google.cloud.bigtable_v2.gapic import bigtable_client
        from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client

        data_api = bigtable_client.BigtableClient(mock.Mock())
        table_api = bigtable_table_admin_client.BigtableTableAdminClient(mock.Mock())
        credentials = _make_credentials()
        client = self._make_client(
            project="project-id", credentials=credentials, admin=True
        )
        client._table_data_client = data_api
        client._table_admin_client = table_api
        instance = client.instance(instance_id=self.INSTANCE_ID)
        table = self._make_table(self.TABLE_ID, instance)

        row_1 = DirectRow(row_key=b"row_key", table=table)
        row_1.set_cell("cf", b"col", b"value1")
        row_2 = DirectRow(row_key=b"row_key_2", table=table)
        row_2.set_cell("cf", b"col", b"value2")

        response = self._make_responses([self.SUCCESS])

        # Patch the stub used by the API method.
        inner_api_calls = client._table_data_client._inner_api_calls
        inner_api_calls["mutate_rows"] = mock.Mock(side_effect=[[response]])

        worker = self._make_worker(client, table.name, [row_1, row_2])
        with self.assertRaises(RuntimeError):
            worker._do_mutate_retryable_rows()


class Test__create_row_request(unittest.TestCase):
    def _call_fut(
        self,
        table_name,
        start_key=None,
        end_key=None,
        filter_=None,
        limit=None,
        end_inclusive=False,
        app_profile_id=None,
        row_set=None,
    ):

        from google.cloud.bigtable.table import _create_row_request

        return _create_row_request(
            table_name,
            start_key=start_key,
            end_key=end_key,
            filter_=filter_,
            limit=limit,
            end_inclusive=end_inclusive,
            app_profile_id=app_profile_id,
            row_set=row_set,
        )

    def test_table_name_only(self):
        table_name = "table_name"
        result = self._call_fut(table_name)
        expected_result = _ReadRowsRequestPB(table_name=table_name)
        self.assertEqual(result, expected_result)

    def test_row_range_row_set_conflict(self):
        with self.assertRaises(ValueError):
            self._call_fut(None, end_key=object(), row_set=object())

    def test_row_range_start_key(self):
        table_name = "table_name"
        start_key = b"start_key"
        result = self._call_fut(table_name, start_key=start_key)
        expected_result = _ReadRowsRequestPB(table_name=table_name)
        expected_result.rows.row_ranges.add(start_key_closed=start_key)
        self.assertEqual(result, expected_result)

    def test_row_range_end_key(self):
        table_name = "table_name"
        end_key = b"end_key"
        result = self._call_fut(table_name, end_key=end_key)
        expected_result = _ReadRowsRequestPB(table_name=table_name)
        expected_result.rows.row_ranges.add(end_key_open=end_key)
        self.assertEqual(result, expected_result)

    def test_row_range_both_keys(self):
        table_name = "table_name"
        start_key = b"start_key"
        end_key = b"end_key"
        result = self._call_fut(table_name, start_key=start_key, end_key=end_key)
        expected_result = _ReadRowsRequestPB(table_name=table_name)
        expected_result.rows.row_ranges.add(
            start_key_closed=start_key, end_key_open=end_key
        )
        self.assertEqual(result, expected_result)

    def test_row_range_both_keys_inclusive(self):
        table_name = "table_name"
        start_key = b"start_key"
        end_key = b"end_key"
        result = self._call_fut(
            table_name, start_key=start_key, end_key=end_key, end_inclusive=True
        )
        expected_result = _ReadRowsRequestPB(table_name=table_name)
        expected_result.rows.row_ranges.add(
            start_key_closed=start_key, end_key_closed=end_key
        )
        self.assertEqual(result, expected_result)

    def test_with_filter(self):
        from google.cloud.bigtable.row_filters import RowSampleFilter

        table_name = "table_name"
        row_filter = RowSampleFilter(0.33)
        result = self._call_fut(table_name, filter_=row_filter)
        expected_result = _ReadRowsRequestPB(
            table_name=table_name, filter=row_filter.to_pb()
        )
        self.assertEqual(result, expected_result)

    def test_with_limit(self):
        table_name = "table_name"
        limit = 1337
        result = self._call_fut(table_name, limit=limit)
        expected_result = _ReadRowsRequestPB(table_name=table_name, rows_limit=limit)
        self.assertEqual(result, expected_result)

    def test_with_row_set(self):
        from google.cloud.bigtable.row_set import RowSet

        table_name = "table_name"
        row_set = RowSet()
        result = self._call_fut(table_name, row_set=row_set)
        expected_result = _ReadRowsRequestPB(table_name=table_name)
        self.assertEqual(result, expected_result)

    def test_with_app_profile_id(self):
        table_name = "table_name"
        limit = 1337
        app_profile_id = "app-profile-id"
        result = self._call_fut(table_name, limit=limit, app_profile_id=app_profile_id)
        expected_result = _ReadRowsRequestPB(
            table_name=table_name, rows_limit=limit, app_profile_id=app_profile_id
        )
        self.assertEqual(result, expected_result)


def _ReadRowsRequestPB(*args, **kw):
    from google.cloud.bigtable_v2.proto import bigtable_pb2 as messages_v2_pb2

    return messages_v2_pb2.ReadRowsRequest(*args, **kw)


class Test_ClusterState(unittest.TestCase):
    def test___eq__(self):
        from google.cloud.bigtable.enums import Table as enum_table
        from google.cloud.bigtable.table import ClusterState

        READY = enum_table.ReplicationState.READY
        state1 = ClusterState(READY)
        state2 = ClusterState(READY)
        self.assertEqual(state1, state2)

    def test___eq__type_differ(self):
        from google.cloud.bigtable.enums import Table as enum_table
        from google.cloud.bigtable.table import ClusterState

        READY = enum_table.ReplicationState.READY
        state1 = ClusterState(READY)
        state2 = object()
        self.assertNotEqual(state1, state2)

    def test___ne__same_value(self):
        from google.cloud.bigtable.enums import Table as enum_table
        from google.cloud.bigtable.table import ClusterState

        READY = enum_table.ReplicationState.READY
        state1 = ClusterState(READY)
        state2 = ClusterState(READY)
        comparison_val = state1 != state2
        self.assertFalse(comparison_val)

    def test___ne__(self):
        from google.cloud.bigtable.enums import Table as enum_table
        from google.cloud.bigtable.table import ClusterState

        READY = enum_table.ReplicationState.READY
        INITIALIZING = enum_table.ReplicationState.INITIALIZING
        state1 = ClusterState(READY)
        state2 = ClusterState(INITIALIZING)
        self.assertNotEqual(state1, state2)

    def test__repr__(self):
        from google.cloud.bigtable.enums import Table as enum_table
        from google.cloud.bigtable.table import ClusterState

        STATE_NOT_KNOWN = enum_table.ReplicationState.STATE_NOT_KNOWN
        INITIALIZING = enum_table.ReplicationState.INITIALIZING
        PLANNED_MAINTENANCE = enum_table.ReplicationState.PLANNED_MAINTENANCE
        UNPLANNED_MAINTENANCE = enum_table.ReplicationState.UNPLANNED_MAINTENANCE
        READY = enum_table.ReplicationState.READY

        replication_dict = {
            STATE_NOT_KNOWN: "STATE_NOT_KNOWN",
            INITIALIZING: "INITIALIZING",
            PLANNED_MAINTENANCE: "PLANNED_MAINTENANCE",
            UNPLANNED_MAINTENANCE: "UNPLANNED_MAINTENANCE",
            READY: "READY",
        }

        self.assertEqual(
            str(ClusterState(STATE_NOT_KNOWN)), replication_dict[STATE_NOT_KNOWN]
        )
        self.assertEqual(
            str(ClusterState(INITIALIZING)), replication_dict[INITIALIZING]
        )
        self.assertEqual(
            str(ClusterState(PLANNED_MAINTENANCE)),
            replication_dict[PLANNED_MAINTENANCE],
        )
        self.assertEqual(
            str(ClusterState(UNPLANNED_MAINTENANCE)),
            replication_dict[UNPLANNED_MAINTENANCE],
        )
        self.assertEqual(str(ClusterState(READY)), replication_dict[READY])

        self.assertEqual(
            ClusterState(STATE_NOT_KNOWN).replication_state, STATE_NOT_KNOWN
        )
        self.assertEqual(ClusterState(INITIALIZING).replication_state, INITIALIZING)
        self.assertEqual(
            ClusterState(PLANNED_MAINTENANCE).replication_state, PLANNED_MAINTENANCE
        )
        self.assertEqual(
            ClusterState(UNPLANNED_MAINTENANCE).replication_state, UNPLANNED_MAINTENANCE
        )
        self.assertEqual(ClusterState(READY).replication_state, READY)


def _ReadRowsResponseCellChunkPB(*args, **kw):
    from google.cloud.bigtable_v2.proto import bigtable_pb2 as messages_v2_pb2

    family_name = kw.pop("family_name")
    qualifier = kw.pop("qualifier")
    message = messages_v2_pb2.ReadRowsResponse.CellChunk(*args, **kw)
    message.family_name.value = family_name
    message.qualifier.value = qualifier
    return message


def _ReadRowsResponsePB(*args, **kw):
    from google.cloud.bigtable_v2.proto import bigtable_pb2 as messages_v2_pb2

    return messages_v2_pb2.ReadRowsResponse(*args, **kw)


def _mutate_rows_request_pb(*args, **kw):
    from google.cloud.bigtable_v2.proto import bigtable_pb2 as data_messages_v2_pb2

    return data_messages_v2_pb2.MutateRowsRequest(*args, **kw)


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

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

    __next__ = next


class _MockFailureIterator_1(object):
    def next(self):
        raise DeadlineExceeded("Failed to read from server")

    __next__ = next


class _MockFailureIterator_2(object):
    def __init__(self, *values):
        self.iter_values = values[0]
        self.calls = 0

    def next(self):
        self.calls += 1
        if self.calls == 1:
            return self.iter_values[0]
        else:
            raise DeadlineExceeded("Failed to read from server")

    __next__ = next


class _ReadRowsResponseV2(object):
    def __init__(self, chunks, last_scanned_row_key=""):
        self.chunks = chunks
        self.last_scanned_row_key = last_scanned_row_key


def _TablePB(*args, **kw):
    from google.cloud.bigtable_admin_v2.proto import table_pb2 as table_v2_pb2

    return table_v2_pb2.Table(*args, **kw)


def _ColumnFamilyPB(*args, **kw):
    from google.cloud.bigtable_admin_v2.proto import table_pb2 as table_v2_pb2

    return table_v2_pb2.ColumnFamily(*args, **kw)


def _ClusterStatePB(replication_state):
    from google.cloud.bigtable_admin_v2.proto import table_pb2 as table_v2_pb2

    return table_v2_pb2.Table.ClusterState(replication_state=replication_state)


def _read_rows_retry_exception(exc):
    return isinstance(exc, DeadlineExceeded)
