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 "CUnit/Basic.h" 37 #include "spdk_cunit.h" 38 39 #include "spdk/util.h" 40 41 #include "scsi/dev.c" 42 #include "scsi/port.c" 43 44 #include "spdk_internal/mock.h" 45 46 /* Unit test bdev mockup */ 47 struct spdk_bdev { 48 char name[100]; 49 }; 50 51 static struct spdk_bdev g_bdevs[] = { 52 {"malloc0"}, 53 {"malloc1"}, 54 }; 55 56 const char * 57 spdk_bdev_get_name(const struct spdk_bdev *bdev) 58 { 59 return bdev->name; 60 } 61 62 static struct spdk_scsi_task * 63 spdk_get_task(uint32_t *owner_task_ctr) 64 { 65 struct spdk_scsi_task *task; 66 67 task = calloc(1, sizeof(*task)); 68 if (!task) { 69 return NULL; 70 } 71 72 return task; 73 } 74 75 void 76 spdk_scsi_task_put(struct spdk_scsi_task *task) 77 { 78 free(task); 79 } 80 81 _spdk_scsi_lun * 82 spdk_scsi_lun_construct(struct spdk_bdev *bdev, 83 void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), 84 void *hotremove_ctx) 85 { 86 struct spdk_scsi_lun *lun; 87 88 lun = calloc(1, sizeof(struct spdk_scsi_lun)); 89 SPDK_CU_ASSERT_FATAL(lun != NULL); 90 91 lun->bdev = bdev; 92 93 return lun; 94 } 95 96 void 97 spdk_scsi_lun_destruct(struct spdk_scsi_lun *lun) 98 { 99 free(lun); 100 } 101 102 struct spdk_bdev * 103 spdk_bdev_get_by_name(const char *bdev_name) 104 { 105 size_t i; 106 107 for (i = 0; i < SPDK_COUNTOF(g_bdevs); i++) { 108 if (strcmp(bdev_name, g_bdevs[i].name) == 0) { 109 return &g_bdevs[i]; 110 } 111 } 112 113 return NULL; 114 } 115 116 DEFINE_STUB_V(spdk_scsi_lun_append_mgmt_task, 117 (struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)); 118 119 DEFINE_STUB_V(spdk_scsi_lun_execute_mgmt_task, (struct spdk_scsi_lun *lun)); 120 121 DEFINE_STUB(spdk_scsi_lun_has_pending_mgmt_tasks, bool, 122 (const struct spdk_scsi_lun *lun), false); 123 124 DEFINE_STUB_V(spdk_scsi_lun_append_task, 125 (struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)); 126 127 DEFINE_STUB_V(spdk_scsi_lun_execute_tasks, (struct spdk_scsi_lun *lun)); 128 129 DEFINE_STUB(_spdk_scsi_lun_allocate_io_channel, int, 130 (struct spdk_scsi_lun *lun), 0); 131 132 DEFINE_STUB_V(_spdk_scsi_lun_free_io_channel, (struct spdk_scsi_lun *lun)); 133 134 DEFINE_STUB(spdk_scsi_lun_has_pending_tasks, bool, 135 (const struct spdk_scsi_lun *lun), false); 136 137 static void 138 dev_destruct_null_dev(void) 139 { 140 /* pass null for the dev */ 141 spdk_scsi_dev_destruct(NULL, NULL, NULL); 142 } 143 144 static void 145 dev_destruct_zero_luns(void) 146 { 147 struct spdk_scsi_dev dev = { .is_allocated = 1 }; 148 149 /* No luns attached to the dev */ 150 151 /* free the dev */ 152 spdk_scsi_dev_destruct(&dev, NULL, NULL); 153 } 154 155 static void 156 dev_destruct_null_lun(void) 157 { 158 struct spdk_scsi_dev dev = { .is_allocated = 1 }; 159 160 /* pass null for the lun */ 161 dev.lun[0] = NULL; 162 163 /* free the dev */ 164 spdk_scsi_dev_destruct(&dev, NULL, NULL); 165 } 166 167 static void 168 dev_destruct_success(void) 169 { 170 struct spdk_scsi_dev dev = { .is_allocated = 1 }; 171 int rc; 172 173 /* dev with a single lun */ 174 rc = spdk_scsi_dev_add_lun(&dev, "malloc0", 0, NULL, NULL); 175 176 CU_ASSERT(rc == 0); 177 178 /* free the dev */ 179 spdk_scsi_dev_destruct(&dev, NULL, NULL); 180 181 } 182 183 static void 184 dev_construct_num_luns_zero(void) 185 { 186 struct spdk_scsi_dev *dev; 187 const char *bdev_name_list[1] = {}; 188 int lun_id_list[1] = { 0 }; 189 190 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 0, 191 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 192 193 /* dev should be null since we passed num_luns = 0 */ 194 CU_ASSERT_TRUE(dev == NULL); 195 } 196 197 static void 198 dev_construct_no_lun_zero(void) 199 { 200 struct spdk_scsi_dev *dev; 201 const char *bdev_name_list[1] = {}; 202 int lun_id_list[1] = { 0 }; 203 204 lun_id_list[0] = 1; 205 206 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, 207 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 208 209 /* dev should be null since no LUN0 was specified (lun_id_list[0] = 1) */ 210 CU_ASSERT_TRUE(dev == NULL); 211 } 212 213 static void 214 dev_construct_null_lun(void) 215 { 216 struct spdk_scsi_dev *dev; 217 const char *bdev_name_list[1] = {}; 218 int lun_id_list[1] = { 0 }; 219 220 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, 221 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 222 223 /* dev should be null since no LUN0 was specified (lun_list[0] = NULL) */ 224 CU_ASSERT_TRUE(dev == NULL); 225 } 226 227 static void 228 dev_construct_name_too_long(void) 229 { 230 struct spdk_scsi_dev *dev; 231 const char *bdev_name_list[1] = {"malloc0"}; 232 int lun_id_list[1] = { 0 }; 233 char name[SPDK_SCSI_DEV_MAX_NAME + 1 + 1]; 234 235 /* Try to construct a dev with a name that is one byte longer than allowed. */ 236 memset(name, 'x', sizeof(name) - 1); 237 name[sizeof(name) - 1] = '\0'; 238 239 dev = spdk_scsi_dev_construct(name, bdev_name_list, lun_id_list, 1, 240 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 241 242 CU_ASSERT(dev == NULL); 243 } 244 245 static void 246 dev_construct_success(void) 247 { 248 struct spdk_scsi_dev *dev; 249 const char *bdev_name_list[1] = {"malloc0"}; 250 int lun_id_list[1] = { 0 }; 251 252 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, 253 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 254 255 /* Successfully constructs and returns a dev */ 256 CU_ASSERT_TRUE(dev != NULL); 257 258 /* free the dev */ 259 spdk_scsi_dev_destruct(dev, NULL, NULL); 260 } 261 262 static void 263 dev_construct_success_lun_zero_not_first(void) 264 { 265 struct spdk_scsi_dev *dev; 266 const char *bdev_name_list[2] = {"malloc1", "malloc0"}; 267 int lun_id_list[2] = { 1, 0 }; 268 269 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 2, 270 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 271 272 /* Successfully constructs and returns a dev */ 273 CU_ASSERT_TRUE(dev != NULL); 274 275 /* free the dev */ 276 spdk_scsi_dev_destruct(dev, NULL, NULL); 277 } 278 279 static void 280 dev_queue_mgmt_task_success(void) 281 { 282 struct spdk_scsi_dev *dev; 283 const char *bdev_name_list[1] = {"malloc0"}; 284 int lun_id_list[1] = { 0 }; 285 struct spdk_scsi_task *task; 286 287 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, 288 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 289 290 /* Successfully constructs and returns a dev */ 291 CU_ASSERT_TRUE(dev != NULL); 292 293 task = spdk_get_task(NULL); 294 295 task->function = SPDK_SCSI_TASK_FUNC_LUN_RESET; 296 spdk_scsi_dev_queue_mgmt_task(dev, task); 297 298 spdk_scsi_task_put(task); 299 300 spdk_scsi_dev_destruct(dev, NULL, NULL); 301 } 302 303 static void 304 dev_queue_task_success(void) 305 { 306 struct spdk_scsi_dev *dev; 307 const char *bdev_name_list[1] = {"malloc0"}; 308 int lun_id_list[1] = { 0 }; 309 struct spdk_scsi_task *task; 310 311 dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, 312 SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); 313 314 /* Successfully constructs and returns a dev */ 315 CU_ASSERT_TRUE(dev != NULL); 316 317 task = spdk_get_task(NULL); 318 319 spdk_scsi_dev_queue_task(dev, task); 320 321 spdk_scsi_task_put(task); 322 323 spdk_scsi_dev_destruct(dev, NULL, NULL); 324 } 325 326 static void 327 dev_stop_success(void) 328 { 329 struct spdk_scsi_dev dev = { 0 }; 330 struct spdk_scsi_task *task; 331 struct spdk_scsi_task *task_mgmt; 332 333 task = spdk_get_task(NULL); 334 335 spdk_scsi_dev_queue_task(&dev, task); 336 337 task_mgmt = spdk_get_task(NULL); 338 339 /* Enqueue the tasks into dev->task_mgmt_submit_queue */ 340 task->function = SPDK_SCSI_TASK_FUNC_LUN_RESET; 341 spdk_scsi_dev_queue_mgmt_task(&dev, task_mgmt); 342 343 spdk_scsi_task_put(task); 344 spdk_scsi_task_put(task_mgmt); 345 } 346 347 static void 348 dev_add_port_max_ports(void) 349 { 350 struct spdk_scsi_dev dev = { 0 }; 351 const char *name; 352 int id, rc; 353 354 /* dev is set to SPDK_SCSI_DEV_MAX_PORTS */ 355 dev.num_ports = SPDK_SCSI_DEV_MAX_PORTS; 356 name = "Name of Port"; 357 id = 1; 358 359 rc = spdk_scsi_dev_add_port(&dev, id, name); 360 361 /* returns -1; since the dev already has maximum 362 * number of ports (SPDK_SCSI_DEV_MAX_PORTS) */ 363 CU_ASSERT_TRUE(rc < 0); 364 } 365 366 static void 367 dev_add_port_construct_failure1(void) 368 { 369 struct spdk_scsi_dev dev = { 0 }; 370 const int port_name_length = SPDK_SCSI_PORT_MAX_NAME_LENGTH + 2; 371 char name[port_name_length]; 372 uint64_t id; 373 int rc; 374 375 dev.num_ports = 1; 376 /* Set the name such that the length exceeds SPDK_SCSI_PORT_MAX_NAME_LENGTH 377 * SPDK_SCSI_PORT_MAX_NAME_LENGTH = 256 */ 378 memset(name, 'a', port_name_length - 1); 379 name[port_name_length - 1] = '\0'; 380 id = 1; 381 382 rc = spdk_scsi_dev_add_port(&dev, id, name); 383 384 /* returns -1; since the length of the name exceeds 385 * SPDK_SCSI_PORT_MAX_NAME_LENGTH */ 386 CU_ASSERT_TRUE(rc < 0); 387 } 388 389 static void 390 dev_add_port_construct_failure2(void) 391 { 392 struct spdk_scsi_dev dev = { 0 }; 393 const char *name; 394 uint64_t id; 395 int rc; 396 397 dev.num_ports = 1; 398 name = "Name of Port"; 399 id = 1; 400 401 /* Initialize port[0] to be valid and its index is set to 1 */ 402 dev.port[0].id = id; 403 dev.port[0].is_used = 1; 404 405 rc = spdk_scsi_dev_add_port(&dev, id, name); 406 407 /* returns -1; since the dev already has a port whose index to be 1 */ 408 CU_ASSERT_TRUE(rc < 0); 409 } 410 411 static void 412 dev_add_port_success1(void) 413 { 414 struct spdk_scsi_dev dev = { 0 }; 415 const char *name; 416 int id, rc; 417 418 dev.num_ports = 1; 419 name = "Name of Port"; 420 id = 1; 421 422 rc = spdk_scsi_dev_add_port(&dev, id, name); 423 424 /* successfully adds a port */ 425 CU_ASSERT_EQUAL(rc, 0); 426 /* Assert num_ports has been incremented to 2 */ 427 CU_ASSERT_EQUAL(dev.num_ports, 2); 428 } 429 430 static void 431 dev_add_port_success2(void) 432 { 433 struct spdk_scsi_dev dev = { 0 }; 434 const char *name; 435 uint64_t id; 436 int rc; 437 438 dev.num_ports = 1; 439 name = "Name of Port"; 440 id = 1; 441 /* set id of invalid port[0] to 1. This must be ignored */ 442 dev.port[0].id = id; 443 dev.port[0].is_used = 0; 444 445 rc = spdk_scsi_dev_add_port(&dev, id, name); 446 447 /* successfully adds a port */ 448 CU_ASSERT_EQUAL(rc, 0); 449 /* Assert num_ports has been incremented to 1 */ 450 CU_ASSERT_EQUAL(dev.num_ports, 2); 451 } 452 453 static void 454 dev_add_port_success3(void) 455 { 456 struct spdk_scsi_dev dev = { 0 }; 457 const char *name; 458 uint64_t add_id; 459 int rc; 460 461 dev.num_ports = 1; 462 name = "Name of Port"; 463 dev.port[0].id = 1; 464 dev.port[0].is_used = 1; 465 add_id = 2; 466 467 /* Add a port with id = 2 */ 468 rc = spdk_scsi_dev_add_port(&dev, add_id, name); 469 470 /* successfully adds a port */ 471 CU_ASSERT_EQUAL(rc, 0); 472 /* Assert num_ports has been incremented to 2 */ 473 CU_ASSERT_EQUAL(dev.num_ports, 2); 474 } 475 476 static void 477 dev_find_port_by_id_num_ports_zero(void) 478 { 479 struct spdk_scsi_dev dev = { 0 }; 480 struct spdk_scsi_port *rp_port; 481 uint64_t id; 482 483 dev.num_ports = 0; 484 id = 1; 485 486 rp_port = spdk_scsi_dev_find_port_by_id(&dev, id); 487 488 /* returns null; since dev's num_ports is 0 */ 489 CU_ASSERT_TRUE(rp_port == NULL); 490 } 491 492 static void 493 dev_find_port_by_id_id_not_found_failure(void) 494 { 495 struct spdk_scsi_dev dev = { 0 }; 496 struct spdk_scsi_port *rp_port; 497 const char *name; 498 int rc; 499 uint64_t id, find_id; 500 501 id = 1; 502 dev.num_ports = 1; 503 name = "Name of Port"; 504 find_id = 2; 505 506 /* Add a port with id = 1 */ 507 rc = spdk_scsi_dev_add_port(&dev, id, name); 508 509 CU_ASSERT_EQUAL(rc, 0); 510 511 /* Find port with id = 2 */ 512 rp_port = spdk_scsi_dev_find_port_by_id(&dev, find_id); 513 514 /* returns null; failed to find port specified by id = 2 */ 515 CU_ASSERT_TRUE(rp_port == NULL); 516 } 517 518 static void 519 dev_find_port_by_id_success(void) 520 { 521 struct spdk_scsi_dev dev = { 0 }; 522 struct spdk_scsi_port *rp_port; 523 const char *name; 524 int rc; 525 uint64_t id; 526 527 id = 1; 528 dev.num_ports = 1; 529 name = "Name of Port"; 530 531 /* Add a port */ 532 rc = spdk_scsi_dev_add_port(&dev, id, name); 533 534 CU_ASSERT_EQUAL(rc, 0); 535 536 /* Find port by the same id as the one added above */ 537 rp_port = spdk_scsi_dev_find_port_by_id(&dev, id); 538 539 /* Successfully found port specified by id */ 540 CU_ASSERT_TRUE(rp_port != NULL); 541 if (rp_port != NULL) { 542 /* Assert the found port's id and name are same as 543 * the port added. */ 544 CU_ASSERT_EQUAL(rp_port->id, 1); 545 CU_ASSERT_STRING_EQUAL(rp_port->name, "Name of Port"); 546 } 547 } 548 549 static void 550 dev_add_lun_bdev_not_found(void) 551 { 552 int rc; 553 struct spdk_scsi_dev dev = {0}; 554 555 rc = spdk_scsi_dev_add_lun(&dev, "malloc2", 0, NULL, NULL); 556 557 SPDK_CU_ASSERT_FATAL(dev.lun[0] == NULL); 558 CU_ASSERT_NOT_EQUAL(rc, 0); 559 } 560 561 static void 562 dev_add_lun_no_free_lun_id(void) 563 { 564 int rc; 565 int i; 566 struct spdk_scsi_dev dev = {0}; 567 struct spdk_scsi_lun lun; 568 569 for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { 570 dev.lun[i] = &lun; 571 } 572 573 rc = spdk_scsi_dev_add_lun(&dev, "malloc0", -1, NULL, NULL); 574 575 CU_ASSERT_NOT_EQUAL(rc, 0); 576 } 577 578 static void 579 dev_add_lun_success1(void) 580 { 581 int rc; 582 struct spdk_scsi_dev dev = {0}; 583 584 rc = spdk_scsi_dev_add_lun(&dev, "malloc0", -1, NULL, NULL); 585 586 CU_ASSERT_EQUAL(rc, 0); 587 588 spdk_scsi_dev_destruct(&dev, NULL, NULL); 589 } 590 591 static void 592 dev_add_lun_success2(void) 593 { 594 int rc; 595 struct spdk_scsi_dev dev = {0}; 596 597 rc = spdk_scsi_dev_add_lun(&dev, "malloc0", 0, NULL, NULL); 598 599 CU_ASSERT_EQUAL(rc, 0); 600 601 spdk_scsi_dev_destruct(&dev, NULL, NULL); 602 } 603 604 int 605 main(int argc, char **argv) 606 { 607 CU_pSuite suite = NULL; 608 unsigned int num_failures; 609 610 if (CU_initialize_registry() != CUE_SUCCESS) { 611 return CU_get_error(); 612 } 613 614 suite = CU_add_suite("dev_suite", NULL, NULL); 615 if (suite == NULL) { 616 CU_cleanup_registry(); 617 return CU_get_error(); 618 } 619 620 if ( 621 CU_add_test(suite, "destruct - null dev", 622 dev_destruct_null_dev) == NULL 623 || CU_add_test(suite, "destruct - zero luns", dev_destruct_zero_luns) == NULL 624 || CU_add_test(suite, "destruct - null lun", dev_destruct_null_lun) == NULL 625 || CU_add_test(suite, "destruct - success", dev_destruct_success) == NULL 626 || CU_add_test(suite, "construct - queue depth gt max depth", 627 dev_construct_num_luns_zero) == NULL 628 || CU_add_test(suite, "construct - no lun0", 629 dev_construct_no_lun_zero) == NULL 630 || CU_add_test(suite, "construct - null lun", 631 dev_construct_null_lun) == NULL 632 || CU_add_test(suite, "construct - name too long", dev_construct_name_too_long) == NULL 633 || CU_add_test(suite, "construct - success", dev_construct_success) == NULL 634 || CU_add_test(suite, "construct - success - LUN zero not first", 635 dev_construct_success_lun_zero_not_first) == NULL 636 || CU_add_test(suite, "dev queue task mgmt - success", 637 dev_queue_mgmt_task_success) == NULL 638 || CU_add_test(suite, "dev queue task - success", 639 dev_queue_task_success) == NULL 640 || CU_add_test(suite, "dev stop - success", dev_stop_success) == NULL 641 || CU_add_test(suite, "dev add port - max ports", 642 dev_add_port_max_ports) == NULL 643 || CU_add_test(suite, "dev add port - construct port failure 1", 644 dev_add_port_construct_failure1) == NULL 645 || CU_add_test(suite, "dev add port - construct port failure 2", 646 dev_add_port_construct_failure2) == NULL 647 || CU_add_test(suite, "dev add port - success 1", 648 dev_add_port_success1) == NULL 649 || CU_add_test(suite, "dev add port - success 2", 650 dev_add_port_success2) == NULL 651 || CU_add_test(suite, "dev add port - success 3", 652 dev_add_port_success3) == NULL 653 || CU_add_test(suite, "dev find port by id - num ports zero", 654 dev_find_port_by_id_num_ports_zero) == NULL 655 || CU_add_test(suite, "dev find port by id - different port id failure", 656 dev_find_port_by_id_id_not_found_failure) == NULL 657 || CU_add_test(suite, "dev find port by id - success", 658 dev_find_port_by_id_success) == NULL 659 || CU_add_test(suite, "dev add lun - bdev not found", 660 dev_add_lun_bdev_not_found) == NULL 661 || CU_add_test(suite, "dev add lun - no free lun id", 662 dev_add_lun_no_free_lun_id) == NULL 663 || CU_add_test(suite, "dev add lun - success 1", 664 dev_add_lun_success1) == NULL 665 || CU_add_test(suite, "dev add lun - success 2", 666 dev_add_lun_success2) == NULL 667 ) { 668 CU_cleanup_registry(); 669 return CU_get_error(); 670 } 671 672 CU_basic_set_mode(CU_BRM_VERBOSE); 673 CU_basic_run_tests(); 674 num_failures = CU_get_number_of_failures(); 675 CU_cleanup_registry(); 676 677 return num_failures; 678 } 679