# -*- coding: utf-8 -*- # # Copyright 2016 SUSE 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. ''' :codeauthor: :email:`Bo Maryniuk ` ''' # Import Python Libs from __future__ import absolute_import # Import Salt Testing Libs from salttesting import TestCase from salttesting.mock import MagicMock, patch from salttesting.helpers import ensure_in_syspath from salt.modules.inspectlib.fsdb import CsvDB from salt.modules.inspectlib.entities import CsvDBEntity from salt.utils.odict import OrderedDict from salt.ext.six.moves import StringIO ensure_in_syspath('../../') def mock_open(data=None): ''' Mock "open" function in a simple way. :param data: :return: ''' data = StringIO(data) mock = MagicMock(spec=file) handle = MagicMock(spec=file) handle.write.return_value = None handle.__enter__.return_value = data or handle mock.return_value = handle return mock class Writable(StringIO): def __init__(self, data=None): if data: StringIO.__init__(self, data) else: StringIO.__init__(self) self.data = [] def __exit__(self, exc_type, exc_val, exc_tb): return self def __enter__(self): return self def write(self, s): self.data.append(s) class FoobarEntity(CsvDBEntity): ''' Entity for test purposes. ''' _TABLE = 'some_table' def __init__(self): self.foo = 0 self.bar = '' self.spam = 0. class InspectorFSDBTestCase(TestCase): ''' Test case for the FSDB: FileSystem Database. FSDB is a very simple object-to-CSV storage with a very inefficient update/delete operations (nice to have at some point) and efficient storing/reading the objects (what is exactly needed for the functionality). Main advantage of FSDB is to store Python objects in just a CSV files, and have a very small code base. ''' @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) @patch("gzip.open", mock_open("foo:int,bar:str")) def test_open(self): ''' Test opening the database. :return: ''' csvdb = CsvDB('/foobar') csvdb.open() assert csvdb.list_tables() == ['test_db'] assert csvdb.is_closed() is False @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) @patch("gzip.open", mock_open("foo:int,bar:str")) def test_close(self): ''' Test closing the database. :return: ''' csvdb = CsvDB('/foobar') csvdb.open() csvdb.close() assert csvdb.is_closed() is True @patch("os.makedirs", MagicMock()) @patch("os.path.exists", MagicMock(return_value=False)) @patch("os.listdir", MagicMock(return_value=['some_table'])) def test_create_table(self): ''' Test creating table. :return: ''' writable = Writable() with patch("gzip.open", MagicMock(return_value=writable)): csvdb = CsvDB('/foobar') csvdb.open() csvdb.create_table_from_object(FoobarEntity()) assert writable.data[0].strip() == "foo:int,bar:str,spam:float" @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_list_databases(self): ''' Test list databases. :return: ''' csvdb = CsvDB('/foobar') assert csvdb.list() == ['test_db'] @patch("os.makedirs", MagicMock()) @patch("os.path.exists", MagicMock(return_value=False)) @patch("os.listdir", MagicMock(return_value=['some_table'])) def test_add_object(self): ''' Test storing object into the database. :return: ''' writable = Writable() with patch("gzip.open", MagicMock(return_value=writable)): obj = FoobarEntity() obj.foo = 123 obj.bar = 'test entity' obj.spam = 0.123 csvdb = CsvDB('/foobar') csvdb.open() csvdb._tables = {'some_table': OrderedDict([tuple(elm.split(':')) for elm in ["foo:int", "bar:str", "spam:float"]])} csvdb.store(obj) assert writable.data[0].strip() == '123,test entity,0.123' @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_delete_object(self): ''' Deleting an object from the store. :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'test', '0.123'], ['234', 'another', '0.456']]))): class InterceptedCsvDB(CsvDB): def __init__(self, path): CsvDB.__init__(self, path) self._remained = list() def store(self, obj, distinct=False): self._remained.append(obj) csvdb = InterceptedCsvDB('/foobar') csvdb.open() csvdb.create_table_from_object = MagicMock() csvdb.flush = MagicMock() assert csvdb.delete(FoobarEntity, eq={'foo': 123}) is True assert len(csvdb._remained) == 1 assert csvdb._remained[0].foo == 234 assert csvdb._remained[0].bar == 'another' assert csvdb._remained[0].spam == 0.456 @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_update_object(self): ''' Updating an object from the store. :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'test', '0.123'], ['234', 'another', '0.456']]))): obj = FoobarEntity() obj.foo = 123 obj.bar = 'updated' obj.spam = 0.5 class InterceptedCsvDB(CsvDB): def __init__(self, path): CsvDB.__init__(self, path) self._remained = list() def store(self, obj, distinct=False): self._remained.append(obj) csvdb = InterceptedCsvDB('/foobar') csvdb.open() csvdb.create_table_from_object = MagicMock() csvdb.flush = MagicMock() assert csvdb.update(obj, eq={'foo': 123}) is True assert len(csvdb._remained) == 2 assert csvdb._remained[0].foo == 123 assert csvdb._remained[0].bar == 'updated' assert csvdb._remained[0].spam == 0.5 assert csvdb._remained[1].foo == 234 assert csvdb._remained[1].bar == 'another' assert csvdb._remained[1].spam == 0.456 @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_get_object(self): ''' Getting an object from the store. :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'test', '0.123'], ['234', 'another', '0.456']]))): csvdb = CsvDB('/foobar') csvdb.open() entities = csvdb.get(FoobarEntity) assert list == type(entities) assert len(entities) == 2 assert entities[0].foo == 123 assert entities[0].bar == 'test' assert entities[0].spam == 0.123 assert entities[1].foo == 234 assert entities[1].bar == 'another' assert entities[1].spam == 0.456 @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_get_obj_equals(self): ''' Getting an object from the store with conditions :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'test', '0.123'], ['234', 'another', '0.456']]))): csvdb = CsvDB('/foobar') csvdb.open() entities = csvdb.get(FoobarEntity, eq={'foo': 123}) assert list == type(entities) assert len(entities) == 1 assert entities[0].foo == 123 assert entities[0].bar == 'test' assert entities[0].spam == 0.123 @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_get_obj_more_than(self): ''' Getting an object from the store with conditions :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'test', '0.123'], ['234', 'another', '0.456']]))): csvdb = CsvDB('/foobar') csvdb.open() entities = csvdb.get(FoobarEntity, mt={'foo': 123}) assert list == type(entities) assert len(entities) == 1 assert entities[0].foo == 234 assert entities[0].bar == 'another' assert entities[0].spam == 0.456 @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_get_obj_less_than(self): ''' Getting an object from the store with conditions :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'test', '0.123'], ['234', 'another', '0.456']]))): csvdb = CsvDB('/foobar') csvdb.open() entities = csvdb.get(FoobarEntity, lt={'foo': 234}) assert list == type(entities) assert len(entities) == 1 assert entities[0].foo == 123 assert entities[0].bar == 'test' assert entities[0].spam == 0.123 @patch("os.makedirs", MagicMock()) @patch("os.listdir", MagicMock(return_value=['test_db'])) def test_get_obj_matching(self): ''' Getting an object from the store with conditions :return: ''' with patch("gzip.open", MagicMock()): with patch("csv.reader", MagicMock(return_value=iter([[], ['foo:int', 'bar:str', 'spam:float'], ['123', 'this is test of something', '0.123'], ['234', 'another test of stuff', '0.456']]))): csvdb = CsvDB('/foobar') csvdb.open() entities = csvdb.get(FoobarEntity, matches={'bar': r'is\stest'}) assert list == type(entities) assert len(entities) == 1 assert entities[0].foo == 123 assert entities[0].bar == 'this is test of something' assert entities[0].spam == 0.123 def test_obj_serialization(self): ''' Test object serialization. :return: ''' obj = FoobarEntity() obj.foo = 123 obj.bar = 'test entity' obj.spam = 0.123 descr = OrderedDict([tuple(elm.split(':')) for elm in ["foo:int", "bar:str", "spam:float"]]) assert obj._serialize(descr) == [123, 'test entity', 0.123] @patch("os.makedirs", MagicMock()) @patch("os.path.exists", MagicMock(return_value=False)) @patch("os.listdir", MagicMock(return_value=['some_table'])) def test_obj_validation(self): ''' Test object validation. :return: ''' obj = FoobarEntity() obj.foo = 123 obj.bar = 'test entity' obj.spam = 0.123 csvdb = CsvDB('/foobar') csvdb._tables = {'some_table': OrderedDict([tuple(elm.split(':')) for elm in ["foo:int", "bar:str", "spam:float"]])} assert csvdb._validate_object(obj) == [123, 'test entity', 0.123] @patch("os.makedirs", MagicMock()) @patch("os.path.exists", MagicMock(return_value=False)) @patch("os.listdir", MagicMock(return_value=['some_table'])) def test_criteria(self): ''' Test criteria selector. :return: ''' obj = FoobarEntity() obj.foo = 123 obj.bar = 'test entity' obj.spam = 0.123 obj.pi = 3.14 cmp = CsvDB('/foobar')._CsvDB__criteria # Single assert cmp(obj, eq={'foo': 123}) is True assert cmp(obj, lt={'foo': 124}) is True assert cmp(obj, mt={'foo': 122}) is True assert cmp(obj, eq={'foo': 0}) is False assert cmp(obj, lt={'foo': 123}) is False assert cmp(obj, mt={'foo': 123}) is False assert cmp(obj, matches={'bar': r't\se.*?'}) is True assert cmp(obj, matches={'bar': r'\s\sentity'}) is False # Combined assert cmp(obj, eq={'foo': 123, 'bar': r'test entity', 'spam': 0.123}) is True assert cmp(obj, eq={'foo': 123, 'bar': r'test', 'spam': 0.123}) is False assert cmp(obj, lt={'foo': 124, 'spam': 0.124}) is True assert cmp(obj, lt={'foo': 124, 'spam': 0.123}) is False assert cmp(obj, mt={'foo': 122, 'spam': 0.122}) is True assert cmp(obj, mt={'foo': 122, 'spam': 0.123}) is False assert cmp(obj, matches={'bar': r'test'}, mt={'foo': 122}, lt={'spam': 0.124}, eq={'pi': 3.14}) is True assert cmp(obj, matches={'bar': r'^test.*?y$'}, mt={'foo': 122}, lt={'spam': 0.124}, eq={'pi': 3.14}) is True assert cmp(obj, matches={'bar': r'^ent'}, mt={'foo': 122}, lt={'spam': 0.124}, eq={'pi': 3.14}) is False assert cmp(obj, matches={'bar': r'^test.*?y$'}, mt={'foo': 123}, lt={'spam': 0.124}, eq={'pi': 3.14}) is False