将Python 3资源警告转换为异常

12 投票
2 回答
1756 浏览
提问于 2025-04-18 13:05

有没有办法让Python 3的单元测试在出现任何资源警告时强制失败,而不是仅仅在错误输出中打印一个警告呢?

我试过以下方法:

import warnings
warnings.simplefilter(action='error', category=ResourceWarning)

这导致单元测试输出了以下内容:

my_test (__main__.MyTest) ... Exception ignored in: <socket.socket fd=9, family=AddressFamily.AF_INET, type=SocketType.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 54065), raddr=('127.0.0.1', 27017)>
ResourceWarning: unclosed <socket.socket fd=9, family=AddressFamily.AF_INET, type=SocketType.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 54065), raddr=('127.0.0.1', 27017)>
ok

----------------------------------------------------------------------
Ran 1 test in 0.110s

注意到“异常被忽略”的信息了吗?我希望测试能直接失败,而不是让我去看输出寻找资源警告。

2 个回答

6

很遗憾,这似乎是不可能的。那个“在这里忽略的异常”消息是由CPython中的一个函数PyErr_WriteUnraisable生成的,具体在Python/errors.c文件里。这个函数前面的注释提到:

/* Call when an exception has occurred but there is no way for Python
   to handle it.  Examples: exception in __del__ or during GC. */

实际上,ResourceWarning是在垃圾回收时产生的,Python会打印出这个消息,因为在那个时候它无法抛出异常。这与CPython的核心实现有关,unittest无法对此进行覆盖。

更新:虽然上面的分析在技术上是正确的,但还有另一种方法可以真正解决提问者的问题。更多细节请查看J.F. Sebastian的回答

10

这里有一个单元测试,如果在with catch_warning()语句中的代码产生了ResourceWarning警告,这个测试就会失败:

#!/usr/bin/env python3
import gc
import socket
import unittest
import warnings

class Test(unittest.TestCase):
    def test_resource_warning(self):
        s = socket.socket()
        ####s.close() #XXX uncomment to pass the test

        # generate resource warning when s is deleted
        with warnings.catch_warnings(record=True) as w:
            warnings.resetwarnings() # clear all filters
            warnings.simplefilter('ignore') # ignore all
            warnings.simplefilter('always', ResourceWarning) # add filter
            del s        # remove reference
            gc.collect() # run garbage collection (for pypy3)
            self.assertFalse(w and str(w[-1])) # test fails if there
                                               # are warnings

if __name__=="__main__":
    unittest.main()

撰写回答