1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Base unit test (KUnit) API. 4 * 5 * Copyright (C) 2019, Google LLC. 6 * Author: Brendan Higgins <brendanhiggins@google.com> 7 */ 8 9 #include <kunit/resource.h> 10 #include <kunit/test.h> 11 #include <kunit/test-bug.h> 12 #include <linux/kernel.h> 13 #include <linux/module.h> 14 #include <linux/moduleparam.h> 15 #include <linux/panic.h> 16 #include <linux/sched/debug.h> 17 #include <linux/sched.h> 18 19 #include "debugfs.h" 20 #include "string-stream.h" 21 #include "try-catch-impl.h" 22 23 #if IS_BUILTIN(CONFIG_KUNIT) 24 /* 25 * Fail the current test and print an error message to the log. 26 */ 27 void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...) 28 { 29 va_list args; 30 int len; 31 char *buffer; 32 33 if (!current->kunit_test) 34 return; 35 36 kunit_set_failure(current->kunit_test); 37 38 /* kunit_err() only accepts literals, so evaluate the args first. */ 39 va_start(args, fmt); 40 len = vsnprintf(NULL, 0, fmt, args) + 1; 41 va_end(args); 42 43 buffer = kunit_kmalloc(current->kunit_test, len, GFP_KERNEL); 44 if (!buffer) 45 return; 46 47 va_start(args, fmt); 48 vsnprintf(buffer, len, fmt, args); 49 va_end(args); 50 51 kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer); 52 kunit_kfree(current->kunit_test, buffer); 53 } 54 EXPORT_SYMBOL_GPL(__kunit_fail_current_test); 55 #endif 56 57 /* 58 * Enable KUnit tests to run. 59 */ 60 #ifdef CONFIG_KUNIT_DEFAULT_ENABLED 61 static bool enable_param = true; 62 #else 63 static bool enable_param; 64 #endif 65 module_param_named(enable, enable_param, bool, 0); 66 MODULE_PARM_DESC(enable, "Enable KUnit tests"); 67 68 /* 69 * KUnit statistic mode: 70 * 0 - disabled 71 * 1 - only when there is more than one subtest 72 * 2 - enabled 73 */ 74 static int kunit_stats_enabled = 1; 75 module_param_named(stats_enabled, kunit_stats_enabled, int, 0644); 76 MODULE_PARM_DESC(stats_enabled, 77 "Print test stats: never (0), only for multiple subtests (1), or always (2)"); 78 79 struct kunit_result_stats { 80 unsigned long passed; 81 unsigned long skipped; 82 unsigned long failed; 83 unsigned long total; 84 }; 85 86 static bool kunit_should_print_stats(struct kunit_result_stats stats) 87 { 88 if (kunit_stats_enabled == 0) 89 return false; 90 91 if (kunit_stats_enabled == 2) 92 return true; 93 94 return (stats.total > 1); 95 } 96 97 static void kunit_print_test_stats(struct kunit *test, 98 struct kunit_result_stats stats) 99 { 100 if (!kunit_should_print_stats(stats)) 101 return; 102 103 kunit_log(KERN_INFO, test, 104 KUNIT_SUBTEST_INDENT 105 "# %s: pass:%lu fail:%lu skip:%lu total:%lu", 106 test->name, 107 stats.passed, 108 stats.failed, 109 stats.skipped, 110 stats.total); 111 } 112 113 /* 114 * Append formatted message to log, size of which is limited to 115 * KUNIT_LOG_SIZE bytes (including null terminating byte). 116 */ 117 void kunit_log_append(char *log, const char *fmt, ...) 118 { 119 char line[KUNIT_LOG_SIZE]; 120 va_list args; 121 int len_left; 122 123 if (!log) 124 return; 125 126 len_left = KUNIT_LOG_SIZE - strlen(log) - 1; 127 if (len_left <= 0) 128 return; 129 130 va_start(args, fmt); 131 vsnprintf(line, sizeof(line), fmt, args); 132 va_end(args); 133 134 strncat(log, line, len_left); 135 } 136 EXPORT_SYMBOL_GPL(kunit_log_append); 137 138 size_t kunit_suite_num_test_cases(struct kunit_suite *suite) 139 { 140 struct kunit_case *test_case; 141 size_t len = 0; 142 143 kunit_suite_for_each_test_case(suite, test_case) 144 len++; 145 146 return len; 147 } 148 EXPORT_SYMBOL_GPL(kunit_suite_num_test_cases); 149 150 static void kunit_print_suite_start(struct kunit_suite *suite) 151 { 152 kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "# Subtest: %s", 153 suite->name); 154 kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "1..%zd", 155 kunit_suite_num_test_cases(suite)); 156 } 157 158 static void kunit_print_ok_not_ok(void *test_or_suite, 159 bool is_test, 160 enum kunit_status status, 161 size_t test_number, 162 const char *description, 163 const char *directive) 164 { 165 struct kunit_suite *suite = is_test ? NULL : test_or_suite; 166 struct kunit *test = is_test ? test_or_suite : NULL; 167 const char *directive_header = (status == KUNIT_SKIPPED) ? " # SKIP " : ""; 168 169 /* 170 * We do not log the test suite results as doing so would 171 * mean debugfs display would consist of the test suite 172 * description and status prior to individual test results. 173 * Hence directly printk the suite status, and we will 174 * separately seq_printf() the suite status for the debugfs 175 * representation. 176 */ 177 if (suite) 178 pr_info("%s %zd - %s%s%s\n", 179 kunit_status_to_ok_not_ok(status), 180 test_number, description, directive_header, 181 (status == KUNIT_SKIPPED) ? directive : ""); 182 else 183 kunit_log(KERN_INFO, test, 184 KUNIT_SUBTEST_INDENT "%s %zd - %s%s%s", 185 kunit_status_to_ok_not_ok(status), 186 test_number, description, directive_header, 187 (status == KUNIT_SKIPPED) ? directive : ""); 188 } 189 190 enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite) 191 { 192 const struct kunit_case *test_case; 193 enum kunit_status status = KUNIT_SKIPPED; 194 195 if (suite->suite_init_err) 196 return KUNIT_FAILURE; 197 198 kunit_suite_for_each_test_case(suite, test_case) { 199 if (test_case->status == KUNIT_FAILURE) 200 return KUNIT_FAILURE; 201 else if (test_case->status == KUNIT_SUCCESS) 202 status = KUNIT_SUCCESS; 203 } 204 205 return status; 206 } 207 EXPORT_SYMBOL_GPL(kunit_suite_has_succeeded); 208 209 static size_t kunit_suite_counter = 1; 210 211 static void kunit_print_suite_end(struct kunit_suite *suite) 212 { 213 kunit_print_ok_not_ok((void *)suite, false, 214 kunit_suite_has_succeeded(suite), 215 kunit_suite_counter++, 216 suite->name, 217 suite->status_comment); 218 } 219 220 unsigned int kunit_test_case_num(struct kunit_suite *suite, 221 struct kunit_case *test_case) 222 { 223 struct kunit_case *tc; 224 unsigned int i = 1; 225 226 kunit_suite_for_each_test_case(suite, tc) { 227 if (tc == test_case) 228 return i; 229 i++; 230 } 231 232 return 0; 233 } 234 EXPORT_SYMBOL_GPL(kunit_test_case_num); 235 236 static void kunit_print_string_stream(struct kunit *test, 237 struct string_stream *stream) 238 { 239 struct string_stream_fragment *fragment; 240 char *buf; 241 242 if (string_stream_is_empty(stream)) 243 return; 244 245 buf = string_stream_get_string(stream); 246 if (!buf) { 247 kunit_err(test, 248 "Could not allocate buffer, dumping stream:\n"); 249 list_for_each_entry(fragment, &stream->fragments, node) { 250 kunit_err(test, "%s", fragment->fragment); 251 } 252 kunit_err(test, "\n"); 253 } else { 254 kunit_err(test, "%s", buf); 255 kunit_kfree(test, buf); 256 } 257 } 258 259 static void kunit_fail(struct kunit *test, const struct kunit_loc *loc, 260 enum kunit_assert_type type, const struct kunit_assert *assert, 261 assert_format_t assert_format, const struct va_format *message) 262 { 263 struct string_stream *stream; 264 265 kunit_set_failure(test); 266 267 stream = alloc_string_stream(test, GFP_KERNEL); 268 if (IS_ERR(stream)) { 269 WARN(true, 270 "Could not allocate stream to print failed assertion in %s:%d\n", 271 loc->file, 272 loc->line); 273 return; 274 } 275 276 kunit_assert_prologue(loc, type, stream); 277 assert_format(assert, message, stream); 278 279 kunit_print_string_stream(test, stream); 280 281 string_stream_destroy(stream); 282 } 283 284 static void __noreturn kunit_abort(struct kunit *test) 285 { 286 kunit_try_catch_throw(&test->try_catch); /* Does not return. */ 287 288 /* 289 * Throw could not abort from test. 290 * 291 * XXX: we should never reach this line! As kunit_try_catch_throw is 292 * marked __noreturn. 293 */ 294 WARN_ONCE(true, "Throw could not abort from test!\n"); 295 } 296 297 void kunit_do_failed_assertion(struct kunit *test, 298 const struct kunit_loc *loc, 299 enum kunit_assert_type type, 300 const struct kunit_assert *assert, 301 assert_format_t assert_format, 302 const char *fmt, ...) 303 { 304 va_list args; 305 struct va_format message; 306 va_start(args, fmt); 307 308 message.fmt = fmt; 309 message.va = &args; 310 311 kunit_fail(test, loc, type, assert, assert_format, &message); 312 313 va_end(args); 314 315 if (type == KUNIT_ASSERTION) 316 kunit_abort(test); 317 } 318 EXPORT_SYMBOL_GPL(kunit_do_failed_assertion); 319 320 void kunit_init_test(struct kunit *test, const char *name, char *log) 321 { 322 spin_lock_init(&test->lock); 323 INIT_LIST_HEAD(&test->resources); 324 test->name = name; 325 test->log = log; 326 if (test->log) 327 test->log[0] = '\0'; 328 test->status = KUNIT_SUCCESS; 329 test->status_comment[0] = '\0'; 330 } 331 EXPORT_SYMBOL_GPL(kunit_init_test); 332 333 /* 334 * Initializes and runs test case. Does not clean up or do post validations. 335 */ 336 static void kunit_run_case_internal(struct kunit *test, 337 struct kunit_suite *suite, 338 struct kunit_case *test_case) 339 { 340 if (suite->init) { 341 int ret; 342 343 ret = suite->init(test); 344 if (ret) { 345 kunit_err(test, "failed to initialize: %d\n", ret); 346 kunit_set_failure(test); 347 return; 348 } 349 } 350 351 test_case->run_case(test); 352 } 353 354 static void kunit_case_internal_cleanup(struct kunit *test) 355 { 356 kunit_cleanup(test); 357 } 358 359 /* 360 * Performs post validations and cleanup after a test case was run. 361 * XXX: Should ONLY BE CALLED AFTER kunit_run_case_internal! 362 */ 363 static void kunit_run_case_cleanup(struct kunit *test, 364 struct kunit_suite *suite) 365 { 366 if (suite->exit) 367 suite->exit(test); 368 369 kunit_case_internal_cleanup(test); 370 } 371 372 struct kunit_try_catch_context { 373 struct kunit *test; 374 struct kunit_suite *suite; 375 struct kunit_case *test_case; 376 }; 377 378 static void kunit_try_run_case(void *data) 379 { 380 struct kunit_try_catch_context *ctx = data; 381 struct kunit *test = ctx->test; 382 struct kunit_suite *suite = ctx->suite; 383 struct kunit_case *test_case = ctx->test_case; 384 385 current->kunit_test = test; 386 387 /* 388 * kunit_run_case_internal may encounter a fatal error; if it does, 389 * abort will be called, this thread will exit, and finally the parent 390 * thread will resume control and handle any necessary clean up. 391 */ 392 kunit_run_case_internal(test, suite, test_case); 393 /* This line may never be reached. */ 394 kunit_run_case_cleanup(test, suite); 395 } 396 397 static void kunit_catch_run_case(void *data) 398 { 399 struct kunit_try_catch_context *ctx = data; 400 struct kunit *test = ctx->test; 401 struct kunit_suite *suite = ctx->suite; 402 int try_exit_code = kunit_try_catch_get_result(&test->try_catch); 403 404 if (try_exit_code) { 405 kunit_set_failure(test); 406 /* 407 * Test case could not finish, we have no idea what state it is 408 * in, so don't do clean up. 409 */ 410 if (try_exit_code == -ETIMEDOUT) { 411 kunit_err(test, "test case timed out\n"); 412 /* 413 * Unknown internal error occurred preventing test case from 414 * running, so there is nothing to clean up. 415 */ 416 } else { 417 kunit_err(test, "internal error occurred preventing test case from running: %d\n", 418 try_exit_code); 419 } 420 return; 421 } 422 423 /* 424 * Test case was run, but aborted. It is the test case's business as to 425 * whether it failed or not, we just need to clean up. 426 */ 427 kunit_run_case_cleanup(test, suite); 428 } 429 430 /* 431 * Performs all logic to run a test case. It also catches most errors that 432 * occur in a test case and reports them as failures. 433 */ 434 static void kunit_run_case_catch_errors(struct kunit_suite *suite, 435 struct kunit_case *test_case, 436 struct kunit *test) 437 { 438 struct kunit_try_catch_context context; 439 struct kunit_try_catch *try_catch; 440 441 kunit_init_test(test, test_case->name, test_case->log); 442 try_catch = &test->try_catch; 443 444 kunit_try_catch_init(try_catch, 445 test, 446 kunit_try_run_case, 447 kunit_catch_run_case); 448 context.test = test; 449 context.suite = suite; 450 context.test_case = test_case; 451 kunit_try_catch_run(try_catch, &context); 452 453 /* Propagate the parameter result to the test case. */ 454 if (test->status == KUNIT_FAILURE) 455 test_case->status = KUNIT_FAILURE; 456 else if (test_case->status != KUNIT_FAILURE && test->status == KUNIT_SUCCESS) 457 test_case->status = KUNIT_SUCCESS; 458 } 459 460 static void kunit_print_suite_stats(struct kunit_suite *suite, 461 struct kunit_result_stats suite_stats, 462 struct kunit_result_stats param_stats) 463 { 464 if (kunit_should_print_stats(suite_stats)) { 465 kunit_log(KERN_INFO, suite, 466 "# %s: pass:%lu fail:%lu skip:%lu total:%lu", 467 suite->name, 468 suite_stats.passed, 469 suite_stats.failed, 470 suite_stats.skipped, 471 suite_stats.total); 472 } 473 474 if (kunit_should_print_stats(param_stats)) { 475 kunit_log(KERN_INFO, suite, 476 "# Totals: pass:%lu fail:%lu skip:%lu total:%lu", 477 param_stats.passed, 478 param_stats.failed, 479 param_stats.skipped, 480 param_stats.total); 481 } 482 } 483 484 static void kunit_update_stats(struct kunit_result_stats *stats, 485 enum kunit_status status) 486 { 487 switch (status) { 488 case KUNIT_SUCCESS: 489 stats->passed++; 490 break; 491 case KUNIT_SKIPPED: 492 stats->skipped++; 493 break; 494 case KUNIT_FAILURE: 495 stats->failed++; 496 break; 497 } 498 499 stats->total++; 500 } 501 502 static void kunit_accumulate_stats(struct kunit_result_stats *total, 503 struct kunit_result_stats add) 504 { 505 total->passed += add.passed; 506 total->skipped += add.skipped; 507 total->failed += add.failed; 508 total->total += add.total; 509 } 510 511 int kunit_run_tests(struct kunit_suite *suite) 512 { 513 char param_desc[KUNIT_PARAM_DESC_SIZE]; 514 struct kunit_case *test_case; 515 struct kunit_result_stats suite_stats = { 0 }; 516 struct kunit_result_stats total_stats = { 0 }; 517 518 /* Taint the kernel so we know we've run tests. */ 519 add_taint(TAINT_TEST, LOCKDEP_STILL_OK); 520 521 if (suite->suite_init) { 522 suite->suite_init_err = suite->suite_init(suite); 523 if (suite->suite_init_err) { 524 kunit_err(suite, KUNIT_SUBTEST_INDENT 525 "# failed to initialize (%d)", suite->suite_init_err); 526 goto suite_end; 527 } 528 } 529 530 kunit_print_suite_start(suite); 531 532 kunit_suite_for_each_test_case(suite, test_case) { 533 struct kunit test = { .param_value = NULL, .param_index = 0 }; 534 struct kunit_result_stats param_stats = { 0 }; 535 test_case->status = KUNIT_SKIPPED; 536 537 if (!test_case->generate_params) { 538 /* Non-parameterised test. */ 539 kunit_run_case_catch_errors(suite, test_case, &test); 540 kunit_update_stats(¶m_stats, test.status); 541 } else { 542 /* Get initial param. */ 543 param_desc[0] = '\0'; 544 test.param_value = test_case->generate_params(NULL, param_desc); 545 kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT 546 "# Subtest: %s", test_case->name); 547 548 while (test.param_value) { 549 kunit_run_case_catch_errors(suite, test_case, &test); 550 551 if (param_desc[0] == '\0') { 552 snprintf(param_desc, sizeof(param_desc), 553 "param-%d", test.param_index); 554 } 555 556 kunit_log(KERN_INFO, &test, 557 KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT 558 "%s %d - %s", 559 kunit_status_to_ok_not_ok(test.status), 560 test.param_index + 1, param_desc); 561 562 /* Get next param. */ 563 param_desc[0] = '\0'; 564 test.param_value = test_case->generate_params(test.param_value, param_desc); 565 test.param_index++; 566 567 kunit_update_stats(¶m_stats, test.status); 568 } 569 } 570 571 572 kunit_print_test_stats(&test, param_stats); 573 574 kunit_print_ok_not_ok(&test, true, test_case->status, 575 kunit_test_case_num(suite, test_case), 576 test_case->name, 577 test.status_comment); 578 579 kunit_update_stats(&suite_stats, test_case->status); 580 kunit_accumulate_stats(&total_stats, param_stats); 581 } 582 583 if (suite->suite_exit) 584 suite->suite_exit(suite); 585 586 kunit_print_suite_stats(suite, suite_stats, total_stats); 587 suite_end: 588 kunit_print_suite_end(suite); 589 590 return 0; 591 } 592 EXPORT_SYMBOL_GPL(kunit_run_tests); 593 594 static void kunit_init_suite(struct kunit_suite *suite) 595 { 596 kunit_debugfs_create_suite(suite); 597 suite->status_comment[0] = '\0'; 598 suite->suite_init_err = 0; 599 } 600 601 bool kunit_enabled(void) 602 { 603 return enable_param; 604 } 605 606 int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_suites) 607 { 608 unsigned int i; 609 610 if (!kunit_enabled() && num_suites > 0) { 611 pr_info("kunit: disabled\n"); 612 return 0; 613 } 614 615 for (i = 0; i < num_suites; i++) { 616 kunit_init_suite(suites[i]); 617 kunit_run_tests(suites[i]); 618 } 619 return 0; 620 } 621 EXPORT_SYMBOL_GPL(__kunit_test_suites_init); 622 623 static void kunit_exit_suite(struct kunit_suite *suite) 624 { 625 kunit_debugfs_destroy_suite(suite); 626 } 627 628 void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites) 629 { 630 unsigned int i; 631 632 if (!kunit_enabled()) 633 return; 634 635 for (i = 0; i < num_suites; i++) 636 kunit_exit_suite(suites[i]); 637 638 kunit_suite_counter = 1; 639 } 640 EXPORT_SYMBOL_GPL(__kunit_test_suites_exit); 641 642 #ifdef CONFIG_MODULES 643 static void kunit_module_init(struct module *mod) 644 { 645 __kunit_test_suites_init(mod->kunit_suites, mod->num_kunit_suites); 646 } 647 648 static void kunit_module_exit(struct module *mod) 649 { 650 __kunit_test_suites_exit(mod->kunit_suites, mod->num_kunit_suites); 651 } 652 653 static int kunit_module_notify(struct notifier_block *nb, unsigned long val, 654 void *data) 655 { 656 struct module *mod = data; 657 658 switch (val) { 659 case MODULE_STATE_LIVE: 660 kunit_module_init(mod); 661 break; 662 case MODULE_STATE_GOING: 663 kunit_module_exit(mod); 664 break; 665 case MODULE_STATE_COMING: 666 case MODULE_STATE_UNFORMED: 667 break; 668 } 669 670 return 0; 671 } 672 673 static struct notifier_block kunit_mod_nb = { 674 .notifier_call = kunit_module_notify, 675 .priority = 0, 676 }; 677 #endif 678 679 struct kunit_kmalloc_array_params { 680 size_t n; 681 size_t size; 682 gfp_t gfp; 683 }; 684 685 static int kunit_kmalloc_array_init(struct kunit_resource *res, void *context) 686 { 687 struct kunit_kmalloc_array_params *params = context; 688 689 res->data = kmalloc_array(params->n, params->size, params->gfp); 690 if (!res->data) 691 return -ENOMEM; 692 693 return 0; 694 } 695 696 static void kunit_kmalloc_array_free(struct kunit_resource *res) 697 { 698 kfree(res->data); 699 } 700 701 void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) 702 { 703 struct kunit_kmalloc_array_params params = { 704 .size = size, 705 .n = n, 706 .gfp = gfp 707 }; 708 709 return kunit_alloc_resource(test, 710 kunit_kmalloc_array_init, 711 kunit_kmalloc_array_free, 712 gfp, 713 ¶ms); 714 } 715 EXPORT_SYMBOL_GPL(kunit_kmalloc_array); 716 717 static inline bool kunit_kfree_match(struct kunit *test, 718 struct kunit_resource *res, void *match_data) 719 { 720 /* Only match resources allocated with kunit_kmalloc() and friends. */ 721 return res->free == kunit_kmalloc_array_free && res->data == match_data; 722 } 723 724 void kunit_kfree(struct kunit *test, const void *ptr) 725 { 726 if (!ptr) 727 return; 728 729 if (kunit_destroy_resource(test, kunit_kfree_match, (void *)ptr)) 730 KUNIT_FAIL(test, "kunit_kfree: %px already freed or not allocated by kunit", ptr); 731 } 732 EXPORT_SYMBOL_GPL(kunit_kfree); 733 734 void kunit_cleanup(struct kunit *test) 735 { 736 struct kunit_resource *res; 737 unsigned long flags; 738 739 /* 740 * test->resources is a stack - each allocation must be freed in the 741 * reverse order from which it was added since one resource may depend 742 * on another for its entire lifetime. 743 * Also, we cannot use the normal list_for_each constructs, even the 744 * safe ones because *arbitrary* nodes may be deleted when 745 * kunit_resource_free is called; the list_for_each_safe variants only 746 * protect against the current node being deleted, not the next. 747 */ 748 while (true) { 749 spin_lock_irqsave(&test->lock, flags); 750 if (list_empty(&test->resources)) { 751 spin_unlock_irqrestore(&test->lock, flags); 752 break; 753 } 754 res = list_last_entry(&test->resources, 755 struct kunit_resource, 756 node); 757 /* 758 * Need to unlock here as a resource may remove another 759 * resource, and this can't happen if the test->lock 760 * is held. 761 */ 762 spin_unlock_irqrestore(&test->lock, flags); 763 kunit_remove_resource(test, res); 764 } 765 current->kunit_test = NULL; 766 } 767 EXPORT_SYMBOL_GPL(kunit_cleanup); 768 769 static int __init kunit_init(void) 770 { 771 kunit_debugfs_init(); 772 #ifdef CONFIG_MODULES 773 return register_module_notifier(&kunit_mod_nb); 774 #else 775 return 0; 776 #endif 777 } 778 late_initcall(kunit_init); 779 780 static void __exit kunit_exit(void) 781 { 782 #ifdef CONFIG_MODULES 783 unregister_module_notifier(&kunit_mod_nb); 784 #endif 785 kunit_debugfs_cleanup(); 786 } 787 module_exit(kunit_exit); 788 789 MODULE_LICENSE("GPL v2"); 790