@@ -2611,6 +2611,239 @@ def test_format_trimmed() -> None:
2611
2611
assert _format_trimmed (" ({}) " , msg , len (msg ) + 3 ) == " (unconditional ...) "
2612
2612
2613
2613
2614
+ class TestFineGrainedTestCase :
2615
+ DEFAULT_FILE_CONTENTS = """
2616
+ import pytest
2617
+
2618
+ @pytest.mark.parametrize("i", range(4))
2619
+ def test_ok(i):
2620
+ '''
2621
+ some docstring
2622
+ '''
2623
+ pass
2624
+
2625
+ def test_fail():
2626
+ assert False
2627
+ """
2628
+ LONG_SKIP_FILE_CONTENTS = """
2629
+ import pytest
2630
+
2631
+ @pytest.mark.skip(
2632
+ "some long skip reason that will not fit on a single line with other content that goes"
2633
+ " on and on and on and on and on"
2634
+ )
2635
+ def test_skip():
2636
+ pass
2637
+ """
2638
+
2639
+ @pytest .mark .parametrize ("verbosity" , [1 , 2 ])
2640
+ def test_execute_positive (self , verbosity , pytester : Pytester ) -> None :
2641
+ # expected: one test case per line (with file name), word describing result
2642
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = verbosity )
2643
+ result = pytester .runpytest (p )
2644
+
2645
+ result .stdout .fnmatch_lines (
2646
+ [
2647
+ "collected 5 items" ,
2648
+ "" ,
2649
+ f"{ p .name } ::test_ok[0] PASSED [ 20%]" ,
2650
+ f"{ p .name } ::test_ok[1] PASSED [ 40%]" ,
2651
+ f"{ p .name } ::test_ok[2] PASSED [ 60%]" ,
2652
+ f"{ p .name } ::test_ok[3] PASSED [ 80%]" ,
2653
+ f"{ p .name } ::test_fail FAILED [100%]" ,
2654
+ ],
2655
+ consecutive = True ,
2656
+ )
2657
+
2658
+ def test_execute_0_global_1 (self , pytester : Pytester ) -> None :
2659
+ # expected: one file name per line, single character describing result
2660
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = 0 )
2661
+ result = pytester .runpytest ("-v" , p )
2662
+
2663
+ result .stdout .fnmatch_lines (
2664
+ [
2665
+ "collecting ... collected 5 items" ,
2666
+ "" ,
2667
+ f"{ p .name } ....F [100%]" ,
2668
+ ],
2669
+ consecutive = True ,
2670
+ )
2671
+
2672
+ @pytest .mark .parametrize ("verbosity" , [- 1 , - 2 ])
2673
+ def test_execute_negative (self , verbosity , pytester : Pytester ) -> None :
2674
+ # expected: single character describing result
2675
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = verbosity )
2676
+ result = pytester .runpytest (p )
2677
+
2678
+ result .stdout .fnmatch_lines (
2679
+ [
2680
+ "collected 5 items" ,
2681
+ "....F [100%]" ,
2682
+ ],
2683
+ consecutive = True ,
2684
+ )
2685
+
2686
+ def test_execute_skipped_positive_2 (self , pytester : Pytester ) -> None :
2687
+ # expected: one test case per line (with file name), word describing result, full reason
2688
+ p = TestFineGrainedTestCase ._initialize_files (
2689
+ pytester ,
2690
+ verbosity = 2 ,
2691
+ file_contents = TestFineGrainedTestCase .LONG_SKIP_FILE_CONTENTS ,
2692
+ )
2693
+ result = pytester .runpytest (p )
2694
+
2695
+ result .stdout .fnmatch_lines (
2696
+ [
2697
+ "collected 1 item" ,
2698
+ "" ,
2699
+ f"{ p .name } ::test_skip SKIPPED (some long skip" ,
2700
+ "reason that will not fit on a single line with other content that goes" ,
2701
+ "on and on and on and on and on) [100%]" ,
2702
+ ],
2703
+ consecutive = True ,
2704
+ )
2705
+
2706
+ def test_execute_skipped_positive_1 (self , pytester : Pytester ) -> None :
2707
+ # expected: one test case per line (with file name), word describing result, reason truncated
2708
+ p = TestFineGrainedTestCase ._initialize_files (
2709
+ pytester ,
2710
+ verbosity = 1 ,
2711
+ file_contents = TestFineGrainedTestCase .LONG_SKIP_FILE_CONTENTS ,
2712
+ )
2713
+ result = pytester .runpytest (p )
2714
+
2715
+ result .stdout .fnmatch_lines (
2716
+ [
2717
+ "collected 1 item" ,
2718
+ "" ,
2719
+ f"{ p .name } ::test_skip SKIPPED (some long ski...) [100%]" ,
2720
+ ],
2721
+ consecutive = True ,
2722
+ )
2723
+
2724
+ def test_execute_skipped__0_global_1 (self , pytester : Pytester ) -> None :
2725
+ # expected: one file name per line, single character describing result (no reason)
2726
+ p = TestFineGrainedTestCase ._initialize_files (
2727
+ pytester ,
2728
+ verbosity = 0 ,
2729
+ file_contents = TestFineGrainedTestCase .LONG_SKIP_FILE_CONTENTS ,
2730
+ )
2731
+ result = pytester .runpytest ("-v" , p )
2732
+
2733
+ result .stdout .fnmatch_lines (
2734
+ [
2735
+ "collecting ... collected 1 item" ,
2736
+ "" ,
2737
+ f"{ p .name } s [100%]" ,
2738
+ ],
2739
+ consecutive = True ,
2740
+ )
2741
+
2742
+ @pytest .mark .parametrize ("verbosity" , [- 1 , - 2 ])
2743
+ def test_execute_skipped_negative (self , verbosity , pytester : Pytester ) -> None :
2744
+ # expected: single character describing result (no reason)
2745
+ p = TestFineGrainedTestCase ._initialize_files (
2746
+ pytester ,
2747
+ verbosity = verbosity ,
2748
+ file_contents = TestFineGrainedTestCase .LONG_SKIP_FILE_CONTENTS ,
2749
+ )
2750
+ result = pytester .runpytest (p )
2751
+
2752
+ result .stdout .fnmatch_lines (
2753
+ [
2754
+ "collected 1 item" ,
2755
+ "s [100%]" ,
2756
+ ],
2757
+ consecutive = True ,
2758
+ )
2759
+
2760
+ @pytest .mark .parametrize ("verbosity" , [1 , 2 ])
2761
+ def test__collect_only_positive (self , verbosity , pytester : Pytester ) -> None :
2762
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = verbosity )
2763
+ result = pytester .runpytest ("--collect-only" , p )
2764
+
2765
+ result .stdout .fnmatch_lines (
2766
+ [
2767
+ "collected 5 items" ,
2768
+ "" ,
2769
+ f"<Dir { p .parent .name } >" ,
2770
+ f" <Module { p .name } >" ,
2771
+ " <Function test_ok[0]>" ,
2772
+ " some docstring" ,
2773
+ " <Function test_ok[1]>" ,
2774
+ " some docstring" ,
2775
+ " <Function test_ok[2]>" ,
2776
+ " some docstring" ,
2777
+ " <Function test_ok[3]>" ,
2778
+ " some docstring" ,
2779
+ " <Function test_fail>" ,
2780
+ ],
2781
+ consecutive = True ,
2782
+ )
2783
+
2784
+ def test_collect_only_0_global_1 (self , pytester : Pytester ) -> None :
2785
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = 0 )
2786
+ result = pytester .runpytest ("-v" , "--collect-only" , p )
2787
+
2788
+ result .stdout .fnmatch_lines (
2789
+ [
2790
+ "collecting ... collected 5 items" ,
2791
+ "" ,
2792
+ f"<Dir { p .parent .name } >" ,
2793
+ f" <Module { p .name } >" ,
2794
+ " <Function test_ok[0]>" ,
2795
+ " <Function test_ok[1]>" ,
2796
+ " <Function test_ok[2]>" ,
2797
+ " <Function test_ok[3]>" ,
2798
+ " <Function test_fail>" ,
2799
+ ],
2800
+ consecutive = True ,
2801
+ )
2802
+
2803
+ def test_collect_only_negative_1 (self , pytester : Pytester ) -> None :
2804
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = - 1 )
2805
+ result = pytester .runpytest ("--collect-only" , p )
2806
+
2807
+ result .stdout .fnmatch_lines (
2808
+ [
2809
+ "collected 5 items" ,
2810
+ "" ,
2811
+ f"{ p .name } ::test_ok[0]" ,
2812
+ f"{ p .name } ::test_ok[1]" ,
2813
+ f"{ p .name } ::test_ok[2]" ,
2814
+ f"{ p .name } ::test_ok[3]" ,
2815
+ f"{ p .name } ::test_fail" ,
2816
+ ],
2817
+ consecutive = True ,
2818
+ )
2819
+
2820
+ def test_collect_only_negative_2 (self , pytester : Pytester ) -> None :
2821
+ p = TestFineGrainedTestCase ._initialize_files (pytester , verbosity = - 2 )
2822
+ result = pytester .runpytest ("--collect-only" , p )
2823
+
2824
+ result .stdout .fnmatch_lines (
2825
+ [
2826
+ "collected 5 items" ,
2827
+ "" ,
2828
+ f"{ p .name } : 5" ,
2829
+ ],
2830
+ consecutive = True ,
2831
+ )
2832
+
2833
+ @staticmethod
2834
+ def _initialize_files (
2835
+ pytester : Pytester , verbosity : int , file_contents : str = DEFAULT_FILE_CONTENTS
2836
+ ) -> Path :
2837
+ p = pytester .makepyfile (file_contents )
2838
+ pytester .makeini (
2839
+ f"""
2840
+ [pytest]
2841
+ verbosity_test_cases = { verbosity }
2842
+ """
2843
+ )
2844
+ return p
2845
+
2846
+
2614
2847
def test_summary_xfail_reason (pytester : Pytester ) -> None :
2615
2848
pytester .makepyfile (
2616
2849
"""
0 commit comments