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