# Copyright 2016 Google Inc. 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 unittest2 class Test_make_row(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import make_row return make_row(*args, **kwargs) def test_it(self): with self.assertRaises(NotImplementedError): self._callFUT({}, False) class Test_make_ordered_row(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import make_ordered_row return make_ordered_row(*args, **kwargs) def test_it(self): with self.assertRaises(NotImplementedError): self._callFUT([], False) class TestTable(unittest2.TestCase): def _getTargetClass(self): from gcloud.bigtable.happybase.table import Table return Table def _makeOne(self, *args, **kwargs): return self._getTargetClass()(*args, **kwargs) def test_constructor(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' instance = object() connection = _Connection(instance) tables_constructed = [] def make_low_level_table(*args, **kwargs): result = _MockLowLevelTable(*args, **kwargs) tables_constructed.append(result) return result with _Monkey(MUT, _LowLevelTable=make_low_level_table): table = self._makeOne(name, connection) self.assertEqual(table.name, name) self.assertEqual(table.connection, connection) table_instance, = tables_constructed self.assertEqual(table._low_level_table, table_instance) self.assertEqual(table_instance.args, (name, instance)) self.assertEqual(table_instance.kwargs, {}) def test_constructor_null_connection(self): name = 'table-name' connection = None table = self._makeOne(name, connection) self.assertEqual(table.name, name) self.assertEqual(table.connection, connection) self.assertEqual(table._low_level_table, None) def test_families(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() # Mock the column families to be returned. col_fam_name = 'fam' gc_rule = object() col_fam = _MockLowLevelColumnFamily(col_fam_name, gc_rule=gc_rule) col_fams = {col_fam_name: col_fam} table._low_level_table.column_families = col_fams to_dict_result = object() to_dict_calls = [] def mock_gc_rule_to_dict(gc_rule): to_dict_calls.append(gc_rule) return to_dict_result with _Monkey(MUT, _gc_rule_to_dict=mock_gc_rule_to_dict): result = table.families() self.assertEqual(result, {col_fam_name: to_dict_result}) self.assertEqual(table._low_level_table.list_column_families_calls, 1) self.assertEqual(to_dict_calls, [gc_rule]) def test___repr__(self): name = 'table-name' table = self._makeOne(name, None) self.assertEqual(repr(table), '') def test_regions(self): name = 'table-name' connection = None table = self._makeOne(name, connection) with self.assertRaises(NotImplementedError): table.regions() def test_row_empty_row(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() table._low_level_table.read_row_result = None # Set-up mocks. fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter row_key = 'row-key' timestamp = object() with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper): result = table.row(row_key, timestamp=timestamp) # read_row_result == None --> No results. self.assertEqual(result, {}) read_row_args = (row_key,) read_row_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_row_calls, [ (read_row_args, read_row_kwargs), ]) expected_kwargs = { 'filters': [], 'versions': 1, 'timestamp': timestamp, } self.assertEqual(mock_filters, [expected_kwargs]) def test_row_with_columns(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() table._low_level_table.read_row_result = None # Set-up mocks. fake_col_filter = object() mock_columns = [] def mock_columns_filter_helper(*args): mock_columns.append(args) return fake_col_filter fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter row_key = 'row-key' columns = object() with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper, _columns_filter_helper=mock_columns_filter_helper): result = table.row(row_key, columns=columns) # read_row_result == None --> No results. self.assertEqual(result, {}) read_row_args = (row_key,) read_row_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_row_calls, [ (read_row_args, read_row_kwargs), ]) self.assertEqual(mock_columns, [(columns,)]) expected_kwargs = { 'filters': [fake_col_filter], 'versions': 1, 'timestamp': None, } self.assertEqual(mock_filters, [expected_kwargs]) def test_row_with_results(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT from gcloud.bigtable.row_data import PartialRowData row_key = 'row-key' name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() partial_row = PartialRowData(row_key) table._low_level_table.read_row_result = partial_row # Set-up mocks. fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter fake_pair = object() mock_cells = [] def mock_cells_to_pairs(*args, **kwargs): mock_cells.append((args, kwargs)) return [fake_pair] col_fam = u'cf1' qual = b'qual' fake_cells = object() partial_row._cells = {col_fam: {qual: fake_cells}} include_timestamp = object() with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper, _cells_to_pairs=mock_cells_to_pairs): result = table.row(row_key, include_timestamp=include_timestamp) # The results come from _cells_to_pairs. expected_result = {col_fam.encode('ascii') + b':' + qual: fake_pair} self.assertEqual(result, expected_result) read_row_args = (row_key,) read_row_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_row_calls, [ (read_row_args, read_row_kwargs), ]) expected_kwargs = { 'filters': [], 'versions': 1, 'timestamp': None, } self.assertEqual(mock_filters, [expected_kwargs]) to_pairs_kwargs = {'include_timestamp': include_timestamp} self.assertEqual(mock_cells, [((fake_cells,), to_pairs_kwargs)]) def test_rows_empty_row(self): name = 'table-name' connection = None table = self._makeOne(name, connection) result = table.rows([]) self.assertEqual(result, []) def test_rows_with_columns(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() rr_result = _MockPartialRowsData() table._low_level_table.read_rows_result = rr_result self.assertEqual(rr_result.consume_all_calls, 0) # Set-up mocks. fake_col_filter = object() mock_cols = [] def mock_columns_filter_helper(*args): mock_cols.append(args) return fake_col_filter fake_rows_filter = object() mock_rows = [] def mock_row_keys_filter_helper(*args): mock_rows.append(args) return fake_rows_filter fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter rows = ['row-key'] columns = object() with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper, _row_keys_filter_helper=mock_row_keys_filter_helper, _columns_filter_helper=mock_columns_filter_helper): result = table.rows(rows, columns=columns) # read_rows_result == Empty PartialRowsData --> No results. self.assertEqual(result, []) read_rows_args = () read_rows_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_rows_calls, [ (read_rows_args, read_rows_kwargs), ]) self.assertEqual(rr_result.consume_all_calls, 1) self.assertEqual(mock_cols, [(columns,)]) self.assertEqual(mock_rows, [(rows,)]) expected_kwargs = { 'filters': [fake_col_filter, fake_rows_filter], 'versions': 1, 'timestamp': None, } self.assertEqual(mock_filters, [expected_kwargs]) def test_rows_with_results(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT from gcloud.bigtable.row_data import PartialRowData row_key1 = 'row-key1' row_key2 = 'row-key2' rows = [row_key1, row_key2] name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() row1 = PartialRowData(row_key1) # Return row1 but not row2 rr_result = _MockPartialRowsData(rows={row_key1: row1}) table._low_level_table.read_rows_result = rr_result self.assertEqual(rr_result.consume_all_calls, 0) # Set-up mocks. fake_rows_filter = object() mock_rows = [] def mock_row_keys_filter_helper(*args): mock_rows.append(args) return fake_rows_filter fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter fake_pair = object() mock_cells = [] def mock_cells_to_pairs(*args, **kwargs): mock_cells.append((args, kwargs)) return [fake_pair] col_fam = u'cf1' qual = b'qual' fake_cells = object() row1._cells = {col_fam: {qual: fake_cells}} include_timestamp = object() with _Monkey(MUT, _row_keys_filter_helper=mock_row_keys_filter_helper, _filter_chain_helper=mock_filter_chain_helper, _cells_to_pairs=mock_cells_to_pairs): result = table.rows(rows, include_timestamp=include_timestamp) # read_rows_result == PartialRowsData with row_key1 expected_result = {col_fam.encode('ascii') + b':' + qual: fake_pair} self.assertEqual(result, [(row_key1, expected_result)]) read_rows_args = () read_rows_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_rows_calls, [ (read_rows_args, read_rows_kwargs), ]) self.assertEqual(rr_result.consume_all_calls, 1) self.assertEqual(mock_rows, [(rows,)]) expected_kwargs = { 'filters': [fake_rows_filter], 'versions': 1, 'timestamp': None, } self.assertEqual(mock_filters, [expected_kwargs]) to_pairs_kwargs = {'include_timestamp': include_timestamp} self.assertEqual(mock_cells, [((fake_cells,), to_pairs_kwargs)]) def test_cells_empty_row(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() table._low_level_table.read_row_result = None # Set-up mocks. fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter row_key = 'row-key' column = 'fam:col1' with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper): result = table.cells(row_key, column) # read_row_result == None --> No results. self.assertEqual(result, []) read_row_args = (row_key,) read_row_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_row_calls, [ (read_row_args, read_row_kwargs), ]) expected_kwargs = { 'column': column, 'versions': None, 'timestamp': None, } self.assertEqual(mock_filters, [expected_kwargs]) def test_cells_with_results(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT from gcloud.bigtable.row_data import PartialRowData row_key = 'row-key' name = 'table-name' connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() partial_row = PartialRowData(row_key) table._low_level_table.read_row_result = partial_row # These are all passed to mocks. versions = object() timestamp = object() include_timestamp = object() # Set-up mocks. fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter fake_result = object() mock_cells = [] def mock_cells_to_pairs(*args, **kwargs): mock_cells.append((args, kwargs)) return fake_result col_fam = 'cf1' qual = 'qual' fake_cells = object() partial_row._cells = {col_fam: {qual: fake_cells}} column = col_fam + ':' + qual with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper, _cells_to_pairs=mock_cells_to_pairs): result = table.cells(row_key, column, versions=versions, timestamp=timestamp, include_timestamp=include_timestamp) self.assertEqual(result, fake_result) read_row_args = (row_key,) read_row_kwargs = {'filter_': fake_filter} self.assertEqual(table._low_level_table.read_row_calls, [ (read_row_args, read_row_kwargs), ]) filter_kwargs = { 'column': column, 'versions': versions, 'timestamp': timestamp, } self.assertEqual(mock_filters, [filter_kwargs]) to_pairs_kwargs = {'include_timestamp': include_timestamp} self.assertEqual(mock_cells, [((fake_cells,), to_pairs_kwargs)]) def test_scan_with_batch_size(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT warned = [] def mock_warn(msg): warned.append(msg) name = 'table-name' connection = None table = self._makeOne(name, connection) # Use unknown to force a TypeError, so we don't need to # stub out the rest of the method. with self.assertRaises(TypeError): with _Monkey(MUT, _WARN=mock_warn): list(table.scan(batch_size=object(), unknown=None)) self.assertEqual(len(warned), 1) self.assertIn('batch_size', warned[0]) def test_scan_with_scan_batching(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT warned = [] def mock_warn(msg): warned.append(msg) name = 'table-name' connection = None table = self._makeOne(name, connection) # Use unknown to force a TypeError, so we don't need to # stub out the rest of the method. with self.assertRaises(TypeError): with _Monkey(MUT, _WARN=mock_warn): list(table.scan(scan_batching=object(), unknown=None)) self.assertEqual(len(warned), 1) self.assertIn('scan_batching', warned[0]) def test_scan_with_sorted_columns(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT warned = [] def mock_warn(msg): warned.append(msg) name = 'table-name' connection = None table = self._makeOne(name, connection) # Use unknown to force a TypeError, so we don't need to # stub out the rest of the method. with self.assertRaises(TypeError): with _Monkey(MUT, _WARN=mock_warn): list(table.scan(sorted_columns=object(), unknown=None)) self.assertEqual(len(warned), 1) self.assertIn('sorted_columns', warned[0]) def test_scan_with_invalid_limit(self): name = 'table-name' connection = None table = self._makeOne(name, connection) with self.assertRaises(ValueError): list(table.scan(limit=-10)) def test_scan_with_row_prefix_and_row_start(self): name = 'table-name' connection = None table = self._makeOne(name, connection) with self.assertRaises(ValueError): list(table.scan(row_prefix='a', row_stop='abc')) def test_scan_with_string_filter(self): name = 'table-name' connection = None table = self._makeOne(name, connection) with self.assertRaises(TypeError): list(table.scan(filter='some-string')) def _scan_test_helper(self, row_limits=(None, None), row_prefix=None, columns=None, filter_=None, timestamp=None, include_timestamp=False, limit=None, rr_result=None, expected_result=None): import types from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' row_start, row_stop = row_limits connection = None table = self._makeOne(name, connection) table._low_level_table = _MockLowLevelTable() rr_result = rr_result or _MockPartialRowsData() table._low_level_table.read_rows_result = rr_result self.assertEqual(rr_result.consume_next_calls, 0) # Set-up mocks. fake_col_filter = object() mock_columns = [] def mock_columns_filter_helper(*args): mock_columns.append(args) return fake_col_filter fake_filter = object() mock_filters = [] def mock_filter_chain_helper(**kwargs): mock_filters.append(kwargs) return fake_filter with _Monkey(MUT, _filter_chain_helper=mock_filter_chain_helper, _columns_filter_helper=mock_columns_filter_helper): result = table.scan(row_start=row_start, row_stop=row_stop, row_prefix=row_prefix, columns=columns, filter=filter_, timestamp=timestamp, include_timestamp=include_timestamp, limit=limit) self.assertTrue(isinstance(result, types.GeneratorType)) # Need to consume the result while the monkey patch is applied. # read_rows_result == Empty PartialRowsData --> No results. expected_result = expected_result or [] self.assertEqual(list(result), expected_result) read_rows_args = () if row_prefix: row_start = row_prefix row_stop = MUT._string_successor(row_prefix) read_rows_kwargs = { 'end_key': row_stop, 'filter_': fake_filter, 'limit': limit, 'start_key': row_start, } self.assertEqual(table._low_level_table.read_rows_calls, [ (read_rows_args, read_rows_kwargs), ]) self.assertEqual(rr_result.consume_next_calls, rr_result.iterations + 1) if columns is not None: self.assertEqual(mock_columns, [(columns,)]) else: self.assertEqual(mock_columns, []) filters = [] if filter_ is not None: filters.append(filter_) if columns: filters.append(fake_col_filter) expected_kwargs = { 'filters': filters, 'versions': 1, 'timestamp': timestamp, } self.assertEqual(mock_filters, [expected_kwargs]) def test_scan_with_columns(self): columns = object() self._scan_test_helper(columns=columns) def test_scan_with_row_start_and_stop(self): row_start = 'bar' row_stop = 'foo' row_limits = (row_start, row_stop) self._scan_test_helper(row_limits=row_limits) def test_scan_with_row_prefix(self): row_prefix = 'row-prefi' self._scan_test_helper(row_prefix=row_prefix) def test_scan_with_filter(self): mock_filter = object() self._scan_test_helper(filter_=mock_filter) def test_scan_with_no_results(self): limit = 1337 timestamp = object() self._scan_test_helper(timestamp=timestamp, limit=limit) def test_scan_with_results(self): from gcloud.bigtable.row_data import PartialRowData row_key1 = 'row-key1' row1 = PartialRowData(row_key1) rr_result = _MockPartialRowsData(rows={row_key1: row1}, iterations=1) include_timestamp = object() expected_result = [(row_key1, {})] self._scan_test_helper(include_timestamp=include_timestamp, rr_result=rr_result, expected_result=expected_result) def test_put(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT from gcloud.bigtable.happybase.table import _WAL_SENTINEL name = 'table-name' connection = None table = self._makeOne(name, connection) batches_created = [] def make_batch(*args, **kwargs): result = _MockBatch(*args, **kwargs) batches_created.append(result) return result row = 'row-key' data = {'fam:col': 'foo'} timestamp = None with _Monkey(MUT, Batch=make_batch): result = table.put(row, data, timestamp=timestamp) # There is no return value. self.assertEqual(result, None) # Check how the batch was created and used. batch, = batches_created self.assertTrue(isinstance(batch, _MockBatch)) self.assertEqual(batch.args, (table,)) expected_kwargs = { 'timestamp': timestamp, 'batch_size': None, 'transaction': False, 'wal': _WAL_SENTINEL, } self.assertEqual(batch.kwargs, expected_kwargs) # Make sure it was a successful context manager self.assertEqual(batch.exit_vals, [(None, None, None)]) self.assertEqual(batch.put_args, [(row, data)]) self.assertEqual(batch.delete_args, []) def test_delete(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT from gcloud.bigtable.happybase.table import _WAL_SENTINEL name = 'table-name' connection = None table = self._makeOne(name, connection) batches_created = [] def make_batch(*args, **kwargs): result = _MockBatch(*args, **kwargs) batches_created.append(result) return result row = 'row-key' columns = ['fam:col1', 'fam:col2'] timestamp = None with _Monkey(MUT, Batch=make_batch): result = table.delete(row, columns=columns, timestamp=timestamp) # There is no return value. self.assertEqual(result, None) # Check how the batch was created and used. batch, = batches_created self.assertTrue(isinstance(batch, _MockBatch)) self.assertEqual(batch.args, (table,)) expected_kwargs = { 'timestamp': timestamp, 'batch_size': None, 'transaction': False, 'wal': _WAL_SENTINEL, } self.assertEqual(batch.kwargs, expected_kwargs) # Make sure it was a successful context manager self.assertEqual(batch.exit_vals, [(None, None, None)]) self.assertEqual(batch.put_args, []) self.assertEqual(batch.delete_args, [(row, columns)]) def test_batch(self): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import table as MUT name = 'table-name' connection = None table = self._makeOne(name, connection) timestamp = object() batch_size = 42 transaction = False # Must be False when batch_size is non-null wal = object() with _Monkey(MUT, Batch=_MockBatch): result = table.batch(timestamp=timestamp, batch_size=batch_size, transaction=transaction, wal=wal) self.assertTrue(isinstance(result, _MockBatch)) self.assertEqual(result.args, (table,)) expected_kwargs = { 'timestamp': timestamp, 'batch_size': batch_size, 'transaction': transaction, 'wal': wal, } self.assertEqual(result.kwargs, expected_kwargs) def test_counter_get(self): klass = self._getTargetClass() counter_value = 1337 class TableWithInc(klass): incremented = [] value = counter_value def counter_inc(self, row, column, value=1): self.incremented.append((row, column, value)) self.value += value return self.value name = 'table-name' connection = None table = TableWithInc(name, connection) row = 'row-key' column = 'fam:col1' self.assertEqual(TableWithInc.incremented, []) result = table.counter_get(row, column) self.assertEqual(result, counter_value) self.assertEqual(TableWithInc.incremented, [(row, column, 0)]) def test_counter_dec(self): klass = self._getTargetClass() counter_value = 42 class TableWithInc(klass): incremented = [] value = counter_value def counter_inc(self, row, column, value=1): self.incremented.append((row, column, value)) self.value += value return self.value name = 'table-name' connection = None table = TableWithInc(name, connection) row = 'row-key' column = 'fam:col1' dec_value = 987 self.assertEqual(TableWithInc.incremented, []) result = table.counter_dec(row, column, value=dec_value) self.assertEqual(result, counter_value - dec_value) self.assertEqual(TableWithInc.incremented, [(row, column, -dec_value)]) def _counter_inc_helper(self, row, column, value, commit_result): import six name = 'table-name' connection = None table = self._makeOne(name, connection) # Mock the return values. table._low_level_table = _MockLowLevelTable() table._low_level_table.row_values[row] = row_obj = _MockLowLevelRow( row, commit_result=commit_result) self.assertFalse(row_obj._append) result = table.counter_inc(row, column, value=value) self.assertTrue(row_obj._append) incremented_value = value + _MockLowLevelRow.COUNTER_DEFAULT self.assertEqual(result, incremented_value) # Check the row values returned. row_obj = table._low_level_table.row_values[row] if isinstance(column, six.binary_type): column = column.decode('utf-8') self.assertEqual(row_obj.counts, {tuple(column.split(':')): incremented_value}) def test_counter_set(self): name = 'table-name' connection = None table = self._makeOne(name, connection) row = 'row-key' column = 'fam:col1' value = 42 with self.assertRaises(NotImplementedError): table.counter_set(row, column, value=value) def test_counter_inc(self): import struct row = 'row-key' col_fam = u'fam' col_qual = u'col1' column = col_fam + u':' + col_qual value = 42 packed_value = struct.pack('>q', value) fake_timestamp = None commit_result = { col_fam: { col_qual: [(packed_value, fake_timestamp)], } } self._counter_inc_helper(row, column, value, commit_result) def test_counter_inc_column_bytes(self): import struct row = 'row-key' col_fam = b'fam' col_qual = b'col1' column = col_fam + b':' + col_qual value = 42 packed_value = struct.pack('>q', value) fake_timestamp = None commit_result = { col_fam.decode('utf-8'): { col_qual.decode('utf-8'): [(packed_value, fake_timestamp)], } } self._counter_inc_helper(row, column, value, commit_result) def test_counter_inc_bad_result(self): row = 'row-key' col_fam = 'fam' col_qual = 'col1' column = col_fam + ':' + col_qual value = 42 commit_result = None with self.assertRaises(TypeError): self._counter_inc_helper(row, column, value, commit_result) def test_counter_inc_result_key_error(self): row = 'row-key' col_fam = 'fam' col_qual = 'col1' column = col_fam + ':' + col_qual value = 42 commit_result = {} with self.assertRaises(KeyError): self._counter_inc_helper(row, column, value, commit_result) def test_counter_inc_result_nested_key_error(self): row = 'row-key' col_fam = 'fam' col_qual = 'col1' column = col_fam + ':' + col_qual value = 42 commit_result = {col_fam: {}} with self.assertRaises(KeyError): self._counter_inc_helper(row, column, value, commit_result) def test_counter_inc_result_non_unique_cell(self): row = 'row-key' col_fam = 'fam' col_qual = 'col1' column = col_fam + ':' + col_qual value = 42 fake_timestamp = None packed_value = None commit_result = { col_fam: { col_qual: [ (packed_value, fake_timestamp), (packed_value, fake_timestamp), ], } } with self.assertRaises(ValueError): self._counter_inc_helper(row, column, value, commit_result) class Test__gc_rule_to_dict(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import _gc_rule_to_dict return _gc_rule_to_dict(*args, **kwargs) def test_with_null(self): gc_rule = None result = self._callFUT(gc_rule) self.assertEqual(result, {}) def test_with_max_versions(self): from gcloud.bigtable.column_family import MaxVersionsGCRule max_versions = 2 gc_rule = MaxVersionsGCRule(max_versions) result = self._callFUT(gc_rule) expected_result = {'max_versions': max_versions} self.assertEqual(result, expected_result) def test_with_max_age(self): import datetime from gcloud.bigtable.column_family import MaxAgeGCRule time_to_live = 101 max_age = datetime.timedelta(seconds=time_to_live) gc_rule = MaxAgeGCRule(max_age) result = self._callFUT(gc_rule) expected_result = {'time_to_live': time_to_live} self.assertEqual(result, expected_result) def test_with_non_gc_rule(self): gc_rule = object() result = self._callFUT(gc_rule) self.assertTrue(result is gc_rule) def test_with_gc_rule_union(self): from gcloud.bigtable.column_family import GCRuleUnion gc_rule = GCRuleUnion(rules=[]) result = self._callFUT(gc_rule) self.assertTrue(result is gc_rule) def test_with_intersection_other_than_two(self): from gcloud.bigtable.column_family import GCRuleIntersection gc_rule = GCRuleIntersection(rules=[]) result = self._callFUT(gc_rule) self.assertTrue(result is gc_rule) def test_with_intersection_two_max_num_versions(self): from gcloud.bigtable.column_family import GCRuleIntersection from gcloud.bigtable.column_family import MaxVersionsGCRule rule1 = MaxVersionsGCRule(1) rule2 = MaxVersionsGCRule(2) gc_rule = GCRuleIntersection(rules=[rule1, rule2]) result = self._callFUT(gc_rule) self.assertTrue(result is gc_rule) def test_with_intersection_two_rules(self): import datetime from gcloud.bigtable.column_family import GCRuleIntersection from gcloud.bigtable.column_family import MaxAgeGCRule from gcloud.bigtable.column_family import MaxVersionsGCRule time_to_live = 101 max_age = datetime.timedelta(seconds=time_to_live) rule1 = MaxAgeGCRule(max_age) max_versions = 2 rule2 = MaxVersionsGCRule(max_versions) gc_rule = GCRuleIntersection(rules=[rule1, rule2]) result = self._callFUT(gc_rule) expected_result = { 'max_versions': max_versions, 'time_to_live': time_to_live, } self.assertEqual(result, expected_result) def test_with_intersection_two_nested_rules(self): from gcloud.bigtable.column_family import GCRuleIntersection rule1 = GCRuleIntersection(rules=[]) rule2 = GCRuleIntersection(rules=[]) gc_rule = GCRuleIntersection(rules=[rule1, rule2]) result = self._callFUT(gc_rule) self.assertTrue(result is gc_rule) class Test__string_successor(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import _string_successor return _string_successor(*args, **kwargs) def test_with_alphanumeric(self): self.assertEqual(self._callFUT(b'boa'), b'bob') self.assertEqual(self._callFUT(b'abc1'), b'abc2') def test_with_last_byte(self): self.assertEqual(self._callFUT(b'boa\xff'), b'bob') def test_with_empty_string(self): self.assertEqual(self._callFUT(b''), b'') def test_with_all_last_bytes(self): self.assertEqual(self._callFUT(b'\xff\xff\xff'), b'') def test_with_unicode_input(self): self.assertEqual(self._callFUT(u'boa'), b'bob') class Test__convert_to_time_range(unittest2.TestCase): def _callFUT(self, timestamp=None): from gcloud.bigtable.happybase.table import _convert_to_time_range return _convert_to_time_range(timestamp=timestamp) def test_null(self): timestamp = None result = self._callFUT(timestamp=timestamp) self.assertEqual(result, None) def test_invalid_type(self): timestamp = object() with self.assertRaises(TypeError): self._callFUT(timestamp=timestamp) def test_success(self): from gcloud._helpers import _datetime_from_microseconds from gcloud.bigtable.row_filters import TimestampRange timestamp = 1441928298571 ts_dt = _datetime_from_microseconds(1000 * timestamp) result = self._callFUT(timestamp=timestamp) self.assertTrue(isinstance(result, TimestampRange)) self.assertEqual(result.start, None) self.assertEqual(result.end, ts_dt) class Test__cells_to_pairs(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import _cells_to_pairs return _cells_to_pairs(*args, **kwargs) def test_without_timestamp(self): from gcloud.bigtable.row_data import Cell value1 = 'foo' cell1 = Cell(value=value1, timestamp=None) value2 = 'bar' cell2 = Cell(value=value2, timestamp=None) result = self._callFUT([cell1, cell2]) self.assertEqual(result, [value1, value2]) def test_with_timestamp(self): from gcloud._helpers import _datetime_from_microseconds from gcloud.bigtable.row_data import Cell value1 = 'foo' ts1_millis = 1221934570148 ts1 = _datetime_from_microseconds(ts1_millis * 1000) cell1 = Cell(value=value1, timestamp=ts1) value2 = 'bar' ts2_millis = 1221955575548 ts2 = _datetime_from_microseconds(ts2_millis * 1000) cell2 = Cell(value=value2, timestamp=ts2) result = self._callFUT([cell1, cell2], include_timestamp=True) self.assertEqual(result, [(value1, ts1_millis), (value2, ts2_millis)]) class Test__partial_row_to_dict(unittest2.TestCase): def _callFUT(self, partial_row_data, include_timestamp=False): from gcloud.bigtable.happybase.table import _partial_row_to_dict return _partial_row_to_dict(partial_row_data, include_timestamp=include_timestamp) def test_without_timestamp(self): from gcloud.bigtable.row_data import Cell from gcloud.bigtable.row_data import PartialRowData row_data = PartialRowData(b'row-key') val1 = b'hi-im-bytes' val2 = b'bi-im-hytes' row_data._cells[u'fam1'] = { b'col1': [Cell(val1, None)], b'col2': [Cell(val2, None)], } result = self._callFUT(row_data) expected_result = { b'fam1:col1': val1, b'fam1:col2': val2, } self.assertEqual(result, expected_result) def test_with_timestamp(self): from gcloud._helpers import _datetime_from_microseconds from gcloud.bigtable.row_data import Cell from gcloud.bigtable.row_data import PartialRowData row_data = PartialRowData(b'row-key') val1 = b'hi-im-bytes' ts1_millis = 1221934570148 ts1 = _datetime_from_microseconds(ts1_millis * 1000) val2 = b'bi-im-hytes' ts2_millis = 1331934880000 ts2 = _datetime_from_microseconds(ts2_millis * 1000) row_data._cells[u'fam1'] = { b'col1': [Cell(val1, ts1)], b'col2': [Cell(val2, ts2)], } result = self._callFUT(row_data, include_timestamp=True) expected_result = { b'fam1:col1': (val1, ts1_millis), b'fam1:col2': (val2, ts2_millis), } self.assertEqual(result, expected_result) class Test__filter_chain_helper(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import _filter_chain_helper return _filter_chain_helper(*args, **kwargs) def test_no_filters(self): with self.assertRaises(ValueError): self._callFUT() def test_single_filter(self): from gcloud.bigtable.row_filters import CellsColumnLimitFilter versions = 1337 result = self._callFUT(versions=versions) self.assertTrue(isinstance(result, CellsColumnLimitFilter)) # Relies on the fact that RowFilter instances can # only have one value set. self.assertEqual(result.num_cells, versions) def test_existing_filters(self): from gcloud.bigtable.row_filters import CellsColumnLimitFilter filters = [] versions = 1337 result = self._callFUT(versions=versions, filters=filters) # Make sure filters has grown. self.assertEqual(filters, [result]) self.assertTrue(isinstance(result, CellsColumnLimitFilter)) # Relies on the fact that RowFilter instances can # only have one value set. self.assertEqual(result.num_cells, versions) def _column_helper(self, num_filters, versions=None, timestamp=None, column=None, col_fam=None, qual=None): from gcloud.bigtable.row_filters import ColumnQualifierRegexFilter from gcloud.bigtable.row_filters import FamilyNameRegexFilter from gcloud.bigtable.row_filters import RowFilterChain if col_fam is None: col_fam = 'cf1' if qual is None: qual = 'qual' if column is None: column = col_fam + ':' + qual result = self._callFUT(column, versions=versions, timestamp=timestamp) self.assertTrue(isinstance(result, RowFilterChain)) self.assertEqual(len(result.filters), num_filters) fam_filter = result.filters[0] qual_filter = result.filters[1] self.assertTrue(isinstance(fam_filter, FamilyNameRegexFilter)) self.assertTrue(isinstance(qual_filter, ColumnQualifierRegexFilter)) # Relies on the fact that RowFilter instances can # only have one value set. self.assertEqual(fam_filter.regex, col_fam.encode('utf-8')) self.assertEqual(qual_filter.regex, qual.encode('utf-8')) return result def test_column_only(self): self._column_helper(num_filters=2) def test_column_bytes(self): self._column_helper(num_filters=2, column=b'cfB:qualY', col_fam=u'cfB', qual=u'qualY') def test_column_unicode(self): self._column_helper(num_filters=2, column=u'cfU:qualN', col_fam=u'cfU', qual=u'qualN') def test_with_versions(self): from gcloud.bigtable.row_filters import CellsColumnLimitFilter versions = 11 result = self._column_helper(num_filters=3, versions=versions) version_filter = result.filters[2] self.assertTrue(isinstance(version_filter, CellsColumnLimitFilter)) # Relies on the fact that RowFilter instances can # only have one value set. self.assertEqual(version_filter.num_cells, versions) def test_with_timestamp(self): from gcloud._helpers import _datetime_from_microseconds from gcloud.bigtable.row_filters import TimestampRange from gcloud.bigtable.row_filters import TimestampRangeFilter timestamp = 1441928298571 result = self._column_helper(num_filters=3, timestamp=timestamp) range_filter = result.filters[2] self.assertTrue(isinstance(range_filter, TimestampRangeFilter)) # Relies on the fact that RowFilter instances can # only have one value set. time_range = range_filter.range_ self.assertTrue(isinstance(time_range, TimestampRange)) self.assertEqual(time_range.start, None) ts_dt = _datetime_from_microseconds(1000 * timestamp) self.assertEqual(time_range.end, ts_dt) def test_with_all_options(self): versions = 11 timestamp = 1441928298571 self._column_helper(num_filters=4, versions=versions, timestamp=timestamp) class Test__columns_filter_helper(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import _columns_filter_helper return _columns_filter_helper(*args, **kwargs) def test_no_columns(self): columns = [] with self.assertRaises(ValueError): self._callFUT(columns) def test_single_column(self): from gcloud.bigtable.row_filters import FamilyNameRegexFilter col_fam = 'cf1' columns = [col_fam] result = self._callFUT(columns) expected_result = FamilyNameRegexFilter(col_fam) self.assertEqual(result, expected_result) def test_column_and_column_families(self): from gcloud.bigtable.row_filters import ColumnQualifierRegexFilter from gcloud.bigtable.row_filters import FamilyNameRegexFilter from gcloud.bigtable.row_filters import RowFilterChain from gcloud.bigtable.row_filters import RowFilterUnion col_fam1 = 'cf1' col_fam2 = 'cf2' col_qual2 = 'qual2' columns = [col_fam1, col_fam2 + ':' + col_qual2] result = self._callFUT(columns) self.assertTrue(isinstance(result, RowFilterUnion)) self.assertEqual(len(result.filters), 2) filter1 = result.filters[0] filter2 = result.filters[1] self.assertTrue(isinstance(filter1, FamilyNameRegexFilter)) self.assertEqual(filter1.regex, col_fam1.encode('utf-8')) self.assertTrue(isinstance(filter2, RowFilterChain)) filter2a, filter2b = filter2.filters self.assertTrue(isinstance(filter2a, FamilyNameRegexFilter)) self.assertEqual(filter2a.regex, col_fam2.encode('utf-8')) self.assertTrue(isinstance(filter2b, ColumnQualifierRegexFilter)) self.assertEqual(filter2b.regex, col_qual2.encode('utf-8')) class Test__row_keys_filter_helper(unittest2.TestCase): def _callFUT(self, *args, **kwargs): from gcloud.bigtable.happybase.table import _row_keys_filter_helper return _row_keys_filter_helper(*args, **kwargs) def test_no_rows(self): row_keys = [] with self.assertRaises(ValueError): self._callFUT(row_keys) def test_single_row(self): from gcloud.bigtable.row_filters import RowKeyRegexFilter row_key = b'row-key' row_keys = [row_key] result = self._callFUT(row_keys) expected_result = RowKeyRegexFilter(row_key) self.assertEqual(result, expected_result) def test_many_rows(self): from gcloud.bigtable.row_filters import RowFilterUnion from gcloud.bigtable.row_filters import RowKeyRegexFilter row_key1 = b'row-key1' row_key2 = b'row-key2' row_key3 = b'row-key3' row_keys = [row_key1, row_key2, row_key3] result = self._callFUT(row_keys) filter1 = RowKeyRegexFilter(row_key1) filter2 = RowKeyRegexFilter(row_key2) filter3 = RowKeyRegexFilter(row_key3) expected_result = RowFilterUnion(filters=[filter1, filter2, filter3]) self.assertEqual(result, expected_result) class _Connection(object): def __init__(self, instance): self._instance = instance class _MockLowLevelColumnFamily(object): def __init__(self, column_family_id, gc_rule=None): self.column_family_id = column_family_id self.gc_rule = gc_rule class _MockLowLevelTable(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs self.list_column_families_calls = 0 self.column_families = {} self.row_values = {} self.read_row_calls = [] self.read_row_result = None self.read_rows_calls = [] self.read_rows_result = None def list_column_families(self): self.list_column_families_calls += 1 return self.column_families def row(self, row_key, append=None): result = self.row_values[row_key] result._append = append return result def read_row(self, *args, **kwargs): self.read_row_calls.append((args, kwargs)) return self.read_row_result def read_rows(self, *args, **kwargs): self.read_rows_calls.append((args, kwargs)) return self.read_rows_result class _MockLowLevelRow(object): COUNTER_DEFAULT = 0 def __init__(self, row_key, commit_result=None): self.row_key = row_key self._append = False self.counts = {} self.commit_result = commit_result def increment_cell_value(self, column_family_id, column, int_value): count = self.counts.setdefault((column_family_id, column), self.COUNTER_DEFAULT) self.counts[(column_family_id, column)] = count + int_value def commit(self): return self.commit_result class _MockBatch(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs self.exit_vals = [] self.put_args = [] self.delete_args = [] def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.exit_vals.append((exc_type, exc_value, traceback)) def put(self, *args): self.put_args.append(args) def delete(self, *args): self.delete_args.append(args) class _MockPartialRowsData(object): def __init__(self, rows=None, iterations=0): self.rows = rows or {} self.consume_all_calls = 0 self.consume_next_calls = 0 self.iterations = iterations def consume_all(self): self.consume_all_calls += 1 def consume_next(self): self.consume_next_calls += 1 if self.consume_next_calls > self.iterations: raise StopIteration