Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(33151)

Delta Between Two Patch Sets: Lib/test/test_doctest.py

Issue 24746: doctest 'fancy diff' formats incorrectly strip trailing whitespace
Left Patch Set: Created 4 years, 2 months ago
Right Patch Set: Created 4 years, 2 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « Lib/doctest.py ('k') | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 """ 1 """
2 Test script for doctest. 2 Test script for doctest.
3 """ 3 """
4 4
5 from test import support 5 from test import support
6 import doctest 6 import doctest
7 import functools
7 import os 8 import os
8 import sys 9 import sys
9 10
10 11
11 # NOTE: There are some additional tests relating to interaction with 12 # NOTE: There are some additional tests relating to interaction with
12 # zipimport in the test_zipimport_support test module. 13 # zipimport in the test_zipimport_support test module.
13 14
14 ###################################################################### 15 ######################################################################
15 ## Sample Objects (used by test cases) 16 ## Sample Objects (used by test cases)
16 ###################################################################### 17 ######################################################################
(...skipping 410 matching lines...) Expand 10 before | Expand all | Expand 10 after
427 428
428 We'll simulate a __file__ attr that ends in pyc: 429 We'll simulate a __file__ attr that ends in pyc:
429 430
430 >>> import test.test_doctest 431 >>> import test.test_doctest
431 >>> old = test.test_doctest.__file__ 432 >>> old = test.test_doctest.__file__
432 >>> test.test_doctest.__file__ = 'test_doctest.pyc' 433 >>> test.test_doctest.__file__ = 'test_doctest.pyc'
433 434
434 >>> tests = finder.find(sample_func) 435 >>> tests = finder.find(sample_func)
435 436
436 >>> print(tests) # doctest: +ELLIPSIS 437 >>> print(tests) # doctest: +ELLIPSIS
437 [<DocTest sample_func from ...:18 (1 example)>] 438 [<DocTest sample_func from ...:19 (1 example)>]
438 439
439 The exact name depends on how test_doctest was invoked, so allow for 440 The exact name depends on how test_doctest was invoked, so allow for
440 leading path components. 441 leading path components.
441 442
442 >>> tests[0].filename # doctest: +ELLIPSIS 443 >>> tests[0].filename # doctest: +ELLIPSIS
443 '...test_doctest.py' 444 '...test_doctest.py'
444 445
445 >>> test.test_doctest.__file__ = old 446 >>> test.test_doctest.__file__ = old
446 447
447 448
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after
651 def non_Python_modules(): r""" 652 def non_Python_modules(): r"""
652 653
653 Finding Doctests in Modules Not Written in Python 654 Finding Doctests in Modules Not Written in Python
654 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 655 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
655 DocTestFinder can also find doctests in most modules not written in Python. 656 DocTestFinder can also find doctests in most modules not written in Python.
656 We'll use builtins as an example, since it almost certainly isn't written in 657 We'll use builtins as an example, since it almost certainly isn't written in
657 plain ol' Python and is guaranteed to be available. 658 plain ol' Python and is guaranteed to be available.
658 659
659 >>> import builtins 660 >>> import builtins
660 >>> tests = doctest.DocTestFinder().find(builtins) 661 >>> tests = doctest.DocTestFinder().find(builtins)
661 >>> 790 < len(tests) < 800 # approximate number of objects with docstrings 662 >>> 790 < len(tests) < 810 # approximate number of objects with docstrings
662 True 663 True
663 >>> real_tests = [t for t in tests if len(t.examples) > 0] 664 >>> real_tests = [t for t in tests if len(t.examples) > 0]
664 >>> len(real_tests) # objects that actually have doctests 665 >>> len(real_tests) # objects that actually have doctests
665 8 666 8
666 >>> for t in real_tests: 667 >>> for t in real_tests:
667 ... print('{} {}'.format(len(t.examples), t.name)) 668 ... print('{} {}'.format(len(t.examples), t.name))
668 ... 669 ...
669 1 builtins.bin 670 1 builtins.bin
670 3 builtins.float.as_integer_ratio 671 3 builtins.float.as_integer_ratio
671 2 builtins.float.fromhex 672 2 builtins.float.fromhex
(...skipping 727 matching lines...) Expand 10 before | Expand all | Expand 10 after
1399 you use NDIFF, but at least it is now there to be found. 1400 you use NDIFF, but at least it is now there to be found.
1400 1401
1401 >>> def f(x): 1402 >>> def f(x):
1402 ... r''' 1403 ... r'''
1403 ... >>> print('\n'.join(['a ', 'b'])) 1404 ... >>> print('\n'.join(['a ', 'b']))
1404 ... a 1405 ... a
1405 ... b 1406 ... b
1406 ... ''' 1407 ... '''
1407 1408
1408 >>> test = doctest.DocTestFinder().find(f)[0] 1409 >>> test = doctest.DocTestFinder().find(f)[0]
1409 >>> flags = doctest.REPORT_UDIFF 1410 >>> flags = doctest.REPORT_NDIFF
1410 >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) 1411 >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
1411 ... # doctest: +ELLIPSIS 1412 ... # doctest: +ELLIPSIS
1412 ********************************************************************** 1413 **********************************************************************
1413 File ..., line 3, in f 1414 File ..., line 3, in f
1414 Failed example: 1415 Failed example:
1415 print('\n'.join(['a ', 'b'])) 1416 print('\n'.join(['a ', 'b']))
1416 Differences (context diff with expected followed by actual): 1417 Differences (ndiff with -expected +actual):
1417 @@ -1,7 +1,7 @@ 1418 - a
1418 -a 1419 + a
1419 +a 1420 b
1420 b
1421 TestResults(failed=1, attempted=1) 1421 TestResults(failed=1, attempted=1)
1422
1423 NOTE: there is significant trailing whitespace on the +a line above;
1424 *DO NOT STRIP IT* when editing this file.
1425 1422
1426 The REPORT_NDIFF flag causes failures to use the difflib.Differ algorithm 1423 The REPORT_NDIFF flag causes failures to use the difflib.Differ algorithm
1427 used by the popular ndiff.py utility. This does intraline difference 1424 used by the popular ndiff.py utility. This does intraline difference
1428 marking, as well as interline differences. 1425 marking, as well as interline differences.
1429 1426
1430 >>> def f(x): 1427 >>> def f(x):
1431 ... r''' 1428 ... r'''
1432 ... >>> print("a b c d e f g h i j k l m") 1429 ... >>> print("a b c d e f g h i j k l m")
1433 ... a b c d e f g h i j k 1 m 1430 ... a b c d e f g h i j k 1 m
1434 ... ''' 1431 ... '''
(...skipping 684 matching lines...) Expand 10 before | Expand all | Expand 10 after
2119 >>> suite = doctest.DocTestSuite('test.sample_doctest') 2116 >>> suite = doctest.DocTestSuite('test.sample_doctest')
2120 >>> suite.run(unittest.TestResult()) 2117 >>> suite.run(unittest.TestResult())
2121 <unittest.result.TestResult run=9 errors=0 failures=4> 2118 <unittest.result.TestResult run=9 errors=0 failures=4>
2122 2119
2123 The module need not contain any doctest examples: 2120 The module need not contain any doctest examples:
2124 2121
2125 >>> suite = doctest.DocTestSuite('test.sample_doctest_no_doctests') 2122 >>> suite = doctest.DocTestSuite('test.sample_doctest_no_doctests')
2126 >>> suite.run(unittest.TestResult()) 2123 >>> suite.run(unittest.TestResult())
2127 <unittest.result.TestResult run=0 errors=0 failures=0> 2124 <unittest.result.TestResult run=0 errors=0 failures=0>
2128 2125
2129 However, if DocTestSuite finds no docstrings, it raises an error: 2126 The module need not contain any docstrings either:
2130 2127
2131 >>> try: 2128 >>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings')
2132 ... doctest.DocTestSuite('test.sample_doctest_no_docstrings')
2133 ... except ValueError as e:
2134 ... error = e
2135
2136 >>> print(error.args[1])
2137 has no docstrings
2138
2139 You can prevent this error by passing a DocTestFinder instance with
2140 the `exclude_empty` keyword argument set to False:
2141
2142 >>> finder = doctest.DocTestFinder(exclude_empty=False)
2143 >>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings',
2144 ... test_finder=finder)
2145 >>> suite.run(unittest.TestResult()) 2129 >>> suite.run(unittest.TestResult())
2146 <unittest.result.TestResult run=0 errors=0 failures=0> 2130 <unittest.result.TestResult run=0 errors=0 failures=0>
2147 2131
2148 We can use the current module: 2132 We can use the current module:
2149 2133
2150 >>> suite = test.sample_doctest.test_suite() 2134 >>> suite = test.sample_doctest.test_suite()
2151 >>> suite.run(unittest.TestResult()) 2135 >>> suite.run(unittest.TestResult())
2152 <unittest.result.TestResult run=9 errors=0 failures=4> 2136 <unittest.result.TestResult run=9 errors=0 failures=4>
2137
2138 We can also provide a DocTestFinder:
2139
2140 >>> finder = doctest.DocTestFinder()
2141 >>> suite = doctest.DocTestSuite('test.sample_doctest',
2142 ... test_finder=finder)
2143 >>> suite.run(unittest.TestResult())
2144 <unittest.result.TestResult run=9 errors=0 failures=4>
2145
2146 The DocTestFinder need not return any tests:
2147
2148 >>> finder = doctest.DocTestFinder()
2149 >>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings',
2150 ... test_finder=finder)
2151 >>> suite.run(unittest.TestResult())
2152 <unittest.result.TestResult run=0 errors=0 failures=0>
2153 2153
2154 We can supply global variables. If we pass globs, they will be 2154 We can supply global variables. If we pass globs, they will be
2155 used instead of the module globals. Here we'll pass an empty 2155 used instead of the module globals. Here we'll pass an empty
2156 globals, triggering an extra error: 2156 globals, triggering an extra error:
2157 2157
2158 >>> suite = doctest.DocTestSuite('test.sample_doctest', globs={}) 2158 >>> suite = doctest.DocTestSuite('test.sample_doctest', globs={})
2159 >>> suite.run(unittest.TestResult()) 2159 >>> suite.run(unittest.TestResult())
2160 <unittest.result.TestResult run=9 errors=0 failures=5> 2160 <unittest.result.TestResult run=9 errors=0 failures=5>
2161 2161
2162 Alternatively, we can provide extra globals. Here we'll make an 2162 Alternatively, we can provide extra globals. Here we'll make an
(...skipping 28 matching lines...) Expand all
2191 ... setUp=setUp, tearDown=tearDown) 2191 ... setUp=setUp, tearDown=tearDown)
2192 >>> suite.run(unittest.TestResult()) 2192 >>> suite.run(unittest.TestResult())
2193 <unittest.result.TestResult run=9 errors=0 failures=3> 2193 <unittest.result.TestResult run=9 errors=0 failures=3>
2194 2194
2195 But the tearDown restores sanity: 2195 But the tearDown restores sanity:
2196 2196
2197 >>> import test.test_doctest 2197 >>> import test.test_doctest
2198 >>> test.test_doctest.sillySetup 2198 >>> test.test_doctest.sillySetup
2199 Traceback (most recent call last): 2199 Traceback (most recent call last):
2200 ... 2200 ...
2201 AttributeError: 'module' object has no attribute 'sillySetup' 2201 AttributeError: module 'test.test_doctest' has no attribute 'sillySetup '
2202 2202
2203 The setUp and tearDown functions are passed test objects. Here 2203 The setUp and tearDown functions are passed test objects. Here
2204 we'll use the setUp function to supply the missing variable y: 2204 we'll use the setUp function to supply the missing variable y:
2205 2205
2206 >>> def setUp(test): 2206 >>> def setUp(test):
2207 ... test.globs['y'] = 1 2207 ... test.globs['y'] = 1
2208 2208
2209 >>> suite = doctest.DocTestSuite('test.sample_doctest', setUp=setUp) 2209 >>> suite = doctest.DocTestSuite('test.sample_doctest', setUp=setUp)
2210 >>> suite.run(unittest.TestResult()) 2210 >>> suite.run(unittest.TestResult())
2211 <unittest.result.TestResult run=9 errors=0 failures=3> 2211 <unittest.result.TestResult run=9 errors=0 failures=3>
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
2337 ... setUp=setUp, tearDown=tearDown) 2337 ... setUp=setUp, tearDown=tearDown)
2338 >>> suite.run(unittest.TestResult()) 2338 >>> suite.run(unittest.TestResult())
2339 <unittest.result.TestResult run=3 errors=0 failures=1> 2339 <unittest.result.TestResult run=3 errors=0 failures=1>
2340 2340
2341 But the tearDown restores sanity: 2341 But the tearDown restores sanity:
2342 2342
2343 >>> import test.test_doctest 2343 >>> import test.test_doctest
2344 >>> test.test_doctest.sillySetup 2344 >>> test.test_doctest.sillySetup
2345 Traceback (most recent call last): 2345 Traceback (most recent call last):
2346 ... 2346 ...
2347 AttributeError: 'module' object has no attribute 'sillySetup' 2347 AttributeError: module 'test.test_doctest' has no attribute 'sillySetup '
2348 2348
2349 The setUp and tearDown functions are passed test objects. 2349 The setUp and tearDown functions are passed test objects.
2350 Here, we'll use a setUp function to set the favorite color in 2350 Here, we'll use a setUp function to set the favorite color in
2351 test_doctest.txt: 2351 test_doctest.txt:
2352 2352
2353 >>> def setUp(test): 2353 >>> def setUp(test):
2354 ... test.globs['favorite_color'] = 'blue' 2354 ... test.globs['favorite_color'] = 'blue'
2355 2355
2356 >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp) 2356 >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp)
2357 >>> suite.run(unittest.TestResult()) 2357 >>> suite.run(unittest.TestResult())
(...skipping 26 matching lines...) Expand all
2384 2384
2385 def test_trailing_space_in_test(): 2385 def test_trailing_space_in_test():
2386 """ 2386 """
2387 Trailing spaces in expected output are significant: 2387 Trailing spaces in expected output are significant:
2388 2388
2389 >>> x, y = 'foo', '' 2389 >>> x, y = 'foo', ''
2390 >>> print(x, y) 2390 >>> print(x, y)
2391 foo \n 2391 foo \n
2392 """ 2392 """
2393 2393
2394 class Wrapper:
2395 def __init__(self, func):
2396 self.func = func
2397 functools.update_wrapper(self, func)
2398
2399 def __call__(self, *args, **kwargs):
2400 self.func(*args, **kwargs)
2401
2402 @Wrapper
2403 def test_look_in_unwrapped():
2404 """
2405 Docstrings in wrapped functions must be detected as well.
2406
2407 >>> 'one other test'
2408 'one other test'
2409 """
2394 2410
2395 def test_unittest_reportflags(): 2411 def test_unittest_reportflags():
2396 """Default unittest reporting flags can be set to control reporting 2412 """Default unittest reporting flags can be set to control reporting
2397 2413
2398 Here, we'll set the REPORT_ONLY_FIRST_FAILURE option so we see 2414 Here, we'll set the REPORT_ONLY_FIRST_FAILURE option so we see
2399 only the first failure of each test. First, we'll look at the 2415 only the first failure of each test. First, we'll look at the
2400 output without the flag. The file test_doctest.txt file has two 2416 output without the flag. The file test_doctest.txt file has two
2401 tests. They both fail if blank lines are disabled: 2417 tests. They both fail if blank lines are disabled:
2402 2418
2403 >>> suite = doctest.DocFileSuite('test_doctest.txt', 2419 >>> suite = doctest.DocFileSuite('test_doctest.txt',
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
2448 favorite_color 2464 favorite_color
2449 ... 2465 ...
2450 Failed example: 2466 Failed example:
2451 if 1: 2467 if 1:
2452 print('a') 2468 print('a')
2453 print() 2469 print()
2454 print('b') 2470 print('b')
2455 Differences (ndiff with -expected +actual): 2471 Differences (ndiff with -expected +actual):
2456 a 2472 a
2457 - <BLANKLINE> 2473 - <BLANKLINE>
2458 + 2474 +
2459 b 2475 b
2460 <BLANKLINE> 2476 <BLANKLINE>
2461 <BLANKLINE> 2477 <BLANKLINE>
2462 2478
2463 2479
2464 Test runners can restore the formatting flags after they run: 2480 Test runners can restore the formatting flags after they run:
2465 2481
2466 >>> ignored = doctest.set_unittest_reportflags(old) 2482 >>> ignored = doctest.set_unittest_reportflags(old)
2467 2483
2468 """ 2484 """
(...skipping 263 matching lines...) Expand 10 before | Expand all | Expand 10 after
2732 Note: we also pass TERM='' to all the assert_python calls to avoid a bug 2748 Note: we also pass TERM='' to all the assert_python calls to avoid a bug
2733 in the readline library that is triggered in these tests because we are 2749 in the readline library that is triggered in these tests because we are
2734 running them in a new python process. See: 2750 running them in a new python process. See:
2735 2751
2736 http://lists.gnu.org/archive/html/bug-readline/2013-06/msg00000.html 2752 http://lists.gnu.org/archive/html/bug-readline/2013-06/msg00000.html
2737 2753
2738 With those preliminaries out of the way, we'll start with a file with two 2754 With those preliminaries out of the way, we'll start with a file with two
2739 simple tests and no errors. We'll run both the unadorned doctest command, and 2755 simple tests and no errors. We'll run both the unadorned doctest command, and
2740 the verbose version, and then check the output: 2756 the verbose version, and then check the output:
2741 2757
2742 >>> from test import script_helper 2758 >>> from test.support import script_helper, temp_dir
2743 >>> with script_helper.temp_dir() as tmpdir: 2759 >>> with temp_dir() as tmpdir:
2744 ... fn = os.path.join(tmpdir, 'myfile.doc') 2760 ... fn = os.path.join(tmpdir, 'myfile.doc')
2745 ... with open(fn, 'w') as f: 2761 ... with open(fn, 'w') as f:
2746 ... _ = f.write('This is a very simple test file.\n') 2762 ... _ = f.write('This is a very simple test file.\n')
2747 ... _ = f.write(' >>> 1 + 1\n') 2763 ... _ = f.write(' >>> 1 + 1\n')
2748 ... _ = f.write(' 2\n') 2764 ... _ = f.write(' 2\n')
2749 ... _ = f.write(' >>> "a"\n') 2765 ... _ = f.write(' >>> "a"\n')
2750 ... _ = f.write(" 'a'\n") 2766 ... _ = f.write(" 'a'\n")
2751 ... _ = f.write('\n') 2767 ... _ = f.write('\n')
2752 ... _ = f.write('And that is it.\n') 2768 ... _ = f.write('And that is it.\n')
2753 ... rc1, out1, err1 = script_helper.assert_python_ok( 2769 ... rc1, out1, err1 = script_helper.assert_python_ok(
(...skipping 29 matching lines...) Expand all
2783 2799
2784 Now we'll write a couple files, one with three tests, the other a python module 2800 Now we'll write a couple files, one with three tests, the other a python module
2785 with two tests, both of the files having "errors" in the tests that can be made 2801 with two tests, both of the files having "errors" in the tests that can be made
2786 non-errors by applying the appropriate doctest options to the run (ELLIPSIS in 2802 non-errors by applying the appropriate doctest options to the run (ELLIPSIS in
2787 the first file, NORMALIZE_WHITESPACE in the second). This combination will 2803 the first file, NORMALIZE_WHITESPACE in the second). This combination will
2788 allow to thoroughly test the -f and -o flags, as well as the doctest command's 2804 allow to thoroughly test the -f and -o flags, as well as the doctest command's
2789 ability to process more than one file on the command line and, since the second 2805 ability to process more than one file on the command line and, since the second
2790 file ends in '.py', its handling of python module files (as opposed to straight 2806 file ends in '.py', its handling of python module files (as opposed to straight
2791 text files). 2807 text files).
2792 2808
2793 >>> from test import script_helper 2809 >>> from test.support import script_helper, temp_dir
2794 >>> with script_helper.temp_dir() as tmpdir: 2810 >>> with temp_dir() as tmpdir:
2795 ... fn = os.path.join(tmpdir, 'myfile.doc') 2811 ... fn = os.path.join(tmpdir, 'myfile.doc')
2796 ... with open(fn, 'w') as f: 2812 ... with open(fn, 'w') as f:
2797 ... _ = f.write('This is another simple test file.\n') 2813 ... _ = f.write('This is another simple test file.\n')
2798 ... _ = f.write(' >>> 1 + 1\n') 2814 ... _ = f.write(' >>> 1 + 1\n')
2799 ... _ = f.write(' 2\n') 2815 ... _ = f.write(' 2\n')
2800 ... _ = f.write(' >>> "abcdef"\n') 2816 ... _ = f.write(' >>> "abcdef"\n')
2801 ... _ = f.write(" 'a...f'\n") 2817 ... _ = f.write(" 'a...f'\n")
2802 ... _ = f.write(' >>> "ajkml"\n') 2818 ... _ = f.write(' >>> "ajkml"\n')
2803 ... _ = f.write(" 'a...l'\n") 2819 ... _ = f.write(" 'a...l'\n")
2804 ... _ = f.write('\n') 2820 ... _ = f.write('\n')
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
2950 usage...invalid...nosuchoption... 2966 usage...invalid...nosuchoption...
2951 2967
2952 """ 2968 """
2953 2969
2954 ###################################################################### 2970 ######################################################################
2955 ## Main 2971 ## Main
2956 ###################################################################### 2972 ######################################################################
2957 2973
2958 def test_main(): 2974 def test_main():
2959 # Check the doctest cases in doctest itself: 2975 # Check the doctest cases in doctest itself:
2960 support.run_doctest(doctest, verbosity=True) 2976 ret = support.run_doctest(doctest, verbosity=True)
2961 # Check the doctest cases defined here: 2977 # Check the doctest cases defined here:
2962 from test import test_doctest 2978 from test import test_doctest
2963 support.run_doctest(test_doctest, verbosity=True) 2979 support.run_doctest(test_doctest, verbosity=True)
2964 2980
2965 import sys, re, io 2981 import sys, re, io
2966 2982
2967 def test_coverage(coverdir): 2983 def test_coverage(coverdir):
2968 trace = support.import_module('trace') 2984 trace = support.import_module('trace')
2969 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,], 2985 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
2970 trace=0, count=1) 2986 trace=0, count=1)
2971 tracer.run('test_main()') 2987 tracer.run('test_main()')
2972 r = tracer.results() 2988 r = tracer.results()
2973 print('Writing coverage results...') 2989 print('Writing coverage results...')
2974 r.write_results(show_missing=True, summary=True, 2990 r.write_results(show_missing=True, summary=True,
2975 coverdir=coverdir) 2991 coverdir=coverdir)
2976 2992
2977 if __name__ == '__main__': 2993 if __name__ == '__main__':
2978 if '-c' in sys.argv: 2994 if '-c' in sys.argv:
2979 test_coverage('/tmp/doctest.cover') 2995 test_coverage('/tmp/doctest.cover')
2980 else: 2996 else:
2981 test_main() 2997 test_main()
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+