1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 */ 4 5 #include "spdk/stdinc.h" 6 7 #include "spdk_internal/cunit.h" 8 #include "common/lib/ut_multithread.c" 9 10 static void ut_put_io_channel(struct spdk_io_channel *ch); 11 12 #define spdk_put_io_channel(ch) ut_put_io_channel(ch); 13 #include "blob/bdev/blob_bdev.c" 14 15 DEFINE_STUB(spdk_bdev_io_type_supported, bool, (struct spdk_bdev *bdev, 16 enum spdk_bdev_io_type io_type), false); 17 DEFINE_STUB_V(spdk_bdev_free_io, (struct spdk_bdev_io *g_bdev_io)); 18 DEFINE_STUB(spdk_bdev_queue_io_wait, int, 19 (struct spdk_bdev *bdev, struct spdk_io_channel *ch, 20 struct spdk_bdev_io_wait_entry *entry), 0); 21 DEFINE_STUB(spdk_bdev_read_blocks, int, 22 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf, 23 uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 24 void *cb_arg), 0); 25 DEFINE_STUB(spdk_bdev_write_blocks, int, 26 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf, 27 uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 28 void *cb_arg), 0); 29 DEFINE_STUB(spdk_bdev_readv_blocks, int, 30 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt, 31 uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 32 void *cb_arg), 0); 33 DEFINE_STUB(spdk_bdev_writev_blocks, int, 34 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt, 35 uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 36 void *cb_arg), 0); 37 DEFINE_STUB(spdk_bdev_readv_blocks_ext, int, 38 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt, 39 uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 40 void *cb_arg, struct spdk_bdev_ext_io_opts *opts), 0); 41 DEFINE_STUB(spdk_bdev_writev_blocks_ext, int, 42 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt, 43 uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 44 void *cb_arg, struct spdk_bdev_ext_io_opts *opts), 0); 45 DEFINE_STUB(spdk_bdev_write_zeroes_blocks, int, 46 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, uint64_t offset_blocks, 47 uint64_t num_blocks, spdk_bdev_io_completion_cb cb, void *cb_arg), 0); 48 DEFINE_STUB(spdk_bdev_unmap_blocks, int, 49 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, uint64_t offset_blocks, 50 uint64_t num_blocks, spdk_bdev_io_completion_cb cb, void *cb_arg), 0); 51 DEFINE_STUB(spdk_bdev_copy_blocks, int, 52 (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, uint64_t dst_offset_blocks, 53 uint64_t src_offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, 54 void *cb_arg), 0); 55 56 struct spdk_bdev { 57 char name[16]; 58 uint64_t blockcnt; 59 uint32_t blocklen; 60 uint32_t open_cnt; 61 enum spdk_bdev_claim_type claim_type; 62 struct spdk_bdev_module *claim_module; 63 struct spdk_bdev_desc *claim_desc; 64 }; 65 66 struct spdk_bdev_desc { 67 struct spdk_bdev *bdev; 68 bool write; 69 enum spdk_bdev_claim_type claim_type; 70 struct spdk_thread *thread; 71 }; 72 73 struct spdk_bdev *g_bdev; 74 75 static struct spdk_bdev_module g_bdev_mod = { 76 .name = "blob_bdev_ut" 77 }; 78 79 struct spdk_io_channel * 80 spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc) 81 { 82 if (desc != NULL) { 83 return (struct spdk_io_channel *)0x1; 84 } 85 return NULL; 86 } 87 88 static void 89 ut_put_io_channel(struct spdk_io_channel *ch) 90 { 91 } 92 93 static struct spdk_bdev * 94 get_bdev(const char *bdev_name) 95 { 96 if (g_bdev == NULL) { 97 return NULL; 98 } 99 100 if (strcmp(bdev_name, g_bdev->name) != 0) { 101 return NULL; 102 } 103 104 return g_bdev; 105 } 106 107 int 108 spdk_bdev_open_ext(const char *bdev_name, bool write, spdk_bdev_event_cb_t event_cb, 109 void *event_ctx, struct spdk_bdev_desc **_desc) 110 { 111 struct spdk_bdev_desc *desc; 112 struct spdk_bdev *bdev = get_bdev(bdev_name); 113 114 if (bdev == NULL) { 115 return -ENODEV; 116 } 117 118 if (write && bdev->claim_module != NULL) { 119 return -EPERM; 120 } 121 122 desc = calloc(1, sizeof(*desc)); 123 desc->bdev = g_bdev; 124 desc->write = write; 125 desc->thread = spdk_get_thread(); 126 *_desc = desc; 127 bdev->open_cnt++; 128 129 return 0; 130 } 131 132 void 133 spdk_bdev_close(struct spdk_bdev_desc *desc) 134 { 135 struct spdk_bdev *bdev = desc->bdev; 136 137 CU_ASSERT(desc->thread == spdk_get_thread()); 138 139 bdev->open_cnt--; 140 if (bdev->claim_desc == desc) { 141 bdev->claim_desc = NULL; 142 bdev->claim_type = SPDK_BDEV_CLAIM_NONE; 143 bdev->claim_module = NULL; 144 } 145 free(desc); 146 } 147 148 struct spdk_bdev * 149 spdk_bdev_desc_get_bdev(struct spdk_bdev_desc *desc) 150 { 151 return desc->bdev; 152 } 153 154 uint64_t 155 spdk_bdev_get_num_blocks(const struct spdk_bdev *bdev) 156 { 157 return bdev->blockcnt; 158 } 159 160 uint32_t 161 spdk_bdev_get_block_size(const struct spdk_bdev *bdev) 162 { 163 return bdev->blocklen; 164 } 165 166 /* This is a simple approximation: it does not support shared claims */ 167 int 168 spdk_bdev_module_claim_bdev_desc(struct spdk_bdev_desc *desc, enum spdk_bdev_claim_type type, 169 struct spdk_bdev_claim_opts *opts, 170 struct spdk_bdev_module *module) 171 { 172 struct spdk_bdev *bdev = desc->bdev; 173 174 if (bdev->claim_module != NULL) { 175 return -EPERM; 176 } 177 178 bdev->claim_type = type; 179 bdev->claim_module = module; 180 bdev->claim_desc = desc; 181 182 desc->claim_type = type; 183 184 return 0; 185 } 186 187 static void 188 init_bdev(struct spdk_bdev *bdev, const char *name, uint64_t num_blocks) 189 { 190 memset(bdev, 0, sizeof(*bdev)); 191 snprintf(bdev->name, sizeof(bdev->name), "%s", name); 192 bdev->blockcnt = num_blocks; 193 } 194 195 static void 196 create_bs_dev(void) 197 { 198 struct spdk_bdev bdev; 199 struct spdk_bs_dev *bs_dev = NULL; 200 struct blob_bdev *blob_bdev; 201 int rc; 202 203 init_bdev(&bdev, "bdev0", 16); 204 g_bdev = &bdev; 205 206 rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev); 207 CU_ASSERT(rc == 0); 208 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 209 CU_ASSERT(bdev.open_cnt == 1); 210 211 blob_bdev = (struct blob_bdev *)bs_dev; 212 CU_ASSERT(blob_bdev->desc != NULL); 213 CU_ASSERT(blob_bdev->desc->write); 214 CU_ASSERT(blob_bdev->desc->bdev == g_bdev); 215 CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE); 216 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 217 218 bs_dev->destroy(bs_dev); 219 CU_ASSERT(bdev.open_cnt == 0); 220 g_bdev = NULL; 221 } 222 223 static void 224 create_bs_dev_ro(void) 225 { 226 struct spdk_bdev bdev; 227 struct spdk_bs_dev *bs_dev = NULL; 228 struct blob_bdev *blob_bdev; 229 struct spdk_bdev_bs_dev_opts opts = { 0 }; 230 int rc; 231 232 /* opts with the wrong size returns -EINVAL */ 233 rc = spdk_bdev_create_bs_dev("nope", false, &opts, sizeof(opts) + 8, NULL, NULL, &bs_dev); 234 CU_ASSERT(rc == -EINVAL); 235 236 /* opts with the right size is OK, but can still fail if the device doesn't exist. */ 237 opts.opts_size = sizeof(opts); 238 rc = spdk_bdev_create_bs_dev("nope", false, &opts, sizeof(opts), NULL, NULL, &bs_dev); 239 CU_ASSERT(rc == -ENODEV); 240 241 init_bdev(&bdev, "bdev0", 16); 242 g_bdev = &bdev; 243 244 /* The normal way to create a read-only device */ 245 rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev); 246 CU_ASSERT(rc == 0); 247 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 248 CU_ASSERT(bdev.open_cnt == 1); 249 250 blob_bdev = (struct blob_bdev *)bs_dev; 251 CU_ASSERT(blob_bdev->desc != NULL); 252 CU_ASSERT(!blob_bdev->desc->write); 253 CU_ASSERT(blob_bdev->desc->bdev == g_bdev); 254 CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE); 255 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 256 257 bs_dev->destroy(bs_dev); 258 CU_ASSERT(bdev.open_cnt == 0); 259 g_bdev = NULL; 260 } 261 262 static void 263 create_bs_dev_rw(void) 264 { 265 struct spdk_bdev bdev; 266 struct spdk_bs_dev *bs_dev = NULL; 267 struct blob_bdev *blob_bdev; 268 int rc; 269 270 init_bdev(&bdev, "bdev0", 16); 271 g_bdev = &bdev; 272 273 /* This is equivalent to spdk_bdev_create_bs_dev_ext() */ 274 rc = spdk_bdev_create_bs_dev("bdev0", true, NULL, 0, NULL, NULL, &bs_dev); 275 CU_ASSERT(rc == 0); 276 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 277 CU_ASSERT(bdev.open_cnt == 1); 278 279 blob_bdev = (struct blob_bdev *)bs_dev; 280 CU_ASSERT(blob_bdev->desc != NULL); 281 CU_ASSERT(blob_bdev->desc->write); 282 CU_ASSERT(blob_bdev->desc->bdev == g_bdev); 283 CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE); 284 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 285 286 bs_dev->destroy(bs_dev); 287 CU_ASSERT(bdev.open_cnt == 0); 288 g_bdev = NULL; 289 } 290 291 static void 292 claim_bs_dev(void) 293 { 294 struct spdk_bdev bdev; 295 struct spdk_bs_dev *bs_dev = NULL, *bs_dev2 = NULL; 296 struct blob_bdev *blob_bdev; 297 int rc; 298 299 init_bdev(&bdev, "bdev0", 16); 300 g_bdev = &bdev; 301 302 rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev); 303 CU_ASSERT(rc == 0); 304 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 305 306 blob_bdev = (struct blob_bdev *)bs_dev; 307 CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE); 308 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 309 CU_ASSERT(blob_bdev->desc->write); 310 311 /* Can get an exclusive write claim */ 312 rc = spdk_bs_bdev_claim(bs_dev, &g_bdev_mod); 313 CU_ASSERT(rc == 0); 314 CU_ASSERT(blob_bdev->desc->write); 315 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_ONE); 316 CU_ASSERT(bdev.claim_desc == blob_bdev->desc); 317 318 /* Claim blocks a second writer without messing up the first one. */ 319 rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev2); 320 CU_ASSERT(rc == -EPERM); 321 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_ONE); 322 CU_ASSERT(bdev.claim_desc == blob_bdev->desc); 323 324 /* Claim blocks a second claim without messing up the first one. */ 325 rc = spdk_bs_bdev_claim(bs_dev, &g_bdev_mod); 326 CU_ASSERT(rc == -EPERM); 327 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_ONE); 328 CU_ASSERT(bdev.claim_desc == blob_bdev->desc); 329 330 bs_dev->destroy(bs_dev); 331 CU_ASSERT(bdev.open_cnt == 0); 332 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 333 CU_ASSERT(bdev.claim_module == NULL); 334 CU_ASSERT(bdev.claim_desc == NULL); 335 g_bdev = NULL; 336 } 337 338 static void 339 claim_bs_dev_ro(void) 340 { 341 struct spdk_bdev bdev; 342 struct spdk_bs_dev *bs_dev = NULL, *bs_dev2 = NULL; 343 struct blob_bdev *blob_bdev; 344 int rc; 345 346 init_bdev(&bdev, "bdev0", 16); 347 g_bdev = &bdev; 348 349 rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev); 350 CU_ASSERT(rc == 0); 351 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 352 353 blob_bdev = (struct blob_bdev *)bs_dev; 354 CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE); 355 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 356 CU_ASSERT(!blob_bdev->desc->write); 357 358 /* Can get an shared reader claim */ 359 rc = spdk_bs_bdev_claim(bs_dev, &g_bdev_mod); 360 CU_ASSERT(rc == 0); 361 CU_ASSERT(!blob_bdev->desc->write); 362 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_NONE); 363 CU_ASSERT(bdev.claim_desc == blob_bdev->desc); 364 365 /* Claim blocks a writer without messing up the claim. */ 366 rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev2); 367 CU_ASSERT(rc == -EPERM); 368 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_NONE); 369 CU_ASSERT(bdev.claim_desc == blob_bdev->desc); 370 371 /* Another reader is just fine */ 372 rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev2); 373 CU_ASSERT(rc == 0); 374 SPDK_CU_ASSERT_FATAL(bs_dev2 != NULL); 375 bs_dev2->destroy(bs_dev2); 376 377 bs_dev->destroy(bs_dev); 378 CU_ASSERT(bdev.open_cnt == 0); 379 CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE); 380 CU_ASSERT(bdev.claim_module == NULL); 381 CU_ASSERT(bdev.claim_desc == NULL); 382 g_bdev = NULL; 383 } 384 385 /* 386 * Verify that create_channel() and destroy_channel() increment and decrement the blob_bdev->refs. 387 */ 388 static void 389 deferred_destroy_refs(void) 390 { 391 struct spdk_bdev bdev; 392 struct spdk_io_channel *ch1, *ch2; 393 struct spdk_bs_dev *bs_dev = NULL; 394 struct blob_bdev *blob_bdev; 395 int rc; 396 397 set_thread(0); 398 init_bdev(&bdev, "bdev0", 16); 399 g_bdev = &bdev; 400 401 /* Open a blob_bdev, verify reference count is 1. */ 402 rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev); 403 CU_ASSERT(rc == 0); 404 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 405 blob_bdev = (struct blob_bdev *)bs_dev; 406 CU_ASSERT(blob_bdev->refs == 1); 407 CU_ASSERT(blob_bdev->desc != NULL); 408 409 /* Verify reference count increases with channels on the same thread. */ 410 ch1 = bs_dev->create_channel(bs_dev); 411 SPDK_CU_ASSERT_FATAL(ch1 != NULL); 412 CU_ASSERT(blob_bdev->refs == 2); 413 ch2 = bs_dev->create_channel(bs_dev); 414 SPDK_CU_ASSERT_FATAL(ch2 != NULL); 415 CU_ASSERT(blob_bdev->refs == 3); 416 bs_dev->destroy_channel(bs_dev, ch1); 417 CU_ASSERT(blob_bdev->refs == 2); 418 bs_dev->destroy_channel(bs_dev, ch2); 419 CU_ASSERT(blob_bdev->refs == 1); 420 CU_ASSERT(blob_bdev->desc != NULL); 421 422 /* Verify reference count increases with channels on different threads. */ 423 ch1 = bs_dev->create_channel(bs_dev); 424 SPDK_CU_ASSERT_FATAL(ch1 != NULL); 425 CU_ASSERT(blob_bdev->refs == 2); 426 set_thread(1); 427 ch2 = bs_dev->create_channel(bs_dev); 428 SPDK_CU_ASSERT_FATAL(ch2 != NULL); 429 CU_ASSERT(blob_bdev->refs == 3); 430 bs_dev->destroy_channel(bs_dev, ch1); 431 CU_ASSERT(blob_bdev->refs == 2); 432 bs_dev->destroy_channel(bs_dev, ch2); 433 CU_ASSERT(blob_bdev->refs == 1); 434 CU_ASSERT(blob_bdev->desc != NULL); 435 436 set_thread(0); 437 bs_dev->destroy(bs_dev); 438 g_bdev = NULL; 439 } 440 441 /* 442 * When a channel is open bs_dev->destroy() should not free bs_dev until after the last channel is 443 * closed. Further, destroy() prevents the creation of new channels. 444 */ 445 static void 446 deferred_destroy_channels(void) 447 { 448 struct spdk_bdev bdev; 449 struct spdk_io_channel *ch1, *ch2; 450 struct spdk_bs_dev *bs_dev = NULL; 451 struct blob_bdev *blob_bdev; 452 int rc; 453 454 set_thread(0); 455 init_bdev(&bdev, "bdev0", 16); 456 457 /* Open bs_dev and sanity check */ 458 g_bdev = &bdev; 459 rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev); 460 CU_ASSERT(rc == 0); 461 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 462 CU_ASSERT(bdev.open_cnt == 1); 463 blob_bdev = (struct blob_bdev *)bs_dev; 464 CU_ASSERT(blob_bdev->refs == 1); 465 CU_ASSERT(blob_bdev->desc != NULL); 466 467 /* Create a channel, destroy the bs_dev. It should not be freed yet. */ 468 ch1 = bs_dev->create_channel(bs_dev); 469 SPDK_CU_ASSERT_FATAL(ch1 != NULL); 470 CU_ASSERT(blob_bdev->refs == 2); 471 bs_dev->destroy(bs_dev); 472 473 /* Destroy closes the bdev and prevents desc from being used for creating more channels. */ 474 CU_ASSERT(blob_bdev->desc == NULL); 475 CU_ASSERT(bdev.open_cnt == 0); 476 CU_ASSERT(blob_bdev->refs == 1); 477 ch2 = bs_dev->create_channel(bs_dev); 478 CU_ASSERT(ch2 == NULL) 479 CU_ASSERT(blob_bdev->refs == 1); 480 bs_dev->destroy_channel(bs_dev, ch1); 481 g_bdev = NULL; 482 483 /* Now bs_dev should have been freed. Builds with asan will verify. */ 484 } 485 486 /* 487 * Verify that deferred destroy copes well with the last channel destruction being on a thread other 488 * than the thread used to obtain the bdev descriptor. 489 */ 490 static void 491 deferred_destroy_threads(void) 492 { 493 struct spdk_bdev bdev; 494 struct spdk_io_channel *ch1, *ch2; 495 struct spdk_bs_dev *bs_dev = NULL; 496 struct blob_bdev *blob_bdev; 497 int rc; 498 499 set_thread(0); 500 init_bdev(&bdev, "bdev0", 16); 501 g_bdev = &bdev; 502 503 /* Open bs_dev and sanity check */ 504 rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev); 505 CU_ASSERT(rc == 0); 506 SPDK_CU_ASSERT_FATAL(bs_dev != NULL); 507 CU_ASSERT(bdev.open_cnt == 1); 508 blob_bdev = (struct blob_bdev *)bs_dev; 509 CU_ASSERT(blob_bdev->refs == 1); 510 CU_ASSERT(blob_bdev->desc != NULL); 511 512 /* Create two channels, each on their own thread. */ 513 ch1 = bs_dev->create_channel(bs_dev); 514 SPDK_CU_ASSERT_FATAL(ch1 != NULL); 515 CU_ASSERT(blob_bdev->refs == 2); 516 CU_ASSERT(spdk_get_thread() == blob_bdev->desc->thread); 517 set_thread(1); 518 ch2 = bs_dev->create_channel(bs_dev); 519 SPDK_CU_ASSERT_FATAL(ch2 != NULL); 520 CU_ASSERT(blob_bdev->refs == 3); 521 522 /* Destroy the bs_dev on thread 0, the channel on thread 0, then the channel on thread 1. */ 523 set_thread(0); 524 bs_dev->destroy(bs_dev); 525 CU_ASSERT(blob_bdev->desc == NULL); 526 CU_ASSERT(bdev.open_cnt == 0); 527 CU_ASSERT(blob_bdev->refs == 2); 528 bs_dev->destroy_channel(bs_dev, ch1); 529 CU_ASSERT(blob_bdev->refs == 1); 530 set_thread(1); 531 bs_dev->destroy_channel(bs_dev, ch2); 532 set_thread(0); 533 g_bdev = NULL; 534 535 /* Now bs_dev should have been freed. Builds with asan will verify. */ 536 } 537 538 int 539 main(int argc, char **argv) 540 { 541 CU_pSuite suite; 542 unsigned int num_failures; 543 544 CU_initialize_registry(); 545 546 suite = CU_add_suite("blob_bdev", NULL, NULL); 547 548 CU_ADD_TEST(suite, create_bs_dev); 549 CU_ADD_TEST(suite, create_bs_dev_ro); 550 CU_ADD_TEST(suite, create_bs_dev_rw); 551 CU_ADD_TEST(suite, claim_bs_dev); 552 CU_ADD_TEST(suite, claim_bs_dev_ro); 553 CU_ADD_TEST(suite, deferred_destroy_refs); 554 CU_ADD_TEST(suite, deferred_destroy_channels); 555 CU_ADD_TEST(suite, deferred_destroy_threads); 556 557 allocate_threads(2); 558 set_thread(0); 559 560 num_failures = spdk_ut_run_tests(argc, argv, NULL); 561 CU_cleanup_registry(); 562 563 free_threads(); 564 565 return num_failures; 566 } 567