【问题标题】:PDFs produced by R having inconsistent MD5 checksumR 生成的 PDF 具有不一致的 MD5 校验和
【发布时间】:2019-11-27 22:37:17
【问题描述】:

我正在使用 testthat 测试一个 R 包。为 S3 方法 plot.foo 编写测试是一件非常头疼的事情,因为它只返回 NULL,所以我决定将绘图保存到文件中并检查自上次运行以来它是否已更改。

pdf(file='plot_foo.pdf')
plot.foo(bar)
dev.off()
tools::md5sum('plot_foo.pdf')

问题是每次我使用相同的输入得到不同的结果。不过,输出看起来是一样的。

replicate(10, {
  pdf(file='plot.pdf')
  plot(1:10, 10:1)
  dev.off()
  Sys.sleep(1)
  tools::md5sum('plot.pdf')
})

请注意,您需要在每次迭代之间等待一段时间,否则文件将是相同的,这让我怀疑某些基于时间的元数据已更改。

                          plot.pdf                           plot.pdf
"5a0c096fe088342bc3c3d5960c5da1c9" "40d93c26b4901aef55a32b75473d05d2"
                          plot.pdf                           plot.pdf
"9815c6d9b2e94cda763a486fcd2ddf08" "a8e8db82d06b79f98416fa034b5aee46"
                          plot.pdf                           plot.pdf
"c2770250dbef3b60706559114c434851" "91c8cf124eb61ddebd3edbbb2d01677f"
                          plot.pdf                           plot.pdf
"d1594bd83b97fc890410a4c305366682" "f05197f165ec04df3dac4664494f4617"
                          plot.pdf                           plot.pdf
"64427124c6a6454e8f0e5944de20be95" "ff1abf2b31dfe688cf8f5994e409cc6d"

如何强制 R 生成一致的 PDF?我暂时切换到 PostScript 进行测试,但我更喜欢 PDF,因为它的支持更好(Windows 似乎没有内置 PostScript 查看器),因此也可以用作文档。

【问题讨论】:

  • 您是否尝试过查看不同 PDF 之间的差异以了解发生了什么变化?
  • 我认为 PDF 通常在其中包含 CreatedDate 元数据。在我最近生成的一个文件上,我做了pdftk myfile.pdf dump_data 并看到它有一个时间戳。我怀疑使用校验和总是会对像这样可以忽略不计的差异敏感。
  • 是的,我刚刚做了一些检查(使用此方法:superuser.com/questions/125376/…),看起来每个文件中有 1 或 2 个时间戳最终不同,并且没有明显的方法可以禁用时间戳在pdf().
  • 为了测试,您可以将绘图保存为 PNG 文件(或将 PDF 转换为 PNG)。
  • 查看vdiffr。根据其描述:“视觉回归测试和图形差异:'testthat' 包的扩展,可以轻松添加图形单元测试。它提供了一个闪亮的应用程序来管理测试用例。”

标签: r pdf plot graphics metadata


【解决方案1】:

虽然我认为在一些事情上有点粗略,但我认为 vdiffr 会让你做你需要的。

  1. 首先,我要创建一个包;现在是假的,但很有必要,因为vdiffr 只能在严格控制的环境中工作:使用testthat 的包。

    usethis::create_package("~/StackOverflow/nalzok")
    setwd("~/StackOverflow/nalzok")
    usethis::use_testthat()
    
  2. 创建一个test_something.R 测试文件。

    context("basic plot tests")
    baseplot1 <- function() hist(1:10)
    vdiffr::expect_doppelganger("base 1", baseplot1)
    

    (我将假设 hist(1:10) 是相关且有趣的东西。基图需要是一个函数,ggplot2 对象不是;有关更多信息,请参阅文档。)

  3. 我原以为我可以直接调用vdiffr::expect_doppelganger(大多数testthat::expect_* 函数通常都可以),但首先需要对其进行“管理”(设置)。

    vdiffr::manage_cases(".")
    

    每个图像都需要“验证”(由人类),因此这会打开一个闪亮的应用程序,它会遍历每个预期的分身:

  4. 验证后,每次test打包,都会验证图片没有变化:

    devtools::test()
    # Loading nalzok
    # Testing nalzok
    # v | OK F W S | Context
    # v |  1       | basic plot tests
    # == Results =====================================================================
    # OK:       1
    # Failed:   0
    # Warnings: 0
    # Skipped:  0
    
  5. 如果发生变化(可能将hist(1:10) 更改为hist(2:11)),它将无法通过下一次测试:

    devtools::test()
    # Loading nalzok
    # Testing nalzok
    # v | OK F W S | Context
    # x |  0 1     | basic plot tests
    # --------------------------------------------------------------------------------
    # test_something.R:3: failure: (unknown)
    # Figures don't match: base-1.svg
    # --------------------------------------------------------------------------------
    # == Results =====================================================================
    # OK:       0
    # Failed:   1
    # Warnings: 0
    # Skipped:  0
    

    它通过为每个期望创建一个带有目录和.svg 文件的./tests/testthat/figs/ 目录来实现这一点,虽然您不需要与之交互,但.../figs/ 受版本控制是有意义的(你版本控制你的包,对吧?)。

一些警告,我猜:

  • 它正在保存到.svg 文件;如果您的 S3 plot.foo 函数不能很好地与 SVG 配合使用(会发生这种情况吗?我不知道),那么我(尚)不知道如何处理;

  • 由于它使用基于文本的 SVG 格式,它会注意到点或框或其他东西是否发生移动,但仅在一些基本公差范围内;例如,即使某些元参数(限制)被充分改变,也会触发失败。这通常很好,因为我相信测试应该能够适应微小的变化(上游库等)。

    hist(1:10)                    # pass
    hist(1:10, xlim=c(0,10))      # pass, that's the default x-limit given the data
    hist(1:10, xlim=c(0,10+1e-5)) # pass, close enough?
    hist(1:10, xlim=c(0,10+1e-4)) # FAIL
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-02
    相关资源
    最近更新 更多