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,36 @@ 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 has been reset
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 pg_object .list () == [], (
48+ f"Did not delete all { description } . "
49+ f"Elapsed_time: { time .perf_counter () - start_time } "
50+ )
51+
52+ ensure_list_empty (pg_object = gl .projects , description = "projects" )
53+ ensure_list_empty (pg_object = gl .groups , description = "groups" )
54+ ensure_list_empty (pg_object = gl .variables , description = "variables" )
55+
56+ for _ in range (max_iterations ):
57+ if len (gl .users .list ()) <= 1 :
58+ break
59+ time .sleep (sleep_interval )
60+ assert len (gl .users .list ()) <= 1 , (
61+ f"Did not delete all users. "
62+ f"elapsed_time: { time .perf_counter () - start_time } "
63+ )
64+
3365
3466def set_token (container , fixture_dir ):
3567 set_token_rb = fixture_dir / "set_token.rb"
@@ -105,25 +137,59 @@ def _check(container):
105137 return _check
106138
107139
140+ @dataclasses .dataclass
141+ class WaitSidekiq :
142+ iterations : int
143+ step : float
144+ elapsed_time : float
145+ success : bool
146+
147+
108148@pytest .fixture
109- def wait_for_sidekiq (gl ):
149+ def wait_for_sidekiq (gl : gitlab .Gitlab ):
150+ """
151+ Return a helper function to wait until there are no busy sidekiq processes.
152+
153+ Use this with asserts for slow tasks (group/project/user creation/deletion).
154+ """
155+ return _wait_for_sidekiq (gl = gl )
156+
157+
158+ def _wait_for_sidekiq (gl : gitlab .Gitlab ):
110159 """
111160 Return a helper function to wait until there are no busy sidekiq processes.
112161
113162 Use this with asserts for slow tasks (group/project/user creation/deletion).
114163 """
115164
116- def _wait (timeout = 30 , step = 0.5 ):
117- for _ in range (timeout ):
165+ def _wait (
166+ timeout : int = 60 ,
167+ step : float = 0.1 ,
168+ ) -> WaitSidekiq :
169+ # timeout is approximately the timeout in seconds
170+ max_iterations = int (timeout / step )
171+ start_time = time .perf_counter ()
172+ success = False
173+ for count in range (max_iterations ):
174+ print (f"_wait: count: { count } " )
118175 time .sleep (step )
119176 busy = False
120177 processes = gl .sidekiq .process_metrics ()["processes" ]
121178 for process in processes :
179+ print (f"_wait: process['busy']: { process ['busy' ]} " )
122180 if process ["busy" ]:
123181 busy = True
124182 if not busy :
125- return True
126- return False
183+ success = True
184+ break
185+ result = WaitSidekiq (
186+ iterations = count ,
187+ step = step ,
188+ elapsed_time = time .perf_counter () - start_time ,
189+ success = success ,
190+ )
191+ print (f"_wait: { result } " )
192+ return result
127193
128194 return _wait
129195
@@ -194,6 +260,73 @@ def gitlab_runner(gl):
194260 check_output (docker_exec + unregister ).decode ()
195261
196262
263+ @pytest .fixture
264+ def ensure_raises_exception ():
265+ """
266+ Return a helper function to wait until specified exception is raised
267+
268+ Use this with asserts for slow tasks (group/project/user creation/deletion).
269+ """
270+ return _ensure_raises_exception_wrapped ()
271+
272+
273+ def _ensure_raises_exception (
274+ * ,
275+ func ,
276+ exception : Exception ,
277+ args : List [Any ],
278+ kwargs : Dict [str , Any ],
279+ description : str ,
280+ ) -> None :
281+ """Ensure the exception is specified when calling `func`. If no exception is raided
282+ after timeout period, fail the test"""
283+ sleep_interval = 0.1
284+ timeout = 60
285+ max_iterations = int (timeout / sleep_interval )
286+
287+ for _ in range (max_iterations ):
288+ try :
289+ func (* args , ** kwargs )
290+ except exception :
291+ return
292+ time .sleep (sleep_interval )
293+ pytest .fail (f"{ description } did not raise exception { exception } " )
294+
295+
296+ def _ensure_raises_exception_wrapped ():
297+ return _ensure_raises_exception
298+
299+
300+ @pytest .fixture
301+ def ensure_deleted ():
302+ """
303+ Return a helper function to wait until specified object is deleted
304+
305+ Use this with asserts for slow tasks (group/project/user creation/deletion).
306+ """
307+ return _ensure_deleted_wrapped ()
308+
309+
310+ def _ensure_deleted (* , pg_object , object_id : int , description : str ) -> None :
311+ """Ensure the object specified can not be retrieved. If object still exists after
312+ timeout period, fail the test"""
313+ sleep_interval = 0.1
314+ timeout = 60
315+ max_iterations = int (timeout / sleep_interval )
316+
317+ for _ in range (max_iterations ):
318+ try :
319+ pg_object .get (object_id )
320+ except gitlab .exceptions .GitlabGetError :
321+ return
322+ time .sleep (sleep_interval )
323+ pytest .fail (f"{ description } { object_id } was not deleted" )
324+
325+
326+ def _ensure_deleted_wrapped ():
327+ return _ensure_deleted
328+
329+
197330@pytest .fixture (scope = "module" )
198331def group (gl ):
199332 """Group fixture for group API resource tests."""
@@ -203,6 +336,7 @@ def group(gl):
203336 "path" : f"group-{ _id } " ,
204337 }
205338 group = gl .groups .create (data )
339+ group_id = group .id
206340
207341 yield group
208342
@@ -211,6 +345,8 @@ def group(gl):
211345 except gitlab .exceptions .GitlabDeleteError as e :
212346 print (f"Group already deleted: { e } " )
213347
348+ _ensure_deleted (pg_object = gl .groups , object_id = group_id , description = "Group" )
349+
214350
215351@pytest .fixture (scope = "module" )
216352def project (gl ):
@@ -219,6 +355,7 @@ def project(gl):
219355 name = f"test-project-{ _id } "
220356
221357 project = gl .projects .create (name = name )
358+ project_id = project .id
222359
223360 yield project
224361
@@ -227,6 +364,8 @@ def project(gl):
227364 except gitlab .exceptions .GitlabDeleteError as e :
228365 print (f"Project already deleted: { e } " )
229366
367+ _ensure_deleted (pg_object = gl .projects , object_id = project_id , description = "Project" )
368+
230369
231370@pytest .fixture (scope = "function" )
232371def merge_request (project , wait_for_sidekiq ):
@@ -249,8 +388,10 @@ def _merge_request(*, source_branch: str):
249388 # NOTE(jlvillal): Sometimes the CI would give a "500 Internal Server
250389 # Error". Hoping that waiting until all other processes are done will
251390 # help with that.
252- result = wait_for_sidekiq (timeout = 60 )
253- assert result is True , "sidekiq process should have terminated but did not"
391+ result = wait_for_sidekiq ()
392+ assert (
393+ result .success is True
394+ ), f"sidekiq process should have terminated but did not: { result } "
254395
255396 project .refresh () # Gets us the current default branch
256397 project .branches .create (
@@ -274,8 +415,10 @@ def _merge_request(*, source_branch: str):
274415 "remove_source_branch" : True ,
275416 }
276417 )
277- result = wait_for_sidekiq (timeout = 60 )
278- assert result is True , "sidekiq process should have terminated but did not"
418+ result = wait_for_sidekiq ()
419+ assert (
420+ result .success is True
421+ ), f"sidekiq process should have terminated but did not: { result } "
279422
280423 mr_iid = mr .iid
281424 for _ in range (60 ):
@@ -298,6 +441,18 @@ def _merge_request(*, source_branch: str):
298441 # Ignore if branch was already deleted
299442 pass
300443
444+ for mr_iid , source_branch in to_delete :
445+ _ensure_deleted (
446+ pg_object = project .mergerequests ,
447+ object_id = mr_iid ,
448+ description = "Project mergerequest" ,
449+ )
450+ _ensure_deleted (
451+ pg_object = project .branches ,
452+ object_id = source_branch ,
453+ description = "Project branch" ,
454+ )
455+
301456
302457@pytest .fixture (scope = "module" )
303458def project_file (project ):
@@ -342,6 +497,7 @@ def user(gl):
342497 password = "fakepassword"
343498
344499 user = gl .users .create (email = email , username = username , name = name , password = password )
500+ user_id = user .id
345501
346502 yield user
347503
@@ -350,6 +506,8 @@ def user(gl):
350506 except gitlab .exceptions .GitlabDeleteError as e :
351507 print (f"User already deleted: { e } " )
352508
509+ _ensure_deleted (pg_object = gl .users , object_id = user_id , description = "User" )
510+
353511
354512@pytest .fixture (scope = "module" )
355513def issue (project ):
0 commit comments