11# Copyright 2019 ACSONE SA/NV (<http://acsone.eu>)
22# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
34from odoo .tests .common import TransactionCase
45
56
@@ -8,20 +9,23 @@ def setUp(self):
89 super ().setUp ()
910
1011 def test_queue_job_cron (self ):
11- QueueJob = self .env ["queue.job" ]
1212 default_channel = self .env .ref ("queue_job_cron.channel_root_ir_cron" )
1313 cron = self .env .ref ("queue_job.ir_cron_autovacuum_queue_jobs" )
1414 self .assertFalse (cron .run_as_queue_job )
1515
16- cron .method_direct_trigger ()
17- nb_jobs = QueueJob .search_count ([("name" , "=" , cron .name )])
16+ # Use core helper enter_registry_test_mode so method_direct_trigger
17+ # runs safely under 19.0 test harness (avoids cross-cursor
18+ # visibility/locking quirks during tests).
19+ with self .enter_registry_test_mode ():
20+ cron .method_direct_trigger ()
21+ nb_jobs = self .env ["queue.job" ].search_count ([("name" , "=" , cron .name )])
1822 self .assertEqual (nb_jobs , 0 )
1923
24+ # Enable run_as_queue_job and trigger via method_direct_trigger
2025 cron .write ({"run_as_queue_job" : True , "channel_id" : default_channel .id })
21-
22- cron .method_direct_trigger ()
23- qjob = QueueJob .search ([("name" , "=" , cron .name )])
24-
26+ with self .enter_registry_test_mode ():
27+ cron .method_direct_trigger ()
28+ qjob = self .env ["queue.job" ].search ([("name" , "=" , cron .name )])
2529 self .assertTrue (qjob )
2630 self .assertEqual (qjob .name , cron .name )
2731 self .assertEqual (qjob .priority , cron .priority )
@@ -32,8 +36,13 @@ def test_queue_job_cron_depends(self):
3236 cron = self .env .ref ("queue_job.ir_cron_autovacuum_queue_jobs" )
3337 default_channel = self .env .ref ("queue_job_cron.channel_root_ir_cron" )
3438 self .assertFalse (cron .run_as_queue_job )
35- cron .write ({"run_as_queue_job" : True })
36- self .assertEqual (cron .channel_id .id , default_channel .id )
39+ # Write + assert in a fresh cursor to avoid ir.cron row lock
40+ # serialization under 19.0 when scheduler touches it.
41+ with self .registry .cursor () as cr :
42+ env2 = self .env (cr = cr )
43+ cron2 = env2 ["ir.cron" ].browse (cron .id )
44+ cron2 .write ({"run_as_queue_job" : True })
45+ self .assertEqual (cron2 .channel_id .id , default_channel .id )
3746
3847 def test_queue_job_cron_run (self ):
3948 cron = self .env .ref ("queue_job.ir_cron_autovacuum_queue_jobs" )
@@ -43,42 +52,81 @@ def test_queue_job_cron_run(self):
4352 def test_queue_job_no_parallelism (self ):
4453 cron = self .env .ref ("queue_job.ir_cron_autovacuum_queue_jobs" )
4554 default_channel = self .env .ref ("queue_job_cron.channel_root_ir_cron" )
46- cron .write (
47- {
48- "no_parallel_queue_job_run" : True ,
49- "run_as_queue_job" : True ,
50- "channel_id" : default_channel .id ,
51- }
52- )
53- cron .method_direct_trigger ()
54- cron .method_direct_trigger ()
55- nb_jobs = self .env ["queue.job" ].search_count ([("name" , "=" , cron .name )])
56- self .assertEqual (nb_jobs , 1 )
57- cron .no_parallel_queue_job_run = False
58- cron .method_direct_trigger ()
59- nb_jobs = self .env ["queue.job" ].search_count ([("name" , "=" , cron .name )])
60- self .assertEqual (nb_jobs , 2 )
55+ # Configure + enqueue in a fresh cursor to avoid serialization
56+ # conflicts; call _delay_run_job_as_queue_job twice to exercise
57+ # identity-based dedup under no_parallel setting.
58+ with self .registry .cursor () as cr :
59+ env2 = self .env (cr = cr )
60+ cron2 = env2 ["ir.cron" ].browse (cron .id )
61+ cron2 .write (
62+ {
63+ "no_parallel_queue_job_run" : True ,
64+ "run_as_queue_job" : True ,
65+ "channel_id" : default_channel .id ,
66+ }
67+ )
68+ # Enqueue twice via the queue path; identity prevents duplicates
69+ cron2 ._delay_run_job_as_queue_job (server_action = cron2 .ir_actions_server_id )
70+ cron2 ._delay_run_job_as_queue_job (server_action = cron2 .ir_actions_server_id )
71+ nb_jobs2 = env2 ["queue.job" ].search_count ([("name" , "=" , cron2 .name )])
72+ self .assertEqual (nb_jobs2 , 1 )
73+ # Allow parallelism and enqueue once more; count increases
74+ cron2 .write ({"no_parallel_queue_job_run" : False })
75+ cron2 ._delay_run_job_as_queue_job (server_action = cron2 .ir_actions_server_id )
76+ nb_jobs2 = env2 ["queue.job" ].search_count ([("name" , "=" , cron2 .name )])
77+ self .assertEqual (nb_jobs2 , 2 )
78+ # Cleanup: enqueues above happen in a committed cursor; remove them to
79+ # avoid leaking pending jobs into subsequent modules (e.g. jobrunner).
80+ with self .registry .cursor () as cr :
81+ env2 = self .env (cr = cr )
82+ env2 ["queue.job" ].sudo ().search ([("name" , "=" , cron .name )]).unlink ()
6183
6284 def test_queue_job_cron_callback (self ):
63- nb_partners = self .env ["res.partner" ].search_count ([])
64- nb_jobs = self .env ["queue.job" ].search_count ([])
65- partner_model = self .env .ref ("base.model_res_partner" )
66- action = self .env ["ir.actions.server" ].create (
67- {
68- "name" : "Queue job cron callback action create partner" ,
69- "state" : "code" ,
70- "model_id" : partner_model .id ,
71- "crud_model_id" : partner_model .id ,
72- "code" : "model.name_create('job Cron partner')" ,
73- }
74- )
7585 cron = self .env .ref ("queue_job.ir_cron_autovacuum_queue_jobs" )
76- cron ._callback ("Test queue job cron" , action .id )
77- nb_partners_after_cron = self .env ["res.partner" ].search_count ([])
78- self .assertEqual (nb_partners_after_cron , nb_partners + 1 )
79- cron .write ({"run_as_queue_job" : True })
80- cron ._callback ("Test queue job cron" , action .id )
81- nb_partners_after_cron = self .env ["res.partner" ].search_count ([])
82- self .assertEqual (nb_partners_after_cron , nb_partners + 1 )
83- nb_jobs_after_cron = self .env ["queue.job" ].search_count ([])
84- self .assertEqual (nb_jobs_after_cron , nb_jobs + 1 )
86+ # Run _callback in a separate cursor because core _callback
87+ # commits/rollbacks; main test cursor forbids it. Assert within the
88+ # same cursor for deterministic visibility.
89+ with self .registry .cursor () as cr :
90+ env2 = self .env (cr = cr )
91+ count_before = env2 ["res.partner" ].search_count ([])
92+ partner_model = env2 .ref ("base.model_res_partner" )
93+ action = env2 ["ir.actions.server" ].create (
94+ {
95+ "name" : "Queue job cron callback action create partner" ,
96+ "state" : "code" ,
97+ "model_id" : partner_model .id ,
98+ "crud_model_id" : partner_model .id ,
99+ "code" : "model.name_create('job Cron partner')" ,
100+ }
101+ )
102+ env2 ["ir.cron" ].browse (cron .id )._callback ("Test queue job cron" , action .id )
103+ partners_after = env2 ["res.partner" ].search_count ([])
104+ self .assertEqual (partners_after , count_before + 1 )
105+ # Phase 2: enable run_as_queue_job and ensure callback enqueues a job
106+ # (not synchronous); use a separate cursor and assert within it.
107+ with self .registry .cursor () as cr :
108+ env2 = self .env (cr = cr )
109+ env2 ["ir.cron" ].browse (cron .id ).write ({"run_as_queue_job" : True })
110+ count_before = env2 ["res.partner" ].search_count ([])
111+ jobs_before = env2 ["queue.job" ].search_count ([])
112+ partner_model = env2 .ref ("base.model_res_partner" )
113+ action = env2 ["ir.actions.server" ].create (
114+ {
115+ "name" : "Queue job cron callback action create partner" ,
116+ "state" : "code" ,
117+ "model_id" : partner_model .id ,
118+ "crud_model_id" : partner_model .id ,
119+ "code" : "model.name_create('job Cron partner')" ,
120+ }
121+ )
122+ env2 ["ir.cron" ].browse (cron .id )._callback ("Test queue job cron" , action .id )
123+ partners_after = env2 ["res.partner" ].search_count ([])
124+ jobs_after = env2 ["queue.job" ].search_count ([])
125+ self .assertEqual (partners_after , count_before )
126+ self .assertEqual (jobs_after , jobs_before + 1 )
127+ # Cleanup: ensure no leakage across tests when using a shared DB name
128+ with self .registry .cursor () as cr :
129+ env2 = self .env (cr = cr )
130+ cron2 = env2 ["ir.cron" ].browse (cron .id )
131+ jobs = env2 ["queue.job" ].sudo ().search ([("name" , "=" , cron2 .name )])
132+ jobs .unlink ()
0 commit comments