试用测试 DNS 查询使反应器处于不干净状态

Trial tests DNS queries leave reactor in unclean state

我正在尝试创建一个使用 DNS 查询执行某些测试的测试。我尝试创建一个最小测试,启动一个监听 DNS 服务器,并使用扭曲的解析器查询该服务器:

from twisted.trial import unittest

from twisted.internet import reactor, defer
from twisted.names import client, dns, error, server


class Tester(unittest.TestCase):
    def setUp(self):
        self.resolver = client.Resolver(resolv='/etc/resolv.conf')
        self.resolver = client.Resolver(servers=[('127.0.0.1', 1025)])
        self.factory = server.DNSServerFactory(clients=[self.resolver])
        self.protocol = dns.DNSDatagramProtocol(controller=self.factory)
        self.port = reactor.listenUDP(1025, self.protocol)

    def tearDown(self):
        self.port.stopListening()

    def test_test(self):
        def callback(ignore):
            print("Received callback!")
        res = client.createResolver(servers=[('127.0.0.1', 1025)], resolvconf='/dev/null', hosts='/dev/null')
        d = res.lookupAddress('foobar.com')
        d.addCallback(callback)

运行 此测试导致以下错误:

 [ERROR]
 Traceback (most recent call last):
 Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
 DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
 <DelayedCall 0x7f44c69042e8 [0.9992678165435791s] called=0 cancelled=0 
 DNSMixin._clearFailed(<Deferred at 0x7f44c6904358>, 28457)>
 <DelayedCall 0x7f44c68f3e10 [59.99872899055481s] called=0 cancelled=0 Resolver.maybeParseConfig()>

test.Tester.test_test

==================================================================
[ERROR]
Traceback (most recent call last):
Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was 
unclean.
Selectables:
<<class 'twisted.names.dns.DNSDatagramProtocol'> on 34529>

test.Tester.test_test
-------------------------------------------------------------------------------
Ran 1 tests in 0.003s

所以似乎反应器没有从解析器在 test_test 中发送的消息中清除。

我不明白为什么会这样。文件说试运行反应堆,我不应该碰它。我是不是用错了测试框架?

您可能不应该在您的测试套件中处理真实的网络流量。真实的网络是不稳定的,依赖它的测试套件往往容易出错且令人沮丧。你真的不希望你的测试 运行 失败只是因为 systemd-resolved 得到更新并开始对它注意到绕过你的系统的一些 DNS 流量做一些古怪的事情。

避免真实网络流量的主要策略是让接口的替代实现比您正在测试的接口低一级——一种根本不使用真实网络的实现。我的首选策略是使用简单的内存对象模拟网络行为。如果需要,您可以 运行 针对真实和内存中实现的较低级别的测试套件,并验证这两个实现至少在某些时候是 "the same"。

就是说,您的 tearDown 中存在一个简单的错误。它调用 stopListening,其中 return 是 Deferred,但 return 不是 Deferred 本身。因此,试验决定清理在 tearDown return 秒时完成,但可能尚未完成。 Return stopListening Deferred 并且您可以避免 一个 不干净的错误。

test_test 中存在类似的错误。它不会 return d 所以一旦方法 returns 试验决定测试结束(成功)。 Return d 并且当 d 触发时它将决定测试结束(并且只有在触发成功时才通过测试)。

我在使用 twisted 解析 DNS 请求时遇到了类似的问题。

我的问题是 Twisted 每 60 秒保持一次循环调用来检查 resolv.conf 文件。

这是在this line

中的maybeParseConfig方法中完成的
self._parseCall = self._reactor.callLater(self._resolvReadInterval, self.maybeParseConfig)

self._parseCall 保留使您的反应器不干净的延迟调用。

所以,尽管我同意@Jean 的回答,但我现在不想创建自己的假 DNS 解析器并想出了一个解决方法

首先你需要得到扭曲的解析器:

import twisted.names.client
resolver_chain = twisted.names.client.getResolver()

This returns a twisted.names.resolve.ResolverChain,在我的例子中,在 resolvers 属性中有一个包含 3 个解析器的列表。需要清理的是 twisted.names.client.Resolver 的那个,在我的例子中是第 3 个。

resolver = resolver_chain.resolvers[2]
resolver._parseCall.cancel()

你只需要在测试完成后取消调用,你的反应器就会干净。