1
+ import dataclasses
1
2
import tempfile
2
3
import time
3
4
import uuid
4
5
from pathlib import Path
5
6
from subprocess import check_output
7
+ from typing import Any , Dict , List
6
8
7
9
import pytest
8
10
@@ -30,6 +32,34 @@ def reset_gitlab(gl):
30
32
if user .username != "root" :
31
33
user .delete (hard_delete = True )
32
34
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
+
33
63
34
64
def set_token (container , fixture_dir ):
35
65
set_token_rb = fixture_dir / "set_token.rb"
@@ -105,25 +135,59 @@ def _check(container):
105
135
return _check
106
136
107
137
138
+ @dataclasses .dataclass
139
+ class WaitSidekiq :
140
+ iterations : int
141
+ step : float
142
+ elapsed_time : float
143
+ success : bool
144
+
145
+
108
146
@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 ):
110
157
"""
111
158
Return a helper function to wait until there are no busy sidekiq processes.
112
159
113
160
Use this with asserts for slow tasks (group/project/user creation/deletion).
114
161
"""
115
162
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 } " )
118
173
time .sleep (step )
119
174
busy = False
120
175
processes = gl .sidekiq .process_metrics ()["processes" ]
121
176
for process in processes :
177
+ print (f"_wait: process['busy']: { process ['busy' ]} " )
122
178
if process ["busy" ]:
123
179
busy = True
124
180
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
127
191
128
192
return _wait
129
193
@@ -194,6 +258,73 @@ def gitlab_runner(gl):
194
258
check_output (docker_exec + unregister ).decode ()
195
259
196
260
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
+
197
328
@pytest .fixture (scope = "module" )
198
329
def group (gl ):
199
330
"""Group fixture for group API resource tests."""
@@ -203,6 +334,7 @@ def group(gl):
203
334
"path" : f"group-{ _id } " ,
204
335
}
205
336
group = gl .groups .create (data )
337
+ group_id = group .id
206
338
207
339
yield group
208
340
@@ -211,6 +343,8 @@ def group(gl):
211
343
except gitlab .exceptions .GitlabDeleteError as e :
212
344
print (f"Group already deleted: { e } " )
213
345
346
+ _ensure_deleted (pg_object = gl .groups , object_id = group_id , description = "Group" )
347
+
214
348
215
349
@pytest .fixture (scope = "module" )
216
350
def project (gl ):
@@ -219,6 +353,7 @@ def project(gl):
219
353
name = f"test-project-{ _id } "
220
354
221
355
project = gl .projects .create (name = name )
356
+ project_id = project .id
222
357
223
358
yield project
224
359
@@ -227,6 +362,8 @@ def project(gl):
227
362
except gitlab .exceptions .GitlabDeleteError as e :
228
363
print (f"Project already deleted: { e } " )
229
364
365
+ _ensure_deleted (pg_object = gl .projects , object_id = project_id , description = "Project" )
366
+
230
367
231
368
@pytest .fixture (scope = "function" )
232
369
def merge_request (project , wait_for_sidekiq ):
@@ -249,8 +386,10 @@ def _merge_request(*, source_branch: str):
249
386
# NOTE(jlvillal): Sometimes the CI would give a "500 Internal Server
250
387
# Error". Hoping that waiting until all other processes are done will
251
388
# 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 } "
254
393
255
394
project .refresh () # Gets us the current default branch
256
395
project .branches .create (
@@ -274,8 +413,10 @@ def _merge_request(*, source_branch: str):
274
413
"remove_source_branch" : True ,
275
414
}
276
415
)
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 } "
279
420
280
421
mr_iid = mr .iid
281
422
for _ in range (60 ):
@@ -298,6 +439,18 @@ def _merge_request(*, source_branch: str):
298
439
# Ignore if branch was already deleted
299
440
pass
300
441
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
+
301
454
302
455
@pytest .fixture (scope = "module" )
303
456
def project_file (project ):
@@ -342,6 +495,7 @@ def user(gl):
342
495
password = "fakepassword"
343
496
344
497
user = gl .users .create (email = email , username = username , name = name , password = password )
498
+ user_id = user .id
345
499
346
500
yield user
347
501
@@ -350,6 +504,8 @@ def user(gl):
350
504
except gitlab .exceptions .GitlabDeleteError as e :
351
505
print (f"User already deleted: { e } " )
352
506
507
+ _ensure_deleted (pg_object = gl .users , object_id = user_id , description = "User" )
508
+
353
509
354
510
@pytest .fixture (scope = "module" )
355
511
def issue (project ):
0 commit comments