代码之家  ›  专栏  ›  技术社区  ›  cxw

如何获取测试用例的多行列表初始值设定项中的当前行号?

  •  4
  • cxw  · 技术社区  · 6 年前

    在Perl中是否有可靠地获取当前行号的方法 不显式使用的多行列表分配 __LINE__ ?我是 将测试用例存储在列表中,并希望用其行标记每个测试用例 数字。*这样我就可以做(大致上) ok($_->[1], 'line ' . $_->[0]) for @tests . 当然,与 放 _线条__ 在每个测试用例的开头)。我有 找不到方法,我遇到了一些 报告的行中的混乱行为 caller .

    *可能是xy,但我找不到模块来完成。

    更新 我发现了一个黑客并将其发布为 an answer . 感谢@zdim帮助我以不同的方式思考这个问题!

    麦克维尔

    很长的一个,因为我试过几种不同的选择。 my_eval , L() L2{} 是我到目前为止试过的- () 是那个 我最初希望能工作。跳到 my @testcases 看看如何 我在用这些。测试时,请复制shebang行。

    这是我的 non-MCVE use case ,如果您感兴趣。

    #!perl
    use strict; use warnings; use 5.010;
    
    # Modified from https://www.effectiveperlprogramming.com/2011/06/set-the-line-number-and-filename-of-string-evals/#comment-155 by http://sites.google.com/site/shawnhcorey/
    sub my_eval {
        my ( $expr ) = @_;
        my ( undef, $file, $line ) = caller;
        my $code = "# line $line \"$file\"\n" . $expr;
    
        unless(defined wantarray) {
            eval $code; die $@ if $@;
        } elsif(wantarray) {
            my @retval = eval $code; die $@ if $@; return @retval;
        } else {
            my $retval = eval $code; die $@ if $@; return $retval;
        }
    }
    
    sub L {     # Prepend caller's line number
        my (undef, undef, $line) = caller;
        return ["$line", @_];
    } #L
    
    sub L2(&) {     # Prepend caller's line number
        my $fn = shift;
        my (undef, undef, $line) = caller;
        return ["$line", &$fn];
    } #L2
    
    # List of [line number, item index, expected line number, type]
    my @testcases = (
        ([__LINE__,0,32,'LINE']),
        ([__LINE__,1,33,'LINE']),
        (L(2,34,'L()')),
        (L(3,35,'L()')),
        (do { L(4,36,'do {L}') }),
        (do { L(5,37,'do {L}') }),
        (eval { L(6,38,'eval {L}') }),
        (eval { L(7,39,'eval {L}') }),
        (eval "L(8,40,'eval L')"),
        (eval "L(9,41,'eval L')"),
        (my_eval("L(10,42,'my_eval L')")),
        (my_eval("L(11,43,'my_eval L')")),
        (L2{12,44,'L2{}'}),
        (L2{13,45,'L2{}'}),
    );
    
    foreach my $idx (0..$#testcases) {
        printf "%2d %-10s line %2d expected %2d %s\n",
                $idx, $testcases[$idx]->[3], $testcases[$idx]->[0],
                $testcases[$idx]->[2],
                ($testcases[$idx]->[0] != $testcases[$idx]->[2]) && '*';
    }
    

    产量

    添加了我的评论。

     0 LINE       line 32 expected 32
     1 LINE       line 33 expected 33
    

    使用 _线条__ 明确的工作很好,但我正在寻找一个 缩写。

     2 L()        line 45 expected 34 *
     3 L()        line 45 expected 35 *
    

    () 使用 呼叫者 获取行号并报告一行 后来 在文件中 (!).

     4 do {L}     line 36 expected 36
     5 do {L}     line 45 expected 37 *
    

    当我把 () 呼叫 do{} , 呼叫者 返回正确的 行号-但只能一次(!).

     6 eval {L}   line 38 expected 38
     7 eval {L}   line 39 expected 39
    

    街区 eval 有趣的是,效果很好。不过,也不短 比 _线条__ .

     8 eval L     line  1 expected 40 *
     9 eval L     line  1 expected 41 *
    

    埃瓦 埃瓦 (不足为奇)

    10 my_eval L  line 45 expected 42 *
    11 my_eval L  line 45 expected 43 *
    

    my_eval() 是一个字符串 埃瓦 加一 #line 指令基于 呼叫者 . 它还会在文件后面给出一个行号(!).

    12 L2{}       line 45 expected 44 *
    13 L2{}       line 45 expected 45
    

    L2 L 但它需要一个返回列表的块, 而不是 列表本身。它还使用 呼叫者 用于行号。而且它 一次正确,但不是两次(!)(可能是因为它是最后一个 项目- 我的评估 报告第45行。)

    所以, 这是怎么回事?我听说过去舞会,不知道这是不是 优化相关,但我对引擎的了解不够 从哪里开始调查。我也认为这可以通过源代码实现。 过滤器或 Devel::Declare 但是那远远超出了我的范围 经验水平。

    取2

    @Zdim的回答让我开始思考流畅的界面,例如 my answer :

    $testcases2     # line 26
        ->add(__LINE__,0,27,'LINE')
        ->add(__LINE__,1,28,'LINE')
        ->L(2,29,'L()')
        ->L(3,30,'L()')
        ->L(3,31,'L()')
    ;
    

    但是,即使这些在这里也不起作用-我得到了26行 ->L() 电话。所以看起来 呼叫者 将所有链接的呼叫视为来自 $testcases2->... 行。哦,好吧。我仍然有兴趣知道为什么,如果有人能启发我!

    2 回复  |  直到 6 年前
        1
  •  3
  •   cxw    5 年前

    这个 caller 只能获取 声明 ,在汇编时决定。

    当我把代码改为

    my @testcases;
    push @testcases, ([__LINE__,0,32,'LINE']);
    push @testcases, ([__LINE__,1,33,'LINE']);
    push @testcases, (L(2,34,'L()'));
    push @testcases, (L(3,35,'L()'));
    ...
    

    维护行号,它可以工作(字符串eval除外)。

    因此,在实际方面,使用 呼叫者 单独的通话声明可以。

    Perl内部

    在编译和(我的重点)时,行数被烘焙到操作树中。

    在运行时,只有 声明 可用[…]

    ikegami's post on permonks .

    我们可以通过跑步看到这个 perl -MO=Concise script.pl 在哪排队

    2      nextstate(main 25 line_nos.pl:45) v:*,&,{,x*,x&,x$,$,67108864 ->3
    

    是为了 nextstate op,它为 呼叫者 (和警告)。见 this post 下一个状态 下面的例子。

    解决这一问题的一种方法是设法欺骗编译(以某种方式),或者,当然,更好的方法是不要将信息组合成这样的列表。其中一种方法是 answer by cxw .

    this post 有关案例和更多详细信息。

    下一个状态 例子

    这里有一个多行函数调用链贯穿 Deparse (注释):

    $ perl -MO=Concise -e '$x
        ->foo()
        ->bar()
        ->bat()'
    d  <@> leave[1 ref] vKP/REFC ->(end)
    1     <0> enter ->2
    2     <;> nextstate(main 1 -e:1) v:{ ->3    <=== the only nextstate
    c     <1> entersub[t4] vKRS/TARG ->d
    3        <0> pushmark s ->4
    a        <1> entersub[t3] sKRMS/LVINTRO,TARG,INARGS ->b
    4           <0> pushmark s ->5
    8           <1> entersub[t2] sKRMS/LVINTRO,TARG,INARGS ->9
    5              <0> pushmark s ->6
    -              <1> ex-rv2sv sKM/1 ->7
    6                 <#> gvsv[*x] s ->7
    7              <.> method_named[PV "foo"] s ->8
    9           <.> method_named[PV "bar"] s ->a
    b        <.> method_named[PV "bat"] ->c
    -e syntax OK
    

    即使连续调用位于不同的行上,它们也是同一语句的一部分,因此都附加到同一语句 下一个状态 .

        2
  •  2
  •   cxw    5 年前

    编辑 这个答案现在被包装在 CPAN module ( GitHub )!


    @zdim's answer 让我想到了流畅的界面。以下是 两个黑客 这适用于我的特定用例,但这并不能帮助我理解问题中报告的行为。如果你能帮忙,请再发一个答案!

    黑客2(更新) (现在在CPAN上的那个)

    我认为这个非常接近最小值。在Perl中,可以通过引用调用子例程 $ref->() ,您可以省略第二个和后续的 -> 在一连串的箭头中。例如,这意味着你可以做到:

    my $foo; $foo=sub { say shift; return $foo; };
    $foo->(1)
          (2)
          (3);
    

    看起来不错,对吧?这里是mcve:

    #!perl
    use strict; use warnings; use 5.010;
    
    package FluentAutoIncList2 {
        sub new {   # call as $class->new(__LINE__); each element is one line
            my $class = shift;
            my $self = bless {lnum => shift // 0, arr => []}, $class;
    
            # Make a loader that adds an item and returns itself --- not $self
            $self->{loader} = sub { $self->L(@_); return $self->{loader} };
    
            return $self;
        }
        sub size { return scalar @{ shift->{arr} }; }
        sub last { return shift->size-1; }      # $#
    
        sub load { goto &{ shift->{loader} } }  # kick off loading
    
        sub L {     # Push a new record with the next line number on the front
            my $self = shift;
            push @{ $self->{arr} }, [++$self->{lnum}, @_];
            return $self;
        } #L
    
        sub add {   # just add it
            my $self = shift;
            ++$self->{lnum};    # keep it consistent
            push @{ $self->{arr} }, [@_];
            return $self;
        } #add
    
    } #FluentAutoIncList2
    
    # List of [line number, item index, expected line number, type]
    my $testcases = FluentAutoIncList2->new(__LINE__)    # line 28
        ->add(__LINE__,0,36,'LINE')
        ->add(__LINE__,1,37,'LINE');
        $testcases->load(2,38,'load')->     # <== Only need two arrows.
        (3,39,'chain load')                 # <== After that, () are enough.
        (4,40,'chain load')
        (5,41,'chain load')
        (6,42,'chain load')
        (7,43,'chain load')
    ;
    
    foreach my $idx (0..$testcases->last) {
        printf "%2d %-10s line %2d expected %2d %s\n",
                $idx, $testcases->{arr}->[$idx]->[3],
                $testcases->{arr}->[$idx]->[0],
                $testcases->{arr}->[$idx]->[2],
                ($testcases->{arr}->[$idx]->[0] !=
                    $testcases->{arr}->[$idx]->[2]) && '*';
    }
    

    输出:

     0 LINE       line 36 expected 36
     1 LINE       line 37 expected 37
     2 load       line 38 expected 38
     3 chain load line 39 expected 39
     4 chain load line 40 expected 40
     5 chain load line 41 expected 41
     6 chain load line 42 expected 42
     7 chain load line 43 expected 43
    

    所有的 chain load 与原始行相比,加载的行中没有多余的字符 [x, y] 接近。一些开销,但不多!

    黑客1

    代码:

    从…开始 __LINE__ 假设每个电话都有固定数量的线路,计数器就可以做到这一点。这可以用一个 tie .

    #!perl
    use strict; use warnings; use 5.010;
    
    package FluentAutoIncList {
        sub new {   # call as $class->new(__LINE__); each element is one line
            my $class = shift;
            return bless {lnum => shift // 0, arr => []}, $class;
        }
        sub size { return scalar @{ shift->{arr} }; }
        sub last { return shift->size-1; }      # $#
    
        sub L {     # Push a new record with the next line number on the front
            my $self = shift;
            push @{ $self->{arr} }, [++$self->{lnum}, @_];
            return $self;
        } #L
    
        sub add {   # just add it
            my $self = shift;
            ++$self->{lnum};    # keep it consistent
            push @{ $self->{arr} }, [@_];
            return $self;
        } #add
    
    } #FluentAutoIncList
    
    # List of [line number, item index, expected line number, type]
    my $testcases = FluentAutoIncList->new(__LINE__)    # line 28
        ->add(__LINE__,0,29,'LINE')
        ->add(__LINE__,1,30,'LINE')
        ->L(2,31,'L()')
        ->L(3,32,'L()')
        ->L(4,33,'L()')
    ;
    
    foreach my $idx (0..$testcases->last) {
        printf "%2d %-10s line %2d expected %2d %s\n",
                $idx, $testcases->{arr}->[$idx]->[3],
                $testcases->{arr}->[$idx]->[0],
                $testcases->{arr}->[$idx]->[2],
                ($testcases->{arr}->[$idx]->[0] !=
                    $testcases->{arr}->[$idx]->[2]) && '*';
    }
    

    输出:

     0 LINE       line 29 expected 29
     1 LINE       line 30 expected 30
     2 L()        line 31 expected 31
     3 L()        line 32 expected 32
     4 L()        line 33 expected 33