【问题标题】:Faking an IO Error on Linux在 Linux 上伪造 IO 错误
【发布时间】:2014-01-28 19:07:54
【问题描述】:

我在 Linux 上有一个 Python 和 C 应用程序,它应该在从磁盘读取文件时正确处理 IO 错误。大部分应用程序是用 Python 编写的,并带有一个执行 IO 的 C 扩展。检测到 IO 错误就是在这个扩展中。

在我看来,错误有两种情况。

  1. 文件丢失。
  2. 磁盘上的文件(使用stat)看起来比使用fread 读取的要大。

我可以相当轻松地测试和处理案例 1。但是,我还想为案例 2 编写一个单元测试。但是,我不知道如何触发测试的“假”IO 错误。这甚至可能吗?有没有更好的方法来测试这种错误?

【问题讨论】:

  • 旁注:模拟文件在成功打开后消失(例如,它在记忆棒上,现在被移除)可能会成为另一个有趣的案例。
  • Case 2 不会给你一个 IO 错误,它只会返回比你预期更少的字节。 fread 将返回读取的元素数量(对于大多数其他函数也是如此,无论是在 C/POSIX 中还是在 Python 中)。如果你打电话给ferror()feof() 来检查为什么你得到的结果比预期的少,你会分别得到零和非零。那么,您是在尝试测试实际的 I/O 错误,还是您的案例 2?
  • @abarnert 我指的是 errno 值,在这种情况下为 (EIO == 5)。这在技术上不是 IO 错误吗?
  • @chux 这让我想到了,虽然我还不确定我将如何测试它。这可能意味着我必须重构我的代码。 :-/
  • errno 值来自什么?你打电话给fread,然后ferror 告诉你有错误,然后errno 设置为EIO?如果是这样,这并不意味着文件比预期的要短,这意味着发生了其他事情(如物理读取错误)。如果文件比预期的短,那不是错误,ferror 必须告诉你。 (如果在errno 中碰巧有一些旧错误,那就无关紧要了。)除非我怀疑 linux 或 glibc 中存在严重的错误。

标签: python c linux unit-testing


【解决方案1】:

errno(3) 设置为 EIO 仅用于

   EIO    Input/output error (POSIX.1)

另外,根据read(2)

   EIO    I/O error.  This will happen for example when the process is
          in a background process group, tries to read from its
          controlling terminal, and either it is ignoring or blocking
          SIGTTIN or its process group is orphaned.  It may also occur
          when there is a low-level I/O error while reading from a 
          disk or tape.

并根据write(2)为:

   EIO    A low-level I/O error occurred while modifying the inode.

因此模拟特定的错误代码可能很困难;请注意,还有其他用于 I/O 的系统调用,特别是 writev(2) 和(间接)mmap(2),但 read(2)write(2) 是最常见的。

还要注意file systemsLinux kernel(例如它的VFS 层)是caching 数据。您可能会在很久以后或永远不会收到EIO。见sync(2)fsync(2)

但是,一般来说,大多数软件不处理 EIO 特别是 w.r.t。其他错误代码;您可能通过获取另一个错误代码来进行足够的测试,例如

  EDQUOT The user's quota of disk blocks on the filesystem containing
          the file referred to by fd has been exhausted.

因此,您可能会通过限制 disk quotas(参见 quotactl(2)setquota(8) 等...)和文件空间(参见 setrlimit(2)RLIMIT_FSIZEprlimit(1)ulimit 内置bash(1) 等...)

如果您真的想专门伪造EIO,您可能会物理损坏设备(或者可能只是在错误的时刻拔掉USB 磁盘)或编写自己的Filesystem in User Space (FUSE) 来模拟它。我认为这不值得付出努力(因为当某些东西得到EIO 时,整个计算机很快就会变得无法使用,无论如何用户都会注意到这一点......而且因为大多数软件同样处理所有错误代码 - 除了@ 987654357@)

在代码的 C 部分中,您可能希望使用strerror(3)(可能与syslog(3))和/或perror(3)。我不确定是否值得努力以与大多数其他错误截然不同的方式处理 EIO

注意:许多关键领域都有定义如何处理错误以及如何开发和测试代码的标准,例如ISO26262 用于汽车或DO-178B 用于航空电子设备。遵循您所在领域的标准。

【讨论】:

    【解决方案2】:

    据我了解,TDD 经典警告我们不要为 3rd 方接口(包括标准库)编写模拟/存根,请参阅例如here。主要问题是应用程序代码和通用的第 3 方库之间通常存在差距,这很难与模拟对象绑定。此外,这会阻止您使用测试来推导设计问题。

    (即使在您的情况下,C 库并不完全是第 3 方,但单元测试意味着您要单独测试实体)。

    这个想法是,您编写一个适配器类来封装所有低级逻辑并公开一个接近您的应用程序需要的接口(例如,引发更有意义的异常,如FileIsTooBig)。然后你根据你的领域编写模拟对象。至于适配器本身的测试,通过几个简单的系统测试来测试。

    【讨论】:

    • 这是有道理的。我接受这个答案是因为它可以帮助我理解我应该如何从软件架构的角度来解决这个问题。
    【解决方案3】:

    使用fusepy

    fusepy 是 FUSE 之上的一个 python 层,它允许在 Linux 用户空间中实现文件系统。 fusepy 是一个 Python 模块,它为 FUSE 和 MacFUSE 提供了一个简单的接口。它只是一个文件,使用 ctypes 实现。使用 fusepy,您可以修改 write 函数实现的行为,并根据需要抛出 EIO。我会使用memory.py 示例作为基础。

    【讨论】:

      【解决方案4】:

      libfiu(如"How can I simulate a failed disk during testing?" answer 中所述)是一种结构化方法,用于使用插入来执行 POSIX 调用的故障注入,非常适合在测试套件中使用。

      list of Linux disk fault injection mechanisms"Special File that causes I/O error" question 的回答中提到了更通用的技术列表(例如使用 FUSE 文件系统)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-22
        • 2011-04-30
        • 2018-05-18
        • 1970-01-01
        • 2012-11-07
        • 1970-01-01
        • 2011-08-11
        • 1970-01-01
        相关资源
        最近更新 更多