Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f81e4538 authored by Chiachang Wang's avatar Chiachang Wang Committed by Gerrit Code Review
Browse files

Merge "Add DnsUtilsTest"

parents b237ae99 a5115368
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

package android.networkstack.util

import android.net.DnsResolver
import android.net.DnsResolver.FLAG_EMPTY
import android.net.DnsResolver.TYPE_A
import android.net.DnsResolver.TYPE_AAAA
import android.net.Network
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.networkstack.util.DnsUtils
import com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG
import com.android.server.connectivity.NetworkMonitor.DnsLogFunc
import java.net.InetAddress
import java.net.UnknownHostException
import kotlin.test.assertFailsWith
import org.junit.Assert.assertArrayEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations

const val DEFAULT_TIMEOUT_MS = 1000
const val SHORT_TIMEOUT_MS = 200

@RunWith(AndroidJUnit4::class)
@SmallTest
class DnsUtilsTest {
    val fakeNetwork: Network = Network(1234)
    @Mock
    lateinit var mockLogger: DnsLogFunc
    @Mock
    lateinit var mockResolver: DnsResolver
    lateinit var fakeDns: FakeDns

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        fakeDns = FakeDns(mockResolver)
        fakeDns.startMocking()
    }

    private fun assertIpAddressArrayEquals(expect: Array<String>, actual: Array<InetAddress>) =
            assertArrayEquals("Array of IP addresses differs", expect,
                    actual.map { it.getHostAddress() }.toTypedArray())

    @Test
    fun testGetAllByNameWithTypeSuccess() {
        // Test different query types.
        verifyGetAllByName("www.google.com", arrayOf("2001:db8::1"), TYPE_AAAA)
        verifyGetAllByName("www.google.com", arrayOf("192.168.0.1"), TYPE_A)
        verifyGetAllByName("www.android.com", arrayOf("192.168.0.2", "2001:db8::2"),
                TYPE_ADDRCONFIG)
    }

    private fun verifyGetAllByName(name: String, expected: Array<String>, type: Int) {
        fakeDns.setAnswer(name, expected, type)
        DnsUtils.getAllByName(mockResolver, fakeNetwork, name, type, FLAG_EMPTY, DEFAULT_TIMEOUT_MS,
                mockLogger).let { assertIpAddressArrayEquals(expected, it) }
    }

    @Test
    fun testGetAllByNameWithTypeNoResult() {
        verifyGetAllByNameFails("www.android.com", TYPE_A)
        verifyGetAllByNameFails("www.android.com", TYPE_AAAA)
        verifyGetAllByNameFails("www.android.com", TYPE_ADDRCONFIG)
    }

    private fun verifyGetAllByNameFails(name: String, type: Int) {
        assertFailsWith<UnknownHostException> {
            DnsUtils.getAllByName(mockResolver, fakeNetwork, name, type,
                    FLAG_EMPTY, SHORT_TIMEOUT_MS, mockLogger)
        }
    }
    // TODO: Add more tests. Verify timeout, logger and error.
}
 No newline at end of file
+94 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

package android.networkstack.util

import android.net.DnsResolver
import android.net.InetAddresses
import android.os.Looper
import android.os.Handler
import com.android.internal.annotations.GuardedBy
import com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG
import java.net.InetAddress
import java.util.concurrent.Executor
import org.mockito.invocation.InvocationOnMock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doAnswer

// TODO: Integrate with NetworkMonitorTest.
class FakeDns(val mockResolver: DnsResolver) {
    class DnsEntry(val hostname: String, val type: Int, val addresses: List<InetAddress>) {
        fun match(host: String, type: Int) = hostname.equals(host) && type == type
    }

    @GuardedBy("answers")
    val answers = ArrayList<DnsEntry>()

    fun getAnswer(hostname: String, type: Int): DnsEntry? = synchronized(answers) {
        return answers.firstOrNull { it.match(hostname, type) }
    }

    fun setAnswer(hostname: String, answer: Array<String>, type: Int) = synchronized(answers) {
        val ans = DnsEntry(hostname, type, generateAnswer(answer))
        // Replace or remove the existing one.
        when (val index = answers.indexOfFirst { it.match(hostname, type) }) {
            -1 -> answers.add(ans)
            else -> answers[index] = ans
        }
    }

    private fun generateAnswer(answer: Array<String>) =
            answer.filterNotNull().map { InetAddresses.parseNumericAddress(it) }

    fun startMocking() {
        // Mock DnsResolver.query() w/o type
        doAnswer {
            mockAnswer(it, 1, -1, 3, 5)
        }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* flags */,
                any() /* executor */, any() /* cancellationSignal */, any() /*callback*/)
        // Mock DnsResolver.query() w/ type
        doAnswer {
            mockAnswer(it, 1, 2, 4, 6)
        }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* nsType */,
                anyInt() /* flags */, any() /* executor */, any() /* cancellationSignal */,
        any() /*callback*/)
    }

    private fun mockAnswer(
        it: InvocationOnMock,
        posHos: Int,
        posType: Int,
        posExecutor: Int,
        posCallback: Int
    ) {
        val hostname = it.arguments[posHos] as String
        val executor = it.arguments[posExecutor] as Executor
        val callback = it.arguments[posCallback] as DnsResolver.Callback<List<InetAddress>>
        var type = if (posType != -1) it.arguments[posType] as Int else TYPE_ADDRCONFIG
        val answer = getAnswer(hostname, type)

        if (!answer?.addresses.isNullOrEmpty()) {
            Handler(Looper.getMainLooper()).post({ executor.execute({
                    callback.onAnswer(answer?.addresses, 0); }) })
        }
    }

    /** Clears all entries. */
    fun clearAll() = synchronized(answers) {
        answers.clear()
    }
}
 No newline at end of file