Add unit tests for DnsQueryLog

Ensure the basic functions of DnsQueryLog work as expected.

Bug: 139040977
Test: cd packages/modules/DnsResolver && atest
Change-Id: I6b318beeed7ff5942e8d08474c354e48ebdac936
diff --git a/Android.bp b/Android.bp
index 50d5dab..fd868f7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -171,6 +171,7 @@
         "resolv_tls_unit_test.cpp",
         "resolv_unit_test.cpp",
         "DnsStatsTest.cpp",
+        "DnsQueryLogTest.cpp",
     ],
     shared_libs: [
         "libbase",
diff --git a/DnsQueryLog.cpp b/DnsQueryLog.cpp
index 82ed581..6f0e179 100644
--- a/DnsQueryLog.cpp
+++ b/DnsQueryLog.cpp
@@ -61,19 +61,19 @@
 void DnsQueryLog::push(Record&& record) {
     std::lock_guard guard(mLock);
     mQueue.push_back(std::move(record));
-    if (mQueue.size() > kLogSize) {
+    if (mQueue.size() > mCapacity) {
         mQueue.pop_front();
     }
 }
 
 void DnsQueryLog::dump(netdutils::DumpWriter& dw) const {
-    dw.println("DNS query log (last %ld minutes):", kValidityMinutes.count());
+    dw.println("DNS query log (last %lld minutes):", (mValidityTimeMs / 60000).count());
     netdutils::ScopedIndent indentStats(dw);
     const auto now = std::chrono::system_clock::now();
 
     std::lock_guard guard(mLock);
     for (const auto& record : mQueue) {
-        if (now - record.timestamp > kValidityMinutes) continue;
+        if (now - record.timestamp > mValidityTimeMs) continue;
 
         const std::string maskedHostname = maskHostname(record.hostname);
         const std::string maskedIpsStr = maskIps(record.addrs);
diff --git a/DnsQueryLog.h b/DnsQueryLog.h
index a319047..c19f8db 100644
--- a/DnsQueryLog.h
+++ b/DnsQueryLog.h
@@ -49,18 +49,25 @@
         const int timeTaken;
     };
 
+    // Allow the tests to set the capacity and the validaty time in milliseconds.
+    DnsQueryLog(size_t size = kDefaultLogSize,
+                std::chrono::milliseconds time = kDefaultValidityMinutes)
+        : mCapacity(size), mValidityTimeMs(time) {}
+
     void push(Record&& record) EXCLUDES(mLock);
     void dump(netdutils::DumpWriter& dw) const EXCLUDES(mLock);
 
   private:
     mutable std::mutex mLock;
     std::deque<Record> mQueue GUARDED_BY(mLock);
+    const size_t mCapacity;
+    const std::chrono::milliseconds mValidityTimeMs;
 
     // The capacity of the circular buffer.
-    static constexpr size_t kLogSize = 200;
+    static constexpr size_t kDefaultLogSize = 200;
 
-    // Limit to dump the queries within last |kValidityMinutes| minutes.
-    static constexpr std::chrono::minutes kValidityMinutes{60};
+    // Limit to dump the queries within last |kDefaultValidityMinutes| minutes.
+    static constexpr std::chrono::minutes kDefaultValidityMinutes{60};
 };
 
 }  // namespace android::net
diff --git a/DnsQueryLogTest.cpp b/DnsQueryLogTest.cpp
new file mode 100644
index 0000000..b652412
--- /dev/null
+++ b/DnsQueryLogTest.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <regex>
+#include <thread>
+
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "DnsQueryLog.h"
+
+using namespace std::chrono_literals;
+
+namespace android::net {
+
+namespace {
+
+// Dump the log to STDOUT and capture it.
+std::string captureDumpOutput(const DnsQueryLog& queryLog) {
+    netdutils::DumpWriter dw(STDOUT_FILENO);
+    CapturedStdout captured;
+    queryLog.dump(dw);
+    return captured.str();
+}
+
+// A simple check for the dump result by checking the netIds one by one.
+void verifyDumpOutput(const std::string& dumpLog, const std::vector<int>& expectedNetIds) {
+    // Capture three matches: netId, hostname, and answer (empty allowed).
+    static const std::regex pattern(
+            R"(netId=(\d+).* hostname=([\w\*]+) answer=\[([\w:,\.\*\s]*)\])");
+
+    std::string str(dumpLog);
+    std::smatch sm;
+    for (const auto& netId : expectedNetIds) {
+        SCOPED_TRACE(netId);
+        EXPECT_TRUE(std::regex_search(str, sm, pattern));
+        EXPECT_EQ(sm[1], std::to_string(netId));
+        str = sm.suffix();
+    }
+
+    // Ensure the dumpLog is exactly as expected.
+    EXPECT_FALSE(std::regex_search(str, sm, pattern));
+}
+
+}  // namespace
+
+class DnsQueryLogTest : public ::testing::Test {
+  protected:
+    const std::vector<std::string> serversV4 = {"127.0.0.1", "1.2.3.4"};
+    const std::vector<std::string> serversV4V6 = {"127.0.0.1", "1.2.3.4", "2001:db8::1",
+                                                  "fe80:1::2%testnet"};
+};
+
+TEST_F(DnsQueryLogTest, Push) {
+    std::vector<DnsQueryLog::Record> records = {
+            DnsQueryLog::Record(30, 1000, 1000, "example.com", serversV4, 10),
+            DnsQueryLog::Record(31, 1000, 1000, "", serversV4, 10),      // Empty hostname.
+            DnsQueryLog::Record(32, 1000, 1000, "example.com", {}, 10),  // No answer.
+            DnsQueryLog::Record(33, 1000, 1000, "example.com", serversV4V6, 10),
+    };
+    DnsQueryLog queryLog;
+    for (auto& r : records) {
+        queryLog.push(std::move(r));
+    }
+
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {30, 31, 32, 33});
+}
+
+TEST_F(DnsQueryLogTest, PushStressTest) {
+    const int threadNum = 100;
+    const int pushNum = 1000;
+    const size_t size = 500;
+    DnsQueryLog queryLog(size);
+    std::vector<std::thread> threads(threadNum);
+
+    // Launch 'threadNum' threads to push the same queryLog 'pushNum' times.
+    for (auto& thread : threads) {
+        thread = std::thread([&]() {
+            for (int i = 0; i < pushNum; i++) {
+                DnsQueryLog::Record record(30, 1000, 1000, "www.example.com", serversV4, 10);
+                queryLog.push(std::move(record));
+            }
+        });
+    }
+    for (auto& thread : threads) {
+        thread.join();
+    }
+
+    // Verify there are exact 'size' records in queryLog.
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, std::vector(size, 30));
+}
+
+TEST_F(DnsQueryLogTest, ZeroSize) {
+    const size_t size = 0;
+    DnsQueryLog::Record r1(30, 1000, 1000, "www.example1.com", serversV4V6, 10);
+    DnsQueryLog::Record r2(31, 1000, 1000, "www.example2.com", serversV4V6, 10);
+    DnsQueryLog::Record r3(32, 1000, 1000, "www.example3.com", serversV4V6, 10);
+
+    DnsQueryLog queryLog(size);
+    queryLog.push(std::move(r1));
+    queryLog.push(std::move(r2));
+    queryLog.push(std::move(r3));
+
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {});
+}
+
+TEST_F(DnsQueryLogTest, CapacityFull) {
+    const size_t size = 3;
+    DnsQueryLog::Record r1(30, 1000, 1000, "www.example1.com", serversV4V6, 10);
+    DnsQueryLog::Record r2(31, 1000, 1000, "www.example2.com", serversV4V6, 10);
+    DnsQueryLog::Record r3(32, 1000, 1000, "www.example3.com", serversV4V6, 10);
+    DnsQueryLog::Record r4(33, 1000, 1000, "www.example4.com", serversV4V6, 10);
+    const std::vector<int> expectedNetIds = {31, 32, 33};
+
+    DnsQueryLog queryLog(size);
+    queryLog.push(std::move(r1));
+    queryLog.push(std::move(r2));
+    queryLog.push(std::move(r3));
+    queryLog.push(std::move(r4));
+
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, expectedNetIds);
+}
+
+TEST_F(DnsQueryLogTest, ValidityTime) {
+    DnsQueryLog::Record r1(30, 1000, 1000, "www.example.com", serversV4, 10);
+    DnsQueryLog queryLog(3, 100ms);
+    queryLog.push(std::move(r1));
+
+    // Dump the output and verify the correctness by checking netId.
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {30});
+
+    std::this_thread::sleep_for(150ms);
+
+    // The record is expired thus not shown in the output.
+    output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {});
+
+    // Push another record to ensure it still works.
+    DnsQueryLog::Record r2(31, 1000, 1000, "example.com", serversV4V6, 10);
+    queryLog.push(std::move(r2));
+    output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {31});
+}
+
+}  // namespace android::net