【问题标题】:How can I prevent Perl from using a module, for testing purposes?为了测试目的,我如何防止 Perl 使用模块?
【发布时间】:2010-10-11 15:08:34
【问题描述】:

我正在开发一套 Perl 脚本和模块,然后将它们部署在我们公司周围的不同机器和系统上。 一些设施依赖于特定的模块,该模块可能安装在也可能不安装在不同的机器上。我使用 'eval' 来检测这个模块是否可用。

我刚刚收到一个错误报告,归结为用户没有在他的机器上成功安装模块(但没有意识到他没有):但我的代码中的错误是在这种情况下,我没有将错误条件传递到顶层,因此它丢失了,脚本只是默默地未能执行其部分功能。

为了调查它,我禁用了我机器上的特定模块,并且很容易找到并修复了问题。但除了卸载它之外,我能想到的唯一禁用它的方法是重命名文件(当然,我必须通过 sudo 来完成)。

我现在在这个模块不可用的情况下运行我的所有测试,并且它已经引发了我没有正确处理这种情况的其他一些地方。

但是我现在想做的是为这种情况编写一些测试:但是我怎样才能明智地使这个模块在自动测试中暂时不可用。我真的不希望我的测试使用 sudo 将模块移开(我可能同时在机器上做其他事情)。

有没有人知道我可以告诉 Perl“不要找到这个模块,无论我出于测试目的从哪里‘使用’或‘要求’它”?

我正在运行 Perl 5.10.0(在 Fedora 12 上),并使用 Test::More 和 TAP::Harness。我们的一些安装运行 Perl 5.8,所以我愿意在测试中使用 5.10 的特性,但不是在代码本身中。

【问题讨论】:

  • 一般来说,您可以通过安装和使用自己的 perls 进行测试来解决 sudo 问题。我建议你假装系统 perls 甚至不存在。

标签: perl testing


【解决方案1】:

有几个 CPAN 模块可以做到这一点。我经常使用的是Test::Without::Module。另一个是Devel::Hide。这两个,以及其他一些我现在不太记得名字的人,都以几乎相同的方式工作,通过@INCCORE::GLOBAL::require 连接到perl 的模块加载。详细信息记录在perldoc -f require

【讨论】:

    【解决方案2】:

    正如 rafl 所说,有模块可以做到这一点。

    如果您也对它的机制感兴趣(除了获得结果之外),有两种方法可以做到这一点:

    1. 加载模块后,将其从命名空间中移除。 Test::Without::Module 这样做 - 查看源代码了解详细信息。

    2. 首先防止模块被加载。最简单的方法是使用 Perl 的功能将子例程作为@INC 数组的一部分,当通过use/require 加载模块时使用该数组。这背后的理论可以在require's perldoc 中找到——在文本中搜索单词“hooks”。

    子程序引用是最简单的 案子。当包容系统走时 通过@INC 并遇到一个 子程序,这个子程序得到 用两个参数调用,第一个 对自身的引用,以及第二个 要包含的文件的名称 (例如,“Foo/Bar.pm”)。子程序 应该不返回任何内容或返回 最多三个值的列表...

    ... 2. 对子程序的引用。如果没有文件句柄(上一项),那么这个子例程每次调用都会生成一行源代码,将该行写入 $_ 并返回 1,然后在文件末尾返回 0。

    所以你要做的是编写一个子程序(仅针对指定的包)返回空代码。

    # The following code was not tested - for illustrative purposes only
    # MUST be done in the BEGIN block at the very beginning of the test
    # BEFORE any "use Module"; lines
    push @INC, \&my_sub; 
    my %prohibited_module_files = map { $_=> 1} ("Foo/Bar.pm", "x.pm");
                                  # Ideally, translate module names into file names
    sub empty_module_sub {
        $_ = "1;\n"; # Empty module
        return 0; # End of file
    }
    sub my_sub {
        my ($coderef, $filename) = @_; # $coderef is \&my_sub
        if ($prohibited_modules{$filename}) {
            print STDERR "NOT loading module $filename - prohibited!\n";
            # Optionally, die here to simulate not finding the module!!!
            # Otherwise, load empty package
            return (undef, \&empty_module_sub);
        }
        return undef; # Continue searching @INC for good modules.
    }
    

    稍微简单一点(但没有那么有趣或灵活)的方法将依赖于“require”语义首先检查$INC{$filename},并且如果该键存在于 %INC 哈希中,则认为要加载模块;如果键映射为真值,则认为模块已正确加载,而假值则因“编译失败”类型的错误而死。因此,您可以实现类似于上述自定义子的结果,在代码开头的 BEGIN 块中将适当的键(与模块名称匹配的文件名)下的 undef1 值插入 %INC

    【讨论】:

    • 请注意,Test::Without::Module 也执行 require 挂钩。它只是碰巧有一些额外的魔力,可以使已经加载的模块看起来好像从未加载过,这取决于你对它们的观察程度。
    • @rafl - 好的,需要重新阅读 POD/源代码...没注意到,谢谢!另外,它会让“require”死掉吗?
    • 注意:如果您的代码在执行此示例之后执行任何巧妙的@INC 操作,该操作预先挂在@INC 之前(在您刚刚插入的自定义子例程之前),那么上面的示例代码将无法实现其目的)。
    • 注2:另外,您需要考虑是否要传播到子进程(通过设置$ENV{PERL5OPT}) - 像往常一样,真正可靠的代码的魔鬼在于细节,这就是为什么,self - 除了教育之外,CPAN 模块通常应该用于实际用途 - 例如,Devel::Hide 就是这样做的 :)
    • 它不会 dierequire 中,如果这就是你要问的,但它确实会导致 require 通过提供具有错误返回值的东西来引发异常本身.
    猜你喜欢
    • 1970-01-01
    • 2012-01-10
    • 1970-01-01
    • 1970-01-01
    • 2023-01-20
    • 2021-07-14
    • 2014-12-14
    • 2013-07-04
    • 1970-01-01
    相关资源
    最近更新 更多