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

时间:2018-05-24 13:36:14

标签: list perl debugging eval stack-trace

有没有办法在Perl期间可靠地获取当前行号 多行列表分配没有明确使用__LINE__?我是 将测试用例存储在列表中,并希望用它的行标记每个测试用例 数字。*那样我可以(粗略地) ok($_->[1], 'line ' . $_->[0]) for @tests。 而且,当然,我想保存打字比较 将__LINE__放在每个测试用例的开头:)。我有 找不到办法,我遇到了一些 caller报告的行中令人困惑的行为。

*可能是XY,但我找不到一个模块来执行此操作。

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

MCVE

很长一段时间,因为我尝试了几种不同的选择。 my_evalL()L2{}是我迄今为止尝试的一些内容 - L()就是那个 我最初希望能工作。跳到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

使用__LINE__明确表示正常,但我正在寻找一个 缩写

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

L()使用caller获取行号,稍后报告行 在文件中(!)。

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

当我在L()中打包do{}时,caller会返回正确的内容 行号 - 但只有一次(!)。

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

eval,有趣的是,工作正常。但是,它并不短 比__LINE__

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

字符串eval给出eval内的行号(毫不奇怪)

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

my_eval()是一个字符串eval加上一个基于#line的指令 caller。它还会在文件后面给出一个行号(!)。

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

L2L相同,但需要一个返回列表的块, 而不是 清单本身。它还使用caller作为行号。它 是正确的,但不是两次(!)。 (可能只是因为它是最后一次 item - my_eval也报告第45行。)

那么,这里发生了什么?我听说过Deparse并想知道这是不是 优化相关,但我不知道引擎知道 从哪里开始调查。我也想象这可以用来源完成 过滤器或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()')
;

然而,即使那些在这里不起作用 - 我为每个->L()电话获得第26行。因此,caller看起来所有链式调用都来自$testcases2->...行。那好吧。我仍然有兴趣知道为什么,如果有人能够启发我的话!

2 个答案:

答案 0 :(得分:3)

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()'));
...

保持行号,它有效(除了字符串evals)。

因此,在实际方面,使用caller可以使用单独的调用语句。

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,它设置caller(和警告)的行号。请参阅下面的this postnextstate示例。

解决这个问题的方法是尝试欺骗编译(不知何故),或者更好的是,不要将信息组合在这样的列表中。其中一种方法是answer by cxw

有关相关案例和更多详情,请参阅this post

nextstate示例

这是一个通过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

即使连续的调用是在不同的行上,它们也是同一个语句的一部分,所以它们都附加到同一个nextstate

答案 1 :(得分:2)

修改此答案现在包含在CPAN moduleGitHub)中!

@zdim's answer让我想到了流畅的界面。以下两个hacks 适用于我的特定用例,但这不能帮助我理解问题中报告的行为。如果您能提供帮助,请发布另一个答案!

Hack 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]行都加载了额外的零字符。一些开销,但不多!

Hack 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