xref: /spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c (revision 26ae3d66d89392c108a30e405ca8424617a03417)
1 /*-
2  *   BSD LICENSE
3  *
4  *   Copyright (c) Intel Corporation.
5  *   All rights reserved.
6  *
7  *   Redistribution and use in source and binary forms, with or without
8  *   modification, are permitted provided that the following conditions
9  *   are met:
10  *
11  *     * Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *     * Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided with the
16  *       distribution.
17  *     * Neither the name of Intel Corporation nor the names of its
18  *       contributors may be used to endorse or promote products derived
19  *       from this software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include "spdk/stdinc.h"
35 
36 #include "spdk/blobfs.h"
37 #include "spdk/env.h"
38 #include "spdk/log.h"
39 #include "spdk/thread.h"
40 #include "spdk/barrier.h"
41 #include "spdk_internal/thread.h"
42 
43 #include "spdk_cunit.h"
44 #include "unit/lib/blob/bs_dev_common.c"
45 #include "common/lib/test_env.c"
46 #include "blobfs/blobfs.c"
47 #include "blobfs/tree.c"
48 
49 struct spdk_filesystem *g_fs;
50 struct spdk_file *g_file;
51 int g_fserrno;
52 struct spdk_thread *g_dispatch_thread = NULL;
53 struct spdk_trace_histories *g_trace_histories;
54 DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn));
55 DEFINE_STUB_V(spdk_trace_register_description, (const char *name,
56 		uint16_t tpoint_id, uint8_t owner_type,
57 		uint8_t object_type, uint8_t new_object,
58 		uint8_t arg1_is_ptr, const char *arg1_name));
59 DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id,
60 				   uint32_t size, uint64_t object_id, uint64_t arg1));
61 
62 struct ut_request {
63 	fs_request_fn fn;
64 	void *arg;
65 	volatile int done;
66 };
67 
68 static void
69 send_request(fs_request_fn fn, void *arg)
70 {
71 	spdk_thread_send_msg(g_dispatch_thread, (spdk_msg_fn)fn, arg);
72 }
73 
74 static void
75 ut_call_fn(void *arg)
76 {
77 	struct ut_request *req = arg;
78 
79 	req->fn(req->arg);
80 	req->done = 1;
81 }
82 
83 static void
84 ut_send_request(fs_request_fn fn, void *arg)
85 {
86 	struct ut_request req;
87 
88 	req.fn = fn;
89 	req.arg = arg;
90 	req.done = 0;
91 
92 	spdk_thread_send_msg(g_dispatch_thread, ut_call_fn, &req);
93 
94 	/* Wait for this to finish */
95 	while (req.done == 0) {	}
96 }
97 
98 static void
99 fs_op_complete(void *ctx, int fserrno)
100 {
101 	g_fserrno = fserrno;
102 }
103 
104 static void
105 fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno)
106 {
107 	g_fs = fs;
108 	g_fserrno = fserrno;
109 }
110 
111 static void
112 fs_thread_poll(void)
113 {
114 	struct spdk_thread *thread;
115 
116 	thread = spdk_get_thread();
117 	while (spdk_thread_poll(thread, 0, 0) > 0) {}
118 	while (spdk_thread_poll(g_cache_pool_thread, 0, 0) > 0) {}
119 }
120 
121 static void
122 _fs_init(void *arg)
123 {
124 	struct spdk_bs_dev *dev;
125 
126 	g_fs = NULL;
127 	g_fserrno = -1;
128 	dev = init_dev();
129 	spdk_fs_init(dev, NULL, send_request, fs_op_with_handle_complete, NULL);
130 
131 	fs_thread_poll();
132 
133 	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
134 	SPDK_CU_ASSERT_FATAL(g_fs->bdev == dev);
135 	CU_ASSERT(g_fserrno == 0);
136 }
137 
138 static void
139 _fs_load(void *arg)
140 {
141 	struct spdk_bs_dev *dev;
142 
143 	g_fs = NULL;
144 	g_fserrno = -1;
145 	dev = init_dev();
146 	spdk_fs_load(dev, send_request, fs_op_with_handle_complete, NULL);
147 
148 	fs_thread_poll();
149 
150 	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
151 	SPDK_CU_ASSERT_FATAL(g_fs->bdev == dev);
152 	CU_ASSERT(g_fserrno == 0);
153 }
154 
155 static void
156 _fs_unload(void *arg)
157 {
158 	g_fserrno = -1;
159 	spdk_fs_unload(g_fs, fs_op_complete, NULL);
160 
161 	fs_thread_poll();
162 
163 	CU_ASSERT(g_fserrno == 0);
164 	g_fs = NULL;
165 }
166 
167 static void
168 _nop(void *arg)
169 {
170 }
171 
172 static void
173 cache_read_after_write(void)
174 {
175 	uint64_t length;
176 	int rc;
177 	char w_buf[100], r_buf[100];
178 	struct spdk_fs_thread_ctx *channel;
179 	struct spdk_file_stat stat = {0};
180 
181 	ut_send_request(_fs_init, NULL);
182 
183 	channel = spdk_fs_alloc_thread_ctx(g_fs);
184 
185 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
186 	CU_ASSERT(rc == 0);
187 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
188 
189 	length = (4 * 1024 * 1024);
190 	rc = spdk_file_truncate(g_file, channel, length);
191 	CU_ASSERT(rc == 0);
192 
193 	memset(w_buf, 0x5a, sizeof(w_buf));
194 	spdk_file_write(g_file, channel, w_buf, 0, sizeof(w_buf));
195 
196 	CU_ASSERT(spdk_file_get_length(g_file) == length);
197 
198 	rc = spdk_file_truncate(g_file, channel, sizeof(w_buf));
199 	CU_ASSERT(rc == 0);
200 
201 	spdk_file_close(g_file, channel);
202 
203 	fs_thread_poll();
204 
205 	rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat);
206 	CU_ASSERT(rc == 0);
207 	CU_ASSERT(sizeof(w_buf) == stat.size);
208 
209 	rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &g_file);
210 	CU_ASSERT(rc == 0);
211 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
212 
213 	memset(r_buf, 0, sizeof(r_buf));
214 	spdk_file_read(g_file, channel, r_buf, 0, sizeof(r_buf));
215 	CU_ASSERT(memcmp(w_buf, r_buf, sizeof(r_buf)) == 0);
216 
217 	spdk_file_close(g_file, channel);
218 
219 	fs_thread_poll();
220 
221 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
222 	CU_ASSERT(rc == 0);
223 
224 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
225 	CU_ASSERT(rc == -ENOENT);
226 
227 	spdk_fs_free_thread_ctx(channel);
228 
229 	ut_send_request(_fs_unload, NULL);
230 }
231 
232 static void
233 file_length(void)
234 {
235 	int rc;
236 	char *buf;
237 	uint64_t buf_length;
238 	volatile uint64_t *length_flushed;
239 	struct spdk_fs_thread_ctx *channel;
240 	struct spdk_file_stat stat = {0};
241 
242 	ut_send_request(_fs_init, NULL);
243 
244 	channel = spdk_fs_alloc_thread_ctx(g_fs);
245 
246 	g_file = NULL;
247 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
248 	CU_ASSERT(rc == 0);
249 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
250 
251 	/* Write one CACHE_BUFFER.  Filling at least one cache buffer triggers
252 	 * a flush to disk.
253 	 */
254 	buf_length = CACHE_BUFFER_SIZE;
255 	buf = calloc(1, buf_length);
256 	spdk_file_write(g_file, channel, buf, 0, buf_length);
257 	free(buf);
258 
259 	/* Spin until all of the data has been flushed to the SSD.  There's been no
260 	 * sync operation yet, so the xattr on the file is still 0.
261 	 *
262 	 * length_flushed: This variable is modified by a different thread in this unit
263 	 * test. So we need to dereference it as a volatile to ensure the value is always
264 	 * re-read.
265 	 */
266 	length_flushed = &g_file->length_flushed;
267 	while (*length_flushed != buf_length) {}
268 
269 	/* Close the file.  This causes an implicit sync which should write the
270 	 * length_flushed value as the "length" xattr on the file.
271 	 */
272 	spdk_file_close(g_file, channel);
273 
274 	fs_thread_poll();
275 
276 	rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat);
277 	CU_ASSERT(rc == 0);
278 	CU_ASSERT(buf_length == stat.size);
279 
280 	spdk_fs_free_thread_ctx(channel);
281 
282 	/* Unload and reload the filesystem.  The file length will be
283 	 * read during load from the length xattr.  We want to make sure
284 	 * it matches what was written when the file was originally
285 	 * written and closed.
286 	 */
287 	ut_send_request(_fs_unload, NULL);
288 
289 	ut_send_request(_fs_load, NULL);
290 
291 	channel = spdk_fs_alloc_thread_ctx(g_fs);
292 
293 	rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat);
294 	CU_ASSERT(rc == 0);
295 	CU_ASSERT(buf_length == stat.size);
296 
297 	g_file = NULL;
298 	rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &g_file);
299 	CU_ASSERT(rc == 0);
300 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
301 
302 	spdk_file_close(g_file, channel);
303 
304 	fs_thread_poll();
305 
306 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
307 	CU_ASSERT(rc == 0);
308 
309 	spdk_fs_free_thread_ctx(channel);
310 
311 	ut_send_request(_fs_unload, NULL);
312 }
313 
314 static void
315 append_write_to_extend_blob(void)
316 {
317 	uint64_t blob_size, buf_length;
318 	char *buf, append_buf[64];
319 	int rc;
320 	struct spdk_fs_thread_ctx *channel;
321 
322 	ut_send_request(_fs_init, NULL);
323 
324 	channel = spdk_fs_alloc_thread_ctx(g_fs);
325 
326 	/* create a file and write the file with blob_size - 1 data length */
327 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
328 	CU_ASSERT(rc == 0);
329 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
330 
331 	blob_size = __file_get_blob_size(g_file);
332 
333 	buf_length = blob_size - 1;
334 	buf = calloc(1, buf_length);
335 	rc = spdk_file_write(g_file, channel, buf, 0, buf_length);
336 	CU_ASSERT(rc == 0);
337 	free(buf);
338 
339 	spdk_file_close(g_file, channel);
340 	fs_thread_poll();
341 	spdk_fs_free_thread_ctx(channel);
342 	ut_send_request(_fs_unload, NULL);
343 
344 	/* load existing file and write extra 2 bytes to cross blob boundary */
345 	ut_send_request(_fs_load, NULL);
346 
347 	channel = spdk_fs_alloc_thread_ctx(g_fs);
348 	g_file = NULL;
349 	rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &g_file);
350 	CU_ASSERT(rc == 0);
351 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
352 
353 	CU_ASSERT(g_file->length == buf_length);
354 	CU_ASSERT(g_file->last == NULL);
355 	CU_ASSERT(g_file->append_pos == buf_length);
356 
357 	rc = spdk_file_write(g_file, channel, append_buf, buf_length, 2);
358 	CU_ASSERT(rc == 0);
359 	CU_ASSERT(2 * blob_size == __file_get_blob_size(g_file));
360 	spdk_file_close(g_file, channel);
361 	fs_thread_poll();
362 	CU_ASSERT(g_file->length == buf_length + 2);
363 
364 	spdk_fs_free_thread_ctx(channel);
365 	ut_send_request(_fs_unload, NULL);
366 }
367 
368 static void
369 partial_buffer(void)
370 {
371 	int rc;
372 	char *buf;
373 	uint64_t buf_length;
374 	struct spdk_fs_thread_ctx *channel;
375 	struct spdk_file_stat stat = {0};
376 
377 	ut_send_request(_fs_init, NULL);
378 
379 	channel = spdk_fs_alloc_thread_ctx(g_fs);
380 
381 	g_file = NULL;
382 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
383 	CU_ASSERT(rc == 0);
384 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
385 
386 	/* Write one CACHE_BUFFER plus one byte.  Filling at least one cache buffer triggers
387 	 * a flush to disk.  We want to make sure the extra byte is not implicitly flushed.
388 	 * It should only get flushed once we sync or close the file.
389 	 */
390 	buf_length = CACHE_BUFFER_SIZE + 1;
391 	buf = calloc(1, buf_length);
392 	spdk_file_write(g_file, channel, buf, 0, buf_length);
393 	free(buf);
394 
395 	/* Send some nop messages to the dispatch thread.  This will ensure any of the
396 	 * pending write operations are completed.  A well-functioning blobfs should only
397 	 * issue one write for the filled CACHE_BUFFER - a buggy one might try to write
398 	 * the extra byte.  So do a bunch of _nops to make sure all of them (even the buggy
399 	 * ones) get a chance to run.  Note that we can't just send a message to the
400 	 * dispatch thread to call spdk_thread_poll() because the messages are themselves
401 	 * run in the context of spdk_thread_poll().
402 	 */
403 	ut_send_request(_nop, NULL);
404 	ut_send_request(_nop, NULL);
405 	ut_send_request(_nop, NULL);
406 	ut_send_request(_nop, NULL);
407 	ut_send_request(_nop, NULL);
408 	ut_send_request(_nop, NULL);
409 
410 	CU_ASSERT(g_file->length_flushed == CACHE_BUFFER_SIZE);
411 
412 	/* Close the file.  This causes an implicit sync which should write the
413 	 * length_flushed value as the "length" xattr on the file.
414 	 */
415 	spdk_file_close(g_file, channel);
416 
417 	fs_thread_poll();
418 
419 	rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat);
420 	CU_ASSERT(rc == 0);
421 	CU_ASSERT(buf_length == stat.size);
422 
423 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
424 	CU_ASSERT(rc == 0);
425 
426 	spdk_fs_free_thread_ctx(channel);
427 
428 	ut_send_request(_fs_unload, NULL);
429 }
430 
431 static void
432 cache_write_null_buffer(void)
433 {
434 	uint64_t length;
435 	int rc;
436 	struct spdk_fs_thread_ctx *channel;
437 	struct spdk_thread *thread;
438 
439 	ut_send_request(_fs_init, NULL);
440 
441 	channel = spdk_fs_alloc_thread_ctx(g_fs);
442 
443 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
444 	CU_ASSERT(rc == 0);
445 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
446 
447 	length = 0;
448 	rc = spdk_file_truncate(g_file, channel, length);
449 	CU_ASSERT(rc == 0);
450 
451 	rc = spdk_file_write(g_file, channel, NULL, 0, 0);
452 	CU_ASSERT(rc == 0);
453 
454 	spdk_file_close(g_file, channel);
455 
456 	fs_thread_poll();
457 
458 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
459 	CU_ASSERT(rc == 0);
460 
461 	spdk_fs_free_thread_ctx(channel);
462 
463 	thread = spdk_get_thread();
464 	while (spdk_thread_poll(thread, 0, 0) > 0) {}
465 
466 	ut_send_request(_fs_unload, NULL);
467 }
468 
469 static void
470 fs_create_sync(void)
471 {
472 	int rc;
473 	struct spdk_fs_thread_ctx *channel;
474 
475 	ut_send_request(_fs_init, NULL);
476 
477 	channel = spdk_fs_alloc_thread_ctx(g_fs);
478 	CU_ASSERT(channel != NULL);
479 
480 	rc = spdk_fs_create_file(g_fs, channel, "testfile");
481 	CU_ASSERT(rc == 0);
482 
483 	/* Create should fail, because the file already exists. */
484 	rc = spdk_fs_create_file(g_fs, channel, "testfile");
485 	CU_ASSERT(rc != 0);
486 
487 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
488 	CU_ASSERT(rc == 0);
489 
490 	spdk_fs_free_thread_ctx(channel);
491 
492 	fs_thread_poll();
493 
494 	ut_send_request(_fs_unload, NULL);
495 }
496 
497 static void
498 fs_rename_sync(void)
499 {
500 	int rc;
501 	struct spdk_fs_thread_ctx *channel;
502 
503 	ut_send_request(_fs_init, NULL);
504 
505 	channel = spdk_fs_alloc_thread_ctx(g_fs);
506 	CU_ASSERT(channel != NULL);
507 
508 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
509 	CU_ASSERT(rc == 0);
510 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
511 
512 	CU_ASSERT(strcmp(spdk_file_get_name(g_file), "testfile") == 0);
513 
514 	rc = spdk_fs_rename_file(g_fs, channel, "testfile", "newtestfile");
515 	CU_ASSERT(rc == 0);
516 	CU_ASSERT(strcmp(spdk_file_get_name(g_file), "newtestfile") == 0);
517 
518 	spdk_file_close(g_file, channel);
519 
520 	fs_thread_poll();
521 
522 	spdk_fs_free_thread_ctx(channel);
523 
524 	ut_send_request(_fs_unload, NULL);
525 }
526 
527 static void
528 cache_append_no_cache(void)
529 {
530 	int rc;
531 	char buf[100];
532 	struct spdk_fs_thread_ctx *channel;
533 
534 	ut_send_request(_fs_init, NULL);
535 
536 	channel = spdk_fs_alloc_thread_ctx(g_fs);
537 
538 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
539 	CU_ASSERT(rc == 0);
540 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
541 
542 	spdk_file_write(g_file, channel, buf, 0 * sizeof(buf), sizeof(buf));
543 	CU_ASSERT(spdk_file_get_length(g_file) == 1 * sizeof(buf));
544 	spdk_file_write(g_file, channel, buf, 1 * sizeof(buf), sizeof(buf));
545 	CU_ASSERT(spdk_file_get_length(g_file) == 2 * sizeof(buf));
546 	spdk_file_sync(g_file, channel);
547 
548 	fs_thread_poll();
549 
550 	spdk_file_write(g_file, channel, buf, 2 * sizeof(buf), sizeof(buf));
551 	CU_ASSERT(spdk_file_get_length(g_file) == 3 * sizeof(buf));
552 	spdk_file_write(g_file, channel, buf, 3 * sizeof(buf), sizeof(buf));
553 	CU_ASSERT(spdk_file_get_length(g_file) == 4 * sizeof(buf));
554 	spdk_file_write(g_file, channel, buf, 4 * sizeof(buf), sizeof(buf));
555 	CU_ASSERT(spdk_file_get_length(g_file) == 5 * sizeof(buf));
556 
557 	spdk_file_close(g_file, channel);
558 
559 	fs_thread_poll();
560 
561 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
562 	CU_ASSERT(rc == 0);
563 
564 	spdk_fs_free_thread_ctx(channel);
565 
566 	ut_send_request(_fs_unload, NULL);
567 }
568 
569 static void
570 fs_delete_file_without_close(void)
571 {
572 	int rc;
573 	struct spdk_fs_thread_ctx *channel;
574 	struct spdk_file *file;
575 
576 	ut_send_request(_fs_init, NULL);
577 	channel = spdk_fs_alloc_thread_ctx(g_fs);
578 	CU_ASSERT(channel != NULL);
579 
580 	rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
581 	CU_ASSERT(rc == 0);
582 	SPDK_CU_ASSERT_FATAL(g_file != NULL);
583 
584 	rc = spdk_fs_delete_file(g_fs, channel, "testfile");
585 	CU_ASSERT(rc == 0);
586 	CU_ASSERT(g_file->ref_count != 0);
587 	CU_ASSERT(g_file->is_deleted == true);
588 
589 	rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file);
590 	CU_ASSERT(rc != 0);
591 
592 	spdk_file_close(g_file, channel);
593 
594 	fs_thread_poll();
595 
596 	rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file);
597 	CU_ASSERT(rc != 0);
598 
599 	spdk_fs_free_thread_ctx(channel);
600 
601 	ut_send_request(_fs_unload, NULL);
602 
603 }
604 
605 static bool g_thread_exit = false;
606 
607 static void
608 terminate_spdk_thread(void *arg)
609 {
610 	g_thread_exit = true;
611 }
612 
613 static void *
614 spdk_thread(void *arg)
615 {
616 	struct spdk_thread *thread = arg;
617 
618 	spdk_set_thread(thread);
619 
620 	while (!g_thread_exit) {
621 		spdk_thread_poll(thread, 0, 0);
622 	}
623 
624 	return NULL;
625 }
626 
627 int main(int argc, char **argv)
628 {
629 	struct spdk_thread *thread;
630 	CU_pSuite	suite = NULL;
631 	pthread_t	spdk_tid;
632 	unsigned int	num_failures;
633 
634 	CU_set_error_action(CUEA_ABORT);
635 	CU_initialize_registry();
636 
637 	suite = CU_add_suite("blobfs_sync_ut", NULL, NULL);
638 
639 	CU_ADD_TEST(suite, cache_read_after_write);
640 	CU_ADD_TEST(suite, file_length);
641 	CU_ADD_TEST(suite, append_write_to_extend_blob);
642 	CU_ADD_TEST(suite, partial_buffer);
643 	CU_ADD_TEST(suite, cache_write_null_buffer);
644 	CU_ADD_TEST(suite, fs_create_sync);
645 	CU_ADD_TEST(suite, fs_rename_sync);
646 	CU_ADD_TEST(suite, cache_append_no_cache);
647 	CU_ADD_TEST(suite, fs_delete_file_without_close);
648 
649 	spdk_thread_lib_init(NULL, 0);
650 
651 	thread = spdk_thread_create("test_thread", NULL);
652 	spdk_set_thread(thread);
653 
654 	g_dispatch_thread = spdk_thread_create("dispatch_thread", NULL);
655 	pthread_create(&spdk_tid, NULL, spdk_thread, g_dispatch_thread);
656 
657 	g_dev_buffer = calloc(1, DEV_BUFFER_SIZE);
658 
659 	CU_basic_set_mode(CU_BRM_VERBOSE);
660 	CU_basic_run_tests();
661 	num_failures = CU_get_number_of_failures();
662 	CU_cleanup_registry();
663 
664 	free(g_dev_buffer);
665 
666 	ut_send_request(terminate_spdk_thread, NULL);
667 	pthread_join(spdk_tid, NULL);
668 
669 	while (spdk_thread_poll(g_dispatch_thread, 0, 0) > 0) {}
670 	while (spdk_thread_poll(thread, 0, 0) > 0) {}
671 
672 	spdk_set_thread(thread);
673 	spdk_thread_exit(thread);
674 	while (!spdk_thread_is_exited(thread)) {
675 		spdk_thread_poll(thread, 0, 0);
676 	}
677 	spdk_thread_destroy(thread);
678 
679 	spdk_set_thread(g_dispatch_thread);
680 	spdk_thread_exit(g_dispatch_thread);
681 	while (!spdk_thread_is_exited(g_dispatch_thread)) {
682 		spdk_thread_poll(g_dispatch_thread, 0, 0);
683 	}
684 	spdk_thread_destroy(g_dispatch_thread);
685 
686 	spdk_thread_lib_fini();
687 
688 	return num_failures;
689 }
690