From 8fe7f1e6666a5ddba633fdcc2a36a3f5361c6e8b Mon Sep 17 00:00:00 2001 From: Hugo Benichi Date: Wed, 1 Jun 2016 08:50:38 +0900 Subject: [PATCH] Adding tests for DnsEventListenerService Bug: 29035129 Change-Id: Iaf0d9ec781da7a473b6f7d8623060ecde44b9cbd --- .../DnsEventListenerServiceTest.java | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java diff --git a/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java new file mode 100644 index 0000000000..033b2c96c8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016, 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 com.android.server.connectivity; + +import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.metrics.DnsEvent; +import android.net.metrics.IDnsEventListener; +import android.net.metrics.IpConnectivityLog; + +import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.IntStream; + +public class DnsEventListenerServiceTest extends TestCase { + + // TODO: read from DnsEventListenerService after this constant is read from system property + static final int BATCH_SIZE = 100; + static final int EVENT_TYPE = IDnsEventListener.EVENT_GETADDRINFO; + // TODO: read from IDnsEventListener + static final int RETURN_CODE = 1; + + static final byte[] EVENT_TYPES = new byte[BATCH_SIZE]; + static final byte[] RETURN_CODES = new byte[BATCH_SIZE]; + static final int[] LATENCIES = new int[BATCH_SIZE]; + static { + for (int i = 0; i < BATCH_SIZE; i++) { + EVENT_TYPES[i] = EVENT_TYPE; + RETURN_CODES[i] = RETURN_CODE; + LATENCIES[i] = i; + } + } + + DnsEventListenerService mDnsService; + + @Mock ConnectivityManager mCm; + @Mock IpConnectivityLog mLog; + ArgumentCaptor mCallbackCaptor; + ArgumentCaptor mEvCaptor; + + public void setUp() { + MockitoAnnotations.initMocks(this); + mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); + mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); + mDnsService = new DnsEventListenerService(mCm, mLog); + + verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); + } + + public void testOneBatch() throws Exception { + log(105, LATENCIES); + log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event + + verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); + + log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); + + mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor + verifyLoggedEvents( + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + public void testSeveralBatches() throws Exception { + log(105, LATENCIES); + log(106, LATENCIES); + log(105, LATENCIES); + log(107, LATENCIES); + + verifyLoggedEvents( + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + public void testBatchAndNetworkLost() throws Exception { + byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); + byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); + int[] latencies = Arrays.copyOf(LATENCIES, 20); + + log(105, LATENCIES); + log(105, latencies); + mCallbackCaptor.getValue().onLost(new Network(105)); + log(105, LATENCIES); + + verifyLoggedEvents( + new DnsEvent(105, eventTypes, returnCodes, latencies), + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + public void testConcurrentBatchesAndDumps() throws Exception { + final long stop = System.currentTimeMillis() + 100; + final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); + new Thread() { + public void run() { + while (System.currentTimeMillis() < stop) { + mDnsService.dump(pw); + } + } + }.start(); + + logAsync(105, LATENCIES); + logAsync(106, LATENCIES); + logAsync(107, LATENCIES); + + verifyLoggedEvents(500, + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + public void testConcurrentBatchesAndNetworkLoss() throws Exception { + logAsync(105, LATENCIES); + Thread.sleep(10L); + // call onLost() asynchronously to logAsync's onDnsEvent() calls. + mCallbackCaptor.getValue().onLost(new Network(105)); + + // do not verify unpredictable batch + verify(mLog, timeout(500).times(1)).log(any()); + } + + void log(int netId, int[] latencies) { + for (int l : latencies) { + mDnsService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l); + } + } + + void logAsync(int netId, int[] latencies) { + new Thread() { + public void run() { + log(netId, latencies); + } + }.start(); + } + + void verifyLoggedEvents(DnsEvent... expected) { + verifyLoggedEvents(0, expected); + } + + void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) { + verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture()); + for (DnsEvent got : mEvCaptor.getAllValues()) { + OptionalInt index = IntStream.range(0, expectedEvents.length) + .filter(i -> eventsEqual(expectedEvents[i], got)) + .findFirst(); + // Don't match same expected event more than once. + index.ifPresent(i -> expectedEvents[i] = null); + assertTrue(index.isPresent()); + } + } + + /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ + static boolean eventsEqual(DnsEvent expected, DnsEvent got) { + return (expected == got) || ((expected != null) && (got != null) + && (expected.netId == got.netId) + && Arrays.equals(expected.eventTypes, got.eventTypes) + && Arrays.equals(expected.returnCodes, got.returnCodes) + && Arrays.equals(expected.latenciesMs, got.latenciesMs)); + } +}