# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests.common import TransactionCase from odoo.tools import get_cache_key_counter from threading import Thread, Barrier class TestOrmcache(TransactionCase): def test_ormcache(self): """ Test the effectiveness of the ormcache() decorator. """ IMD = self.env['ir.model.data'] XMLID = 'base.group_no_one' # retrieve the cache, its key and stat counter cache, key, counter = get_cache_key_counter(IMD._xmlid_lookup, XMLID) hit = counter.hit miss = counter.miss # clear the caches of ir.model.data, retrieve its key and self.env.registry.clear_cache() self.assertNotIn(key, cache) # lookup some reference self.env.ref(XMLID) self.assertEqual(counter.hit, hit) self.assertEqual(counter.miss, miss + 1) self.assertIn(key, cache) # lookup again self.env.ref(XMLID) self.assertEqual(counter.hit, hit + 1) self.assertEqual(counter.miss, miss + 1) self.assertIn(key, cache) # lookup again self.env.ref(XMLID) self.assertEqual(counter.hit, hit + 2) self.assertEqual(counter.miss, miss + 1) self.assertIn(key, cache) def test_invalidation(self): self.assertEqual(self.env.registry.cache_invalidated, set()) self.env.registry.clear_cache() self.env.registry.clear_cache('templates') self.assertEqual(self.env.registry.cache_invalidated, {'default', 'templates'}) self.env.registry.reset_changes() self.assertEqual(self.env.registry.cache_invalidated, set()) self.env.registry.clear_cache('assets') self.assertEqual(self.env.registry.cache_invalidated, {'assets'}) self.env.registry.reset_changes() self.assertEqual(self.env.registry.cache_invalidated, set()) def test_invalidation_thread_local(self): # this test ensures that the registry.cache_invalidated set is thread local caches = ['default', 'templates', 'assets'] nb_treads = len(caches) # use barriers to ensure threads synchronization sync_clear_cache = Barrier(nb_treads, timeout=5) sync_assert_equal = Barrier(nb_treads, timeout=5) sync_reset = Barrier(nb_treads, timeout=5) operations = [] def run(cache): self.assertEqual(self.env.registry.cache_invalidated, set()) self.env.registry.clear_cache(cache) operations.append('clear_cache') sync_clear_cache.wait() self.assertEqual(self.env.registry.cache_invalidated, {cache}) operations.append('assert_contains') sync_assert_equal.wait() self.env.registry.reset_changes() operations.append('reset_changes') sync_reset.wait() self.assertEqual(self.env.registry.cache_invalidated, set()) operations.append('assert_empty') # run all threads threads = [] for cache in caches: threads.append(Thread(target=run, args=(cache,))) for thread in threads: thread.start() for thread in threads: thread.join() # ensure that the threads operations where executed in the expected order self.assertEqual( operations, ['clear_cache'] * nb_treads + ['assert_contains'] * nb_treads + ['reset_changes'] * nb_treads + ['assert_empty'] * nb_treads ) def test_signaling_01_single(self): self.assertFalse(self.registry.test_cr) self.registry.cache_invalidated.clear() registry = self.registry old_sequences = dict(registry.cache_sequences) with self.assertLogs('odoo.modules.registry') as logs: registry.cache_invalidated.add('assets') self.assertEqual(registry.cache_invalidated, {'assets'}) registry.signal_changes() self.assertFalse(registry.cache_invalidated) self.assertEqual( logs.output, ["INFO:odoo.modules.registry:Caches invalidated, signaling through the database: ['assets']"], ) for key, value in old_sequences.items(): if key == 'assets': self.assertEqual(value + 1, registry.cache_sequences[key], "Assets cache sequence should have changed") else: self.assertEqual(value, registry.cache_sequences[key], "other registry sequence shouldn't have changed") with self.assertNoLogs(None, None): # the registry sequence should be up to date on the same worker registry.check_signaling() # simulate other worker state registry.cache_sequences.update(old_sequences) with self.assertLogs() as logs: registry.check_signaling() self.assertEqual( logs.output, ["INFO:odoo.modules.registry:Invalidating caches after database signaling: ['assets', 'templates.cached_values']"], ) def test_signaling_01_multiple(self): self.assertFalse(self.registry.test_cr) self.registry.cache_invalidated.clear() registry = self.registry old_sequences = dict(registry.cache_sequences) with self.assertLogs('odoo.modules.registry') as logs: registry.cache_invalidated.add('assets') registry.cache_invalidated.add('default') self.assertEqual(registry.cache_invalidated, {'assets', 'default'}) registry.signal_changes() self.assertFalse(registry.cache_invalidated) self.assertEqual( logs.output, [ "INFO:odoo.modules.registry:Caches invalidated, signaling through the database: ['assets', 'default']", ], ) for key, value in old_sequences.items(): if key in ('assets', 'default'): self.assertEqual(value + 1, registry.cache_sequences[key], "Assets and default cache sequence should have changed") else: self.assertEqual(value, registry.cache_sequences[key], "other registry sequence shouldn't have changed") with self.assertNoLogs(None, None): # the registry sequence should be up to date on the same worker registry.check_signaling() # simulate other worker state registry.cache_sequences.update(old_sequences) with self.assertLogs() as logs: registry.check_signaling() self.assertEqual( logs.output, ["INFO:odoo.modules.registry:Invalidating caches after database signaling: ['assets', 'default', 'templates.cached_values']"], )