def test_del(self): with mock.patch('anki.storage.Collection') as anki_storage_Collection: col = anki_storage_Collection.return_value wrapper = CollectionWrapper(self.collection_path) wrapper.open() wrapper = None col.close.assert_called_with()
def __init__(self, path, setup_new_collection=None): self.path = path self.wrapper = CollectionWrapper(path, setup_new_collection) self._queue = Queue() self._thread = None self._running = False self.last_timestamp = time.time() self.start()
def test_setup_func(self): # Run it when the collection doesn't exist with mock.patch('anki.storage.Collection') as anki_storage_Collection: col = anki_storage_Collection.return_value setup_new_collection = MagicMock() self.assertFalse(os.path.exists(self.collection_path)) wrapper = CollectionWrapper(self.collection_path, setup_new_collection) wrapper.open() anki_storage_Collection.assert_called_with(self.collection_path) setup_new_collection.assert_called_with(col) wrapper = None # Make sure that no collection was actually created self.assertFalse(os.path.exists(self.collection_path)) # Create a faux collection file with file(self.collection_path, 'wt') as fd: fd.write('Collection!') # Run it when the collection does exist with mock.patch('anki.storage.Collection'): setup_new_collection = lambda col: self.fail("Setup function called when collection already exists!") self.assertTrue(os.path.exists(self.collection_path)) wrapper = CollectionWrapper(self.collection_path, setup_new_collection) wrapper.open() anki_storage_Collection.assert_called_with(self.collection_path) wrapper = None
def test_execute(self): with mock.patch('anki.storage.Collection') as anki_storage_Collection: col = anki_storage_Collection.return_value func = MagicMock() func.return_value = sentinel.some_object # check that execute works and auto-creates the collection wrapper = CollectionWrapper(self.collection_path) ret = wrapper.execute(func, [1, 2, 3], {'key': 'aoeu'}) self.assertEqual(ret, sentinel.some_object) anki_storage_Collection.assert_called_with(self.collection_path) func.assert_called_with(col, 1, 2, 3, key='aoeu') # check that execute always returns False if waitForReturn=False func.reset_mock() ret = wrapper.execute(func, [1, 2, 3], {'key': 'aoeu'}, waitForReturn=False) self.assertEqual(ret, None) func.assert_called_with(col, 1, 2, 3, key='aoeu')
def test_lifecycle(self): with mock.patch('AnkiServer.collection.CollectionManager.collection_wrapper') as CollectionWrapper: wrapper = MagicMock() CollectionWrapper.return_value = wrapper manager = CollectionManager() # check getting a new collection ret = manager.get_collection('path1') CollectionWrapper.assert_called_with(os.path.realpath('path1'), None) self.assertEqual(ret, wrapper) # change the return value, so that it would return a new object new_wrapper = MagicMock() CollectionWrapper.return_value = new_wrapper CollectionWrapper.reset_mock() # get the new wrapper ret = manager.get_collection('path2') CollectionWrapper.assert_called_with(os.path.realpath('path2'), None) self.assertEqual(ret, new_wrapper) # make sure the wrapper and new_wrapper are different self.assertNotEqual(wrapper, new_wrapper) # assert that calling with the first path again, returns the first wrapper ret = manager.get_collection('path1') self.assertEqual(ret, wrapper) manager.shutdown() wrapper.close.assert_called_with() new_wrapper.close.assert_called_with()
class ThreadingCollectionWrapper(object): """Provides the same interface as CollectionWrapper, but it creates a new Thread to interact with the collection.""" def __init__(self, path, setup_new_collection=None): self.path = path self.wrapper = CollectionWrapper(path, setup_new_collection) self._queue = Queue() self._thread = None self._running = False self.last_timestamp = time.time() self.start() @property def running(self): return self._running def qempty(self): return self._queue.empty() def current(self): from threading import current_thread return current_thread() == self._thread def execute(self, func, args=[], kw={}, waitForReturn=True): """ Executes a given function on this thread with the *args and **kw. If 'waitForReturn' is True, then it will block until the function has executed and return its return value. If False, it will return None immediately and the function will be executed sometime later. """ if waitForReturn: return_queue = Queue() else: return_queue = None self._queue.put((func, args, kw, return_queue)) if return_queue is not None: ret = return_queue.get(True) if isinstance(ret, Exception): raise ret return ret def _run(self): logging.info('CollectionThread[%s]: Starting...', self.path) try: while self._running: func, args, kw, return_queue = self._queue.get(True) if hasattr(func, 'func_name'): func_name = func.func_name else: func_name = func.__class__.__name__ logging.info('CollectionThread[%s]: Running %s(*%s, **%s)', self.path, func_name, repr(args), repr(kw)) self.last_timestamp = time.time() try: ret = self.wrapper.execute(func, args, kw, return_queue) except Exception as e: logging.error( 'CollectionThread[%s]: Unable to %s(*%s, **%s): %s', self.path, func_name, repr(args), repr(kw), e, exc_info=True) # we return the Exception which will be raise'd on the other end ret = e if return_queue is not None: return_queue.put(ret) except Exception as e: logging.error( 'CollectionThread[%s]: Thread crashed! Exception: %s', self.path, e, exc_info=True) finally: self.wrapper.close() # clean out old thread object self._thread = None # in case we got here via an exception self._running = False logging.info('CollectionThread[%s]: Stopped!', self.path) def start(self): if not self._running: self._running = True assert self._thread is None self._thread = Thread(target=self._run) self._thread.start() def stop(self): def _stop(col): self._running = False self.execute(_stop, waitForReturn=False) def stop_and_wait(self): """ Tell the thread to stop and wait for it to happen. """ self.stop() if self._thread is not None: self._thread.join() # # Mimic the CollectionWrapper interface # def open(self): """Non-op. The collection will be opened on demand.""" pass def close(self): """Closes the underlying collection without stopping the thread.""" def _close(col): self.wrapper.close() self.execute(_close, waitForReturn=False) def opened(self): return self.wrapper.opened()
def test_lifecycle_real(self): """Testing common life-cycle with existing and non-existant collections. This test uses the real Anki objects and actually creates a new collection on disk.""" wrapper = CollectionWrapper(self.collection_path) self.assertFalse(os.path.exists(self.collection_path)) self.assertFalse(wrapper.opened()) wrapper.open() self.assertTrue(os.path.exists(self.collection_path)) self.assertTrue(wrapper.opened()) # calling open twice shouldn't break anything wrapper.open() wrapper.close() self.assertTrue(os.path.exists(self.collection_path)) self.assertFalse(wrapper.opened()) # open the same collection again (not a creation) wrapper = CollectionWrapper(self.collection_path) self.assertFalse(wrapper.opened()) wrapper.open() self.assertTrue(wrapper.opened()) wrapper.close() self.assertFalse(wrapper.opened()) self.assertTrue(os.path.exists(self.collection_path))