Increase code coverage in DnsStats

To increase the code coverage in DnsStats, DnsStats unit test
should also cover DnsStats::dump().

Test: resolv_unit_test passed
Change-Id: I5eae976a2b6ce87cc2e5f66ae66facf3f2df4caa
diff --git a/DnsStatsTest.cpp b/DnsStatsTest.cpp
index 525b611..419e6db 100644
--- a/DnsStatsTest.cpp
+++ b/DnsStatsTest.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <array>
+
+#include <android-base/test_utils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -81,10 +84,60 @@
 
 class DnsStatsTest : public ::testing::Test {
   protected:
+    std::string captureDumpOutput() {
+        netdutils::DumpWriter dw(STDOUT_FILENO);
+        CapturedStdout captured;
+        mDnsStats.dump(dw);
+        return captured.str();
+    }
+
+    // Get the output string from dump() and check the content.
+    void verifyDumpOutput(const std::vector<StatsData>& tcpData,
+                          const std::vector<StatsData>& udpData,
+                          const std::vector<StatsData>& dotData) {
+        // A simple pattern to capture two matches:
+        //     server address (empty allowed) and its statistics.
+        const std::regex pattern(R"(\s{4,}([0-9a-fA-F:\.]*) ([<(].*[>)]))");
+        std::string dumpString = captureDumpOutput();
+
+        const auto check = [&](const std::vector<StatsData>& statsData, const std::string& protocol,
+                               std::string* dumpString) {
+            SCOPED_TRACE(protocol);
+            ASSERT_NE(dumpString->find(protocol), std::string::npos);
+            std::smatch sm;
+
+            // Expect to show something even if none of servers is set.
+            if (statsData.empty()) {
+                ASSERT_TRUE(std::regex_search(*dumpString, sm, pattern));
+                EXPECT_TRUE(sm[1].str().empty());
+                EXPECT_EQ(sm[2], "<no server>");
+                *dumpString = sm.suffix();
+                return;
+            }
+
+            for (const auto& stats : statsData) {
+                ASSERT_TRUE(std::regex_search(*dumpString, sm, pattern));
+                EXPECT_EQ(sm[1], stats.serverSockAddr.ip().toString());
+                EXPECT_FALSE(sm[2].str().empty());
+                *dumpString = sm.suffix();
+            }
+        };
+
+        check(udpData, "UDP", &dumpString);
+        check(dotData, "TLS", &dumpString);
+        check(tcpData, "TCP", &dumpString);
+
+        // Ensure the whole string has been checked.
+        EXPECT_EQ(dumpString, "\n");
+    }
+
     DnsStats mDnsStats;
 };
 
 TEST_F(DnsStatsTest, SetServers) {
+    // Check before any operation to mDnsStats.
+    verifyDumpOutput({}, {}, {});
+
     static const struct {
         std::vector<std::string> servers;
         std::vector<std::string> expectation;
@@ -139,6 +192,8 @@
         EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
         EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), UnorderedElementsAreArray(expectedStats));
     }
+
+    verifyDumpOutput({}, {}, {});
 }
 
 TEST_F(DnsStatsTest, SetServersDifferentPorts) {
@@ -157,6 +212,7 @@
     EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), IsEmpty());
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), IsEmpty());
     EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), IsEmpty());
+    verifyDumpOutput({}, {}, {});
 
     EXPECT_TRUE(mDnsStats.setServers(std::vector(servers.begin() + 2, servers.end()), PROTO_TCP));
     EXPECT_TRUE(mDnsStats.setServers(std::vector(servers.begin() + 2, servers.end()), PROTO_UDP));
@@ -171,6 +227,7 @@
     EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), UnorderedElementsAreArray(expectedStats));
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
     EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), UnorderedElementsAreArray(expectedStats));
+    verifyDumpOutput(expectedStats, expectedStats, expectedStats);
 }
 
 TEST_F(DnsStatsTest, AddStatsAndClear) {
@@ -203,6 +260,7 @@
     EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), UnorderedElementsAreArray(expectedStatsForTcp));
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStatsForUdp));
     EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), IsEmpty());
+    verifyDumpOutput(expectedStatsForTcp, expectedStatsForUdp, {});
 
     // Clear stats.
     EXPECT_TRUE(mDnsStats.setServers({}, PROTO_TCP));
@@ -211,6 +269,7 @@
     EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), IsEmpty());
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), IsEmpty());
     EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), IsEmpty());
+    verifyDumpOutput({}, {}, {});
 }
 
 TEST_F(DnsStatsTest, StatsRemainsInExistentServer) {
@@ -237,6 +296,7 @@
             makeStatsData(servers[1], 4, 520ms, {{NS_R_NO_ERROR, 2}, {NS_R_TIMEOUT, 2}}),
     };
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+    verifyDumpOutput({}, expectedStats, {});
 
     // Update the server list, the stats of 127.0.0.2 will remain.
     servers = {
@@ -251,6 +311,7 @@
             makeStatsData(servers[2], 0, 0ms, {}),
     };
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+    verifyDumpOutput({}, expectedStats, {});
 
     // Let's add a record to 127.0.0.2 again.
     EXPECT_TRUE(mDnsStats.addStats(servers[0], recordNoError));
@@ -260,10 +321,15 @@
             makeStatsData(servers[2], 0, 0ms, {}),
     };
     EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+    verifyDumpOutput({}, expectedStats, {});
 }
 
 TEST_F(DnsStatsTest, AddStatsRecords_100000) {
-    constexpr int num = 100000;
+    constexpr size_t operations = 100000;
+    constexpr size_t logSize = DnsStats::kLogSize;
+    constexpr size_t rcodeNum = 4;  // A value by which kLogSize is divisible.
+    ASSERT_EQ(logSize % rcodeNum, 0U);
+
     const std::vector<IPSockAddr> servers = {
             IPSockAddr::toIPSockAddr("127.0.0.1", 53),
             IPSockAddr::toIPSockAddr("127.0.0.2", 53),
@@ -271,22 +337,46 @@
             IPSockAddr::toIPSockAddr("127.0.0.4", 53),
     };
 
+    // To test unknown rcode in rcodeToName(), store the elements as type int.
+    const std::array<int, rcodeNum> rcodes = {
+            NS_R_NO_ERROR,        // NOERROR
+            NS_R_NXDOMAIN,        // NXDOMAIN
+            99,                   // UNKNOWN(99)
+            NS_R_INTERNAL_ERROR,  // INTERNAL_ERROR
+    };
+
     EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_TCP));
     EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_UDP));
     EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_DOT));
 
-    for (int i = 0; i < num; i++) {
-        const auto eventTcp = makeDnsQueryEvent(PROTO_TCP, static_cast<NsRcode>(i % 10), 10ms);
-        const auto eventUdp = makeDnsQueryEvent(PROTO_UDP, static_cast<NsRcode>(i % 10), 10ms);
-        const auto eventDot = makeDnsQueryEvent(PROTO_DOT, static_cast<NsRcode>(i % 10), 10ms);
+    for (size_t i = 0; i < operations; i++) {
+        const NsRcode rcode = static_cast<NsRcode>(rcodes[i % rcodeNum]);
+        const auto eventTcp = makeDnsQueryEvent(PROTO_TCP, rcode, 10ms);
+        const auto eventUdp = makeDnsQueryEvent(PROTO_UDP, rcode, 10ms);
+        const auto eventDot = makeDnsQueryEvent(PROTO_DOT, rcode, 10ms);
         for (const auto& server : servers) {
-            const std::string trace = server.toString() + "-" + std::to_string(i);
-            SCOPED_TRACE(trace);
-            EXPECT_TRUE(mDnsStats.addStats(server, eventTcp));
-            EXPECT_TRUE(mDnsStats.addStats(server, eventUdp));
-            EXPECT_TRUE(mDnsStats.addStats(server, eventDot));
+            SCOPED_TRACE(server.toString() + "-" + std::to_string(i));
+            ASSERT_TRUE(mDnsStats.addStats(server, eventTcp));
+            ASSERT_TRUE(mDnsStats.addStats(server, eventUdp));
+            ASSERT_TRUE(mDnsStats.addStats(server, eventDot));
         }
     }
+
+    std::map<int, int> expectedRcodeCounts;
+    for (const auto& rcode : rcodes) {
+        expectedRcodeCounts.try_emplace(rcode, 32);
+    }
+    const std::vector<StatsData> expectedStats = {
+            makeStatsData(servers[0], logSize, logSize * 10ms, expectedRcodeCounts),
+            makeStatsData(servers[1], logSize, logSize * 10ms, expectedRcodeCounts),
+            makeStatsData(servers[2], logSize, logSize * 10ms, expectedRcodeCounts),
+            makeStatsData(servers[3], logSize, logSize * 10ms, expectedRcodeCounts),
+    };
+
+    EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), UnorderedElementsAreArray(expectedStats));
+    EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+    EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), UnorderedElementsAreArray(expectedStats));
+    verifyDumpOutput(expectedStats, expectedStats, expectedStats);
 }
 
 }  // namespace android::net