87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
import threading
|
||
|
from concurrent.futures import ThreadPoolExecutor
|
||
|
|
||
|
from psycopg2 import IntegrityError
|
||
|
|
||
|
import odoo
|
||
|
from odoo.tests.common import get_db_name, tagged, BaseCase
|
||
|
from odoo.tools import mute_logger
|
||
|
|
||
|
|
||
|
@tagged('-standard', '-at_install', 'post_install', 'database_breaking')
|
||
|
class TestOnboardingConcurrency(BaseCase):
|
||
|
|
||
|
@classmethod
|
||
|
def setUpClass(cls):
|
||
|
super().setUpClass()
|
||
|
cls.registry = odoo.registry(get_db_name())
|
||
|
cls.addClassCleanup(cls.cleanUpClass)
|
||
|
|
||
|
with cls.registry.cursor() as cr:
|
||
|
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||
|
cls.onboarding_id = env['onboarding.onboarding'].create([
|
||
|
{
|
||
|
'name': 'Test Onboarding Concurrent',
|
||
|
'is_per_company': False,
|
||
|
'route_name': 'onboarding_concurrent'
|
||
|
}
|
||
|
]).id
|
||
|
|
||
|
@classmethod
|
||
|
def cleanUpClass(cls):
|
||
|
with cls.registry.cursor() as cr:
|
||
|
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||
|
env['onboarding.onboarding'].browse(cls.onboarding_id).unlink()
|
||
|
env['onboarding.progress'].search([
|
||
|
('onboarding_id', '=', cls.onboarding_id)
|
||
|
]).unlink()
|
||
|
|
||
|
@mute_logger('odoo.sql_db')
|
||
|
def test_concurrent_create_progress(self):
|
||
|
barrier = threading.Barrier(2)
|
||
|
|
||
|
def run():
|
||
|
raised_unique_violation = False
|
||
|
|
||
|
with self.registry.cursor() as cr:
|
||
|
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||
|
onboarding = env['onboarding.onboarding'].search([
|
||
|
('id', '=', self.onboarding_id)
|
||
|
])
|
||
|
# There is no progress record
|
||
|
self.assertFalse(env['onboarding.progress'].search([
|
||
|
('onboarding_id', '=', self.onboarding_id)
|
||
|
]))
|
||
|
barrier.wait(timeout=2)
|
||
|
try:
|
||
|
onboarding._create_progress()
|
||
|
except IntegrityError as e:
|
||
|
if e.pgcode == "23505": # UniqueViolation
|
||
|
raised_unique_violation = True
|
||
|
|
||
|
return raised_unique_violation
|
||
|
|
||
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
||
|
future_1 = executor.submit(run)
|
||
|
future_2 = executor.submit(run)
|
||
|
raised_1 = future_1.result(timeout=3)
|
||
|
raised_2 = future_2.result(timeout=3)
|
||
|
|
||
|
with self.registry.cursor() as cr:
|
||
|
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||
|
self.assertEqual(
|
||
|
len(env['onboarding.progress'].search([('onboarding_id', '=', self.onboarding_id)])),
|
||
|
1,
|
||
|
"Exactly one thread should have been able to create a record."
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
raised_1 + raised_2,
|
||
|
1,
|
||
|
"Exactly one thread should have raised a UniqueViolation error even though "
|
||
|
"there was no progress record at the start of its transaction."
|
||
|
)
|