1+ import dataclasses
12import tempfile
23import time
34import uuid
45from pathlib import Path
56from subprocess import check_output
7+ from typing import Any , Dict , List
68
79import pytest
810
@@ -30,6 +32,34 @@ def reset_gitlab(gl):
3032 if user .username != "root" :
3133 user .delete (hard_delete = True )
3234
35+ sleep_interval = 0.1
36+ timeout = 60
37+ max_iterations = int (timeout / sleep_interval )
38+
39+ # Ensure everything is deleted.
40+ start_time = time .perf_counter ()
41+
42+ def ensure_list_empty (pg_object , description : str ) -> None :
43+ for _ in range (max_iterations ):
44+ if pg_object .list () == []:
45+ break
46+ time .sleep (sleep_interval )
47+ assert (
48+ pg_object .list () == []
49+ ), f"Did not delete all { description } . Elapsed_time: { time .perf_counter () - start_time } "
50+
51+ ensure_list_empty (pg_object = gl .projects , description = "projects" )
52+ ensure_list_empty (pg_object = gl .groups , description = "groups" )
53+ ensure_list_empty (pg_object = gl .variables , description = "variables" )
54+
55+ for _ in range (max_iterations ):
56+ if len (gl .users .list ()) <= 1 :
57+ break
58+ time .sleep (sleep_interval )
59+ assert (
60+ len (gl .users .list ()) <= 1
61+ ), f"elapsed_time: { time .perf_counter () - start_time } "
62+
3363
3464def set_token (container , fixture_dir ):
3565 set_token_rb = fixture_dir / "set_token.rb"
@@ -105,25 +135,59 @@ def _check(container):
105135 return _check
106136
107137
138+ @dataclasses .dataclass
139+ class WaitSidekiq :
140+ iterations : int
141+ step : float
142+ elapsed_time : float
143+ success : bool
144+
145+
108146@pytest .fixture
109- def wait_for_sidekiq (gl ):
147+ def wait_for_sidekiq (gl : gitlab .Gitlab ):
148+ """
149+ Return a helper function to wait until there are no busy sidekiq processes.
150+
151+ Use this with asserts for slow tasks (group/project/user creation/deletion).
152+ """
153+ return _wait_for_sidekiq (gl = gl )
154+
155+
156+ def _wait_for_sidekiq (gl : gitlab .Gitlab ):
110157 """
111158 Return a helper function to wait until there are no busy sidekiq processes.
112159
113160 Use this with asserts for slow tasks (group/project/user creation/deletion).
114161 """
115162
116- def _wait (timeout = 30 , step = 0.5 ):
117- for _ in range (timeout ):
163+ def _wait (
164+ timeout : int = 60 ,
165+ step : float = 0.1 ,
166+ ) -> WaitSidekiq :
167+ # timeout is approximately the timeout in seconds
168+ max_iterations = int (timeout / step )
169+ start_time = time .perf_counter ()
170+ success = False
171+ for count in range (max_iterations ):
172+ print (f"_wait: count: { count } " )
118173 time .sleep (step )
119174 busy = False
120175 processes = gl .sidekiq .process_metrics ()["processes" ]
121176 for process in processes :
177+ print (f"_wait: process['busy']: { process ['busy' ]} " )
122178 if process ["busy" ]:
123179 busy = True
124180 if not busy :
125- return True
126- return False
181+ success = True
182+ break
183+ result = WaitSidekiq (
184+ iterations = count ,
185+ step = step ,
186+ elapsed_time = time .perf_counter () - start_time ,
187+ success = success ,
188+ )
189+ print (f"_wait: { result } " )
190+ return result
127191
128192 return _wait
129193
@@ -194,6 +258,73 @@ def gitlab_runner(gl):
194258 check_output (docker_exec + unregister ).decode ()
195259
196260
261+ @pytest .fixture
262+ def ensure_raises_exception ():
263+ """
264+ Return a helper function to wait until specified exception is raised
265+
266+ Use this with asserts for slow tasks (group/project/user creation/deletion).
267+ """
268+ return _ensure_raises_exception_wrapped ()
269+
270+
271+ def _ensure_raises_exception (
272+ * ,
273+ func ,
274+ exception : Exception ,
275+ args : List [Any ],
276+ kwargs : Dict [str , Any ],
277+ description : str ,
278+ ) -> None :
279+ """Ensure the exception is specified when calling `func`. If no exception is raided
280+ after timeout period, fail the test"""
281+ sleep_interval = 0.1
282+ timeout = 60
283+ max_iterations = int (timeout / sleep_interval )
284+
285+ for _ in range (max_iterations ):
286+ try :
287+ func (* args , ** kwargs )
288+ except exception :
289+ return
290+ time .sleep (sleep_interval )
291+ pytest .fail (f"{ description } did not raise exception { exception } " )
292+
293+
294+ def _ensure_raises_exception_wrapped ():
295+ return _ensure_raises_exception
296+
297+
298+ @pytest .fixture
299+ def ensure_deleted ():
300+ """
301+ Return a helper function to wait until specified object is deleted
302+
303+ Use this with asserts for slow tasks (group/project/user creation/deletion).
304+ """
305+ return _ensure_deleted_wrapped ()
306+
307+
308+ def _ensure_deleted (* , pg_object , object_id : int , description : str ) -> None :
309+ """Ensure the object specified can not be retrieved. If object still exists after
310+ timeout period, fail the test"""
311+ sleep_interval = 0.1
312+ timeout = 60
313+ max_iterations = int (timeout / sleep_interval )
314+
315+ for _ in range (max_iterations ):
316+ try :
317+ pg_object .get (object_id )
318+ except gitlab .exceptions .GitlabGetError :
319+ return
320+ time .sleep (sleep_interval )
321+ pytest .fail (f"{ description } { object_id } was not deleted" )
322+
323+
324+ def _ensure_deleted_wrapped ():
325+ return _ensure_deleted
326+
327+
197328@pytest .fixture (scope = "module" )
198329def group (gl ):
199330 """Group fixture for group API resource tests."""
@@ -203,6 +334,7 @@ def group(gl):
203334 "path" : f"group-{ _id } " ,
204335 }
205336 group = gl .groups .create (data )
337+ group_id = group .id
206338
207339 yield group
208340
@@ -211,6 +343,8 @@ def group(gl):
211343 except gitlab .exceptions .GitlabDeleteError as e :
212344 print (f"Group already deleted: { e } " )
213345
346+ _ensure_deleted (pg_object = gl .groups , object_id = group_id , description = "Group" )
347+
214348
215349@pytest .fixture (scope = "module" )
216350def project (gl ):
@@ -219,6 +353,7 @@ def project(gl):
219353 name = f"test-project-{ _id } "
220354
221355 project = gl .projects .create (name = name )
356+ project_id = project .id
222357
223358 yield project
224359
@@ -227,6 +362,8 @@ def project(gl):
227362 except gitlab .exceptions .GitlabDeleteError as e :
228363 print (f"Project already deleted: { e } " )
229364
365+ _ensure_deleted (pg_object = gl .projects , object_id = project_id , description = "Project" )
366+
230367
231368@pytest .fixture (scope = "function" )
232369def merge_request (project , wait_for_sidekiq ):
@@ -249,8 +386,10 @@ def _merge_request(*, source_branch: str):
249386 # NOTE(jlvillal): Sometimes the CI would give a "500 Internal Server
250387 # Error". Hoping that waiting until all other processes are done will
251388 # help with that.
252- result = wait_for_sidekiq (timeout = 60 )
253- assert result is True , "sidekiq process should have terminated but did not"
389+ result = wait_for_sidekiq ()
390+ assert (
391+ result .success is True
392+ ), f"sidekiq process should have terminated but did not: { result } "
254393
255394 project .refresh () # Gets us the current default branch
256395 project .branches .create (
@@ -274,8 +413,10 @@ def _merge_request(*, source_branch: str):
274413 "remove_source_branch" : True ,
275414 }
276415 )
277- result = wait_for_sidekiq (timeout = 60 )
278- assert result is True , "sidekiq process should have terminated but did not"
416+ result = wait_for_sidekiq ()
417+ assert (
418+ result .success is True
419+ ), f"sidekiq process should have terminated but did not: { result } "
279420
280421 mr_iid = mr .iid
281422 for _ in range (60 ):
@@ -298,6 +439,18 @@ def _merge_request(*, source_branch: str):
298439 # Ignore if branch was already deleted
299440 pass
300441
442+ for mr_iid , source_branch in to_delete :
443+ _ensure_deleted (
444+ pg_object = project .mergerequests ,
445+ object_id = mr_iid ,
446+ description = "Project mergerequest" ,
447+ )
448+ _ensure_deleted (
449+ pg_object = project .branches ,
450+ object_id = source_branch ,
451+ description = "Project branch" ,
452+ )
453+
301454
302455@pytest .fixture (scope = "module" )
303456def project_file (project ):
@@ -342,6 +495,7 @@ def user(gl):
342495 password = "fakepassword"
343496
344497 user = gl .users .create (email = email , username = username , name = name , password = password )
498+ user_id = user .id
345499
346500 yield user
347501
@@ -350,6 +504,8 @@ def user(gl):
350504 except gitlab .exceptions .GitlabDeleteError as e :
351505 print (f"User already deleted: { e } " )
352506
507+ _ensure_deleted (pg_object = gl .users , object_id = user_id , description = "User" )
508+
353509
354510@pytest .fixture (scope = "module" )
355511def issue (project ):
0 commit comments