// Copyright (C) 2012 Jeremy Lainé // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX # include #else # include # include #endif using namespace Qt::StringLiterals; static const int Timeout = 15000; // 15s class tst_QDnsLookup: public QObject { Q_OBJECT const QString normalDomain = u".test.qt-project.org"_s; const QString idnDomain = u".alqualondë.test.qt-project.org"_s; bool usingIdnDomain = false; bool dnsServersMustWork = false; QString domainName(const QString &input); QString domainNameList(const QString &input); QStringList domainNameListAlternatives(const QString &input); std::unique_ptr lookupCommon(QDnsLookup::Type type, const QString &domain, const QHostAddress &server = {}, quint16 port = 53); QStringList formatReply(const QDnsLookup *lookup) const; public slots: void initTestCase(); private slots: void lookupLocalhost(); void lookupRoot(); void lookupNxDomain_data(); void lookupNxDomain(); void lookup_data(); void lookup(); void lookupIdn_data() { lookup_data(); } void lookupIdn(); void lookupReuse(); void lookupAbortRetry(); void setNameserverLoopback(); void setNameserver_data(); void setNameserver(); void bindingsAndProperties(); void automatedBindings(); }; static constexpr qsizetype HeaderSize = 6 * sizeof(quint16); static const char preparedDnsQuery[] = // header "\x00\x00" // transaction ID, we'll replace "\x01\x20" // flags "\x00\x01" // qdcount "\x00\x00" // ancount "\x00\x00" // nscount "\x00\x00" // arcount // query: "\x00\x00\x06\x00\x01" // IN SOA ; static QList systemNameservers() { QList result; #ifdef Q_OS_WIN ULONG infosize = 0; DWORD r = GetNetworkParams(nullptr, &infosize); auto buffer = std::make_unique(infosize); auto info = new (buffer.get()) FIXED_INFO; r = GetNetworkParams(info, &infosize); if (r == NO_ERROR) { for (PIP_ADDR_STRING ptr = &info->DnsServerList; ptr; ptr = ptr->Next) { QLatin1StringView addr(ptr->IpAddress.String); result.emplaceBack(addr); } } #else auto parseFile = [&](QLatin1StringView path) { QFile f(path); if (!f.open(QIODevice::ReadOnly)) return; while (!f.atEnd()) { static const char command[] = "nameserver"; QByteArray line = f.readLine().simplified(); if (!line.startsWith(command)) continue; QString addr = QLatin1StringView(line).mid(sizeof(command)); result.emplaceBack(addr); } }; parseFile("/etc/resolv.conf"_L1); parseFile("/run/systemd/resolve/resolv.conf"_L1); #endif return result; } static QList globalPublicNameservers() { const char *const candidates[] = { // Google's dns.google "8.8.8.8", "2001:4860:4860::8888", //"8.8.4.4", "2001:4860:4860::8844", // CloudFare's one.one.one.one "1.1.1.1", "2606:4700:4700::1111", //"1.0.0.1", "2606:4700:4700::1001", // Quad9's dns9 //"9.9.9.9", "2620:fe::9", }; QList result; QRandomGenerator &rng = *QRandomGenerator::system(); for (auto name : candidates) { // check the candidates for reachability QHostAddress addr{QLatin1StringView(name)}; quint16 id = quint16(rng()); QByteArray data(preparedDnsQuery, sizeof(preparedDnsQuery)); char *ptr = data.data(); qToBigEndian(id, ptr); QUdpSocket socket; socket.connectToHost(addr, 53); if (socket.waitForConnected(1)) socket.write(data); if (!socket.waitForReadyRead(1000)) { qDebug() << addr << "discarded:" << socket.errorString(); continue; } QNetworkDatagram dgram = socket.receiveDatagram(); if (!dgram.isValid()) { qDebug() << addr << "discarded:" << socket.errorString(); continue; } data = dgram.data(); ptr = data.data(); if (data.size() < HeaderSize) { qDebug() << addr << "discarded: reply too small"; continue; } bool ok = qFromBigEndian(ptr) == id && (ptr[2] & 0x80) // is a reply && (ptr[3] & 0xf) == 0 // rcode NOERROR && qFromBigEndian(ptr + 4) == 1 // qdcount && qFromBigEndian(ptr + 6) >= 1; // ancount if (!ok) { qDebug() << addr << "discarded: invalid reply"; continue; } result.emplaceBack(std::move(addr)); } return result; } void tst_QDnsLookup::initTestCase() { if (qgetenv("QTEST_ENVIRONMENT") == "ci") dnsServersMustWork = true; } QString tst_QDnsLookup::domainName(const QString &input) { if (input.isEmpty()) return input; if (input.endsWith(QLatin1Char('.'))) { QString nodot = input; nodot.chop(1); return nodot; } if (usingIdnDomain) return input + idnDomain; return input + normalDomain; } QString tst_QDnsLookup::domainNameList(const QString &input) { const QStringList list = input.split(QLatin1Char(';')); QString result; for (const QString &s : list) { if (!result.isEmpty()) result += ';'; result += domainName(s); } return result; } QStringList tst_QDnsLookup::domainNameListAlternatives(const QString &input) { QStringList alternatives = input.split('|'); for (int i = 0; i < alternatives.size(); ++i) alternatives[i] = domainNameList(alternatives[i]); return alternatives; } std::unique_ptr tst_QDnsLookup::lookupCommon(QDnsLookup::Type type, const QString &domain, const QHostAddress &server, quint16 port) { auto lookup = std::make_unique(type, domainName(domain), server, port); QObject::connect(lookup.get(), &QDnsLookup::finished, &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); lookup->lookup(); QTestEventLoop::instance().enterLoopMSecs(Timeout); QDnsLookup::Error error = lookup->error(); if (QTestEventLoop::instance().timeout()) error = QDnsLookup::TimeoutError; if (!dnsServersMustWork && (error == QDnsLookup::ServerFailureError || error == QDnsLookup::ServerRefusedError || error == QDnsLookup::TimeoutError)) { // It's not a QDnsLookup problem if the server refuses to answer the query. // This happens for queries of type ANY through Dnsmasq, for example. [&] { auto me = QMetaEnum::fromType(); QString msg = u"Server refused or was unable to answer query; %1 type %3: %2"_s .arg(domain, lookup->errorString(), QString(me.valueToKey(int(type)))); QSKIP(msg.toLocal8Bit()); }(); return {}; } return lookup; } QStringList tst_QDnsLookup::formatReply(const QDnsLookup *lookup) const { QStringList result; QString domain = lookup->name(); auto shorter = [this](QString value) { const QString &ending = usingIdnDomain ? idnDomain : normalDomain; if (value.endsWith(ending)) value.chop(ending.size()); else value += u'.'; return value; }; for (const QDnsMailExchangeRecord &rr : lookup->mailExchangeRecords()) { QString entry = u"MX %1 %2"_s.arg(rr.preference(), 5).arg(shorter(rr.exchange())); if (rr.name() != domain) entry = "MX unexpected label to "_L1 + rr.name(); result.append(std::move(entry)); } for (const QDnsServiceRecord &rr : lookup->serviceRecords()) { QString entry = u"SRV %1 %2 %3 %4"_s.arg(rr.priority(), 5).arg(rr.weight()) .arg(rr.port()).arg(shorter(rr.target())); if (rr.name() != domain) entry = "SRV unexpected label to "_L1 + rr.name(); result.append(std::move(entry)); } auto addNameRecords = [&](QLatin1StringView rrtype, const QList &rrset) { for (const QDnsDomainNameRecord &rr : rrset) { QString entry = u"%1 %2"_s.arg(rrtype, shorter(rr.value())); if (rr.name() != domain) entry = rrtype + " unexpected label to "_L1 + rr.name(); result.append(std::move(entry)); } }; addNameRecords("NS"_L1, lookup->nameServerRecords()); addNameRecords("PTR"_L1, lookup->pointerRecords()); addNameRecords("CNAME"_L1, lookup->canonicalNameRecords()); for (const QDnsHostAddressRecord &rr : lookup->hostAddressRecords()) { if (rr.name() != domain) continue; // A and AAAA may appear as extra records in the answer section QHostAddress addr = rr.value(); result.append(u"%1 %2"_s .arg(addr.protocol() == QHostAddress::IPv6Protocol ? "AAAA" : "A", addr.toString())); } for (const QDnsTextRecord &rr : lookup->textRecords()) { QString entry = "TXT"_L1; for (const QByteArray &data : rr.values()) { entry += u' '; entry += QDebug::toString(data); } result.append(std::move(entry)); } result.sort(); return result; } void tst_QDnsLookup::lookupLocalhost() { auto lookup = lookupCommon(QDnsLookup::Type::A, u"localhost."_s); QVERIFY(lookup); QCOMPARE(lookup->error(), QDnsLookup::NoError); QList hosts = lookup->hostAddressRecords(); QCOMPARE(hosts.size(), 1); QCOMPARE(hosts.at(0).value(), QHostAddress::LocalHost); QVERIFY2(hosts.at(0).name().startsWith(lookup->name()), qPrintable(hosts.at(0).name())); } void tst_QDnsLookup::lookupRoot() { #ifdef Q_OS_WIN QSKIP("This test fails on Windows as it seems to treat the lookup as a local one."); #else auto lookup = lookupCommon(QDnsLookup::Type::NS, u""_s); if (!lookup) return; QCOMPARE(lookup->error(), QDnsLookup::NoError); const QList servers = lookup->nameServerRecords(); QVERIFY(!servers.isEmpty()); for (const QDnsDomainNameRecord &ns : servers) { QCOMPARE(ns.name(), QString()); QVERIFY(ns.value().endsWith(".root-servers.net")); } #endif } void tst_QDnsLookup::lookupNxDomain_data() { QTest::addColumn("type"); QTest::addColumn("domain"); QTest::newRow("a") << QDnsLookup::A << "invalid.invalid"; QTest::newRow("aaaa") << QDnsLookup::AAAA << "invalid.invalid"; QTest::newRow("any") << QDnsLookup::ANY << "invalid.invalid"; QTest::newRow("mx") << QDnsLookup::MX << "invalid.invalid"; QTest::newRow("ns") << QDnsLookup::NS << "invalid.invalid"; QTest::newRow("ptr") << QDnsLookup::PTR << "invalid.invalid"; QTest::newRow("srv") << QDnsLookup::SRV << "invalid.invalid"; QTest::newRow("txt") << QDnsLookup::TXT << "invalid.invalid"; } void tst_QDnsLookup::lookupNxDomain() { QFETCH(QDnsLookup::Type, type); QFETCH(QString, domain); auto lookup = lookupCommon(type, domain); if (!lookup) return; QCOMPARE(lookup->name(), domainName(domain)); QCOMPARE(lookup->type(), type); QCOMPARE(lookup->error(), QDnsLookup::NotFoundError); } void tst_QDnsLookup::lookup_data() { QTest::addColumn("type"); QTest::addColumn("domain"); QTest::addColumn("expected"); QTest::newRow("a-single") << QDnsLookup::A << "a-single" << "A 192.0.2.1"; QTest::newRow("a-multi") << QDnsLookup::A << "a-multi" << "A 192.0.2.1;A 192.0.2.2;A 192.0.2.3"; QTest::newRow("aaaa-single") << QDnsLookup::AAAA << "aaaa-single" << "AAAA 2001:db8::1"; QTest::newRow("aaaa-multi") << QDnsLookup::AAAA << "aaaa-multi" << "AAAA 2001:db8::1;AAAA 2001:db8::2;AAAA 2001:db8::3"; QTest::newRow("any-a-single") << QDnsLookup::ANY << "a-single" << "A 192.0.2.1"; QTest::newRow("any-a-plus-aaaa") << QDnsLookup::ANY << "a-plus-aaaa" << "A 198.51.100.1;AAAA 2001:db8::1:1"; QTest::newRow("any-multi") << QDnsLookup::ANY << "multi" << "A 198.51.100.1;A 198.51.100.2;A 198.51.100.3;" "AAAA 2001:db8::1:1;AAAA 2001:db8::1:2" ; QTest::newRow("mx-single") << QDnsLookup::MX << "mx-single" << "MX 10 multi"; QTest::newRow("mx-single-cname") << QDnsLookup::MX << "mx-single-cname" << "MX 10 cname"; QTest::newRow("mx-multi") << QDnsLookup::MX << "mx-multi" << "MX 10 multi;MX 20 a-single"; QTest::newRow("mx-multi-sameprio") << QDnsLookup::MX << "mx-multi-sameprio" << "MX 10 a-single;MX 10 multi"; QTest::newRow("ns-single") << QDnsLookup::NS << "ns-single" << "NS ns11.cloudns.net."; QTest::newRow("ns-multi") << QDnsLookup::NS << "ns-multi" << "NS ns11.cloudns.net.;NS ns12.cloudns.net."; #if 0 // temporarily disabled since the new hosting provider can't insert // PTR records outside of the in-addr.arpa zone QTest::newRow("ptr-single") << QDnsLookup::PTR << "ptr-single" << "PTR a-single"; #endif QTest::newRow("ptr-1.1.1.1") << QDnsLookup::PTR << "1.1.1.1.in-addr.arpa." << "PTR one.one.one.one."; QTest::newRow("ptr-8.8.8.8") << QDnsLookup::PTR << "8.8.8.8.in-addr.arpa." << "PTR dns.google."; QTest::newRow("ptr-2001:4860:4860::8888") << QDnsLookup::PTR << "8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa." << "PTR dns.google."; QTest::newRow("ptr-2606:4700:4700::1111") << QDnsLookup::PTR << "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa." << "PTR one.one.one.one."; QTest::newRow("srv-single") << QDnsLookup::SRV << "_echo._tcp.srv-single" << "SRV 5 0 7 multi"; QTest::newRow("srv-prio") << QDnsLookup::SRV << "_echo._tcp.srv-prio" << "SRV 1 0 7 multi;SRV 2 0 7 a-plus-aaaa"; QTest::newRow("srv-weighted") << QDnsLookup::SRV << "_echo._tcp.srv-weighted" << "SRV 5 25 7 a-plus-aaaa;SRV 5 75 7 multi"; QTest::newRow("srv-multi") << QDnsLookup::SRV << "_echo._tcp.srv-multi" << "SRV 1 50 7 multi;" "SRV 2 50 7 a-single;" "SRV 2 50 7 aaaa-single;" "SRV 3 50 7 a-multi"; QTest::newRow("txt-single") << QDnsLookup::TXT << "txt-single" << "TXT \"Hello\""; QTest::newRow("txt-multi-onerr") << QDnsLookup::TXT << "txt-multi-onerr" << "TXT \"Hello\" \"World\""; QTest::newRow("txt-multi-multirr") << QDnsLookup::TXT << "txt-multi-multirr" << "TXT \"Hello\";TXT \"World\""; } void tst_QDnsLookup::lookup() { QFETCH(QDnsLookup::Type, type); QFETCH(QString, domain); QFETCH(QString, expected); std::unique_ptr lookup = lookupCommon(type, domain); if (!lookup) return; QCOMPARE(lookup->error(), QDnsLookup::NoError); QCOMPARE(lookup->errorString(), QString()); QCOMPARE(lookup->type(), type); QCOMPARE(lookup->name(), domainName(domain)); QString result = formatReply(lookup.get()).join(u';'); QCOMPARE(result, expected); // confirm that MX and SRV records are properly sorted const QList mx = lookup->mailExchangeRecords(); for (qsizetype i = 1; i < mx.size(); ++i) QCOMPARE_GE(mx[i].preference(), mx[i - 1].preference()); const QList srv = lookup->serviceRecords(); for (qsizetype i = 1; i < srv.size(); ++i) QCOMPARE_GE(srv[i].priority(), srv[i - 1].priority()); } void tst_QDnsLookup::lookupIdn() { usingIdnDomain = true; lookup(); usingIdnDomain = false; } void tst_QDnsLookup::lookupReuse() { QDnsLookup lookup; // first lookup lookup.setType(QDnsLookup::A); lookup.setName(domainName("a-single")); lookup.lookup(); QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); QVERIFY(!lookup.hostAddressRecords().isEmpty()); QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single")); QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("192.0.2.1")); // second lookup lookup.setType(QDnsLookup::AAAA); lookup.setName(domainName("aaaa-single")); lookup.lookup(); QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); QVERIFY(!lookup.hostAddressRecords().isEmpty()); QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single")); QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1")); } void tst_QDnsLookup::lookupAbortRetry() { QDnsLookup lookup; // try and abort the lookup lookup.setType(QDnsLookup::A); lookup.setName(domainName("a-single")); lookup.lookup(); lookup.abort(); QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); QCOMPARE(int(lookup.error()), int(QDnsLookup::OperationCancelledError)); QVERIFY(lookup.hostAddressRecords().isEmpty()); // retry a different lookup lookup.setType(QDnsLookup::AAAA); lookup.setName(domainName("aaaa-single")); lookup.lookup(); QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); QVERIFY(!lookup.hostAddressRecords().isEmpty()); QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single")); QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1")); } void tst_QDnsLookup::setNameserverLoopback() { #ifdef Q_OS_WIN // Windows doesn't like sending DNS requests to ports other than 53, so // let's try it first. constexpr quint16 DesiredPort = 53; #else // Trying to bind to port 53 will fail on Unix systems unless this test is // run as root, so we try mDNS's port (to help decoding in a packet capture). constexpr quint16 DesiredPort = 5353; // mDNS #endif // random loopback address so multiple copies of this test can run QHostAddress desiredAddress(0x7f000000 | QRandomGenerator::system()->bounded(0xffffff)); QUdpSocket server; if (!server.bind(desiredAddress, DesiredPort)) { // port in use, try a random one server.bind(QHostAddress::LocalHost, 0); } QCOMPARE(server.state(), QUdpSocket::BoundState); QDnsLookup lookup(QDnsLookup::Type::A, u"somelabel.somedomain"_s); QSignalSpy spy(&lookup, SIGNAL(finished())); lookup.setNameserver(server.localAddress(), server.localPort()); // QDnsLookup is threaded, so we can answer on the main thread QObject::connect(&server, &QUdpSocket::readyRead, &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); QObject::connect(&lookup, &QDnsLookup::finished, &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); lookup.lookup(); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY2(spy.isEmpty(), qPrintable(lookup.errorString())); QNetworkDatagram dgram = server.receiveDatagram(); QByteArray data = dgram.data(); QCOMPARE_GT(data.size(), HeaderSize); quint8 opcode = (quint8(data.at(2)) >> 3) & 0xF; QCOMPARE(opcode, 0); // standard query // send an NXDOMAIN reply to release the lookup thread QByteArray reply = data; reply[2] = 0x80U; // header->qr = true; reply[3] = 3; // header->rcode = NXDOMAIN; server.writeDatagram(reply.constData(), reply.size(), dgram.senderAddress(), dgram.senderPort()); server.close(); // now check that the QDnsLookup finished QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(spy.size(), 1); QCOMPARE(lookup.error(), QDnsLookup::NotFoundError); } void tst_QDnsLookup::setNameserver_data() { static QList servers = systemNameservers() + globalPublicNameservers(); QTest::addColumn("server"); if (servers.isEmpty()) { QSKIP("No reachable DNS servers were found"); } else { for (const QHostAddress &h : std::as_const(servers)) QTest::addRow("%s", qUtf8Printable(h.toString())) << h; } } void tst_QDnsLookup::setNameserver() { QFETCH(QHostAddress, server); std::unique_ptr lookup = lookupCommon(QDnsLookup::Type::A, "a-single", server); if (!lookup) return; QCOMPARE(lookup->error(), QDnsLookup::NoError); QString result = formatReply(lookup.get()).join(';'); QCOMPARE(result, "A 192.0.2.1"); } void tst_QDnsLookup::bindingsAndProperties() { QDnsLookup lookup; lookup.setType(QDnsLookup::A); QProperty dnsTypeProp; lookup.bindableType().setBinding(Qt::makePropertyBinding(dnsTypeProp)); const QSignalSpy typeChangeSpy(&lookup, &QDnsLookup::typeChanged); dnsTypeProp = QDnsLookup::AAAA; QCOMPARE(typeChangeSpy.size(), 1); QCOMPARE(lookup.type(), QDnsLookup::AAAA); dnsTypeProp.setBinding(lookup.bindableType().makeBinding()); lookup.setType(QDnsLookup::A); QCOMPARE(dnsTypeProp.value(), QDnsLookup::A); QProperty nameProp; lookup.bindableName().setBinding(Qt::makePropertyBinding(nameProp)); const QSignalSpy nameChangeSpy(&lookup, &QDnsLookup::nameChanged); nameProp = QStringLiteral("a-plus-aaaa"); QCOMPARE(nameChangeSpy.size(), 1); QCOMPARE(lookup.name(), QStringLiteral("a-plus-aaaa")); nameProp.setBinding(lookup.bindableName().makeBinding()); lookup.setName(QStringLiteral("a-single")); QCOMPARE(nameProp.value(), QStringLiteral("a-single")); QProperty nameserverProp; lookup.bindableNameserver().setBinding(Qt::makePropertyBinding(nameserverProp)); const QSignalSpy nameserverChangeSpy(&lookup, &QDnsLookup::nameserverChanged); const QSignalSpy nameserverPortChangeSpy(&lookup, &QDnsLookup::nameserverPortChanged); nameserverProp = QHostAddress::LocalHost; QCOMPARE(nameserverChangeSpy.size(), 1); QCOMPARE(nameserverPortChangeSpy.size(), 0); QCOMPARE(lookup.nameserver(), QHostAddress::LocalHost); nameserverProp.setBinding(lookup.bindableNameserver().makeBinding()); lookup.setNameserver(QHostAddress::Any); QCOMPARE(nameserverProp.value(), QHostAddress::Any); QCOMPARE(nameserverChangeSpy.size(), 2); QCOMPARE(nameserverPortChangeSpy.size(), 0); lookup.setNameserver(QHostAddress::LocalHostIPv6, 10053); QCOMPARE(nameserverProp.value(), QHostAddress::LocalHostIPv6); QCOMPARE(nameserverChangeSpy.size(), 3); QCOMPARE(nameserverPortChangeSpy.size(), 1); } void tst_QDnsLookup::automatedBindings() { QDnsLookup lookup; QTestPrivate::testReadWritePropertyBasics(lookup, u"aaaa"_s, u"txt"_s, "name"); if (QTest::currentTestFailed()) { qDebug("Failed property test for QDnsLookup::name"); return; } QTestPrivate::testReadWritePropertyBasics(lookup, QDnsLookup::AAAA, QDnsLookup::TXT, "type"); if (QTest::currentTestFailed()) { qDebug("Failed property test for QDnsLookup::type"); return; } QTestPrivate::testReadWritePropertyBasics(lookup, QHostAddress{QHostAddress::Any}, QHostAddress{QHostAddress::LocalHost}, "nameserver"); if (QTest::currentTestFailed()) { qDebug("Failed property test for QDnsLookup::nameserver"); return; } QTestPrivate::testReadWritePropertyBasics(lookup, quint16(123), quint16(456), "nameserverPort"); if (QTest::currentTestFailed()) { qDebug("Failed property test for QDnsLookup::nameserverPort"); return; } } QTEST_MAIN(tst_QDnsLookup) #include "tst_qdnslookup.moc"