1/* 2 * Copyright (C) 2016, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.connectivity; 18 19import android.net.ConnectivityManager.NetworkCallback; 20import android.net.ConnectivityManager; 21import android.net.Network; 22import android.net.metrics.DnsEvent; 23import android.net.metrics.IDnsEventListener; 24import android.net.metrics.IpConnectivityLog; 25 26import junit.framework.TestCase; 27import org.junit.Before; 28import org.junit.Test; 29import static org.junit.Assert.assertArrayEquals; 30import static org.junit.Assert.assertTrue; 31 32import org.mockito.ArgumentCaptor; 33import org.mockito.Mock; 34import org.mockito.Mockito; 35import org.mockito.MockitoAnnotations; 36import static org.mockito.Mockito.any; 37import static org.mockito.Mockito.anyInt; 38import static org.mockito.Mockito.eq; 39import static org.mockito.Mockito.timeout; 40import static org.mockito.Mockito.times; 41import static org.mockito.Mockito.verify; 42 43import java.io.FileOutputStream; 44import java.io.PrintWriter; 45import java.util.Arrays; 46import java.util.List; 47import java.util.OptionalInt; 48import java.util.stream.IntStream; 49 50public class DnsEventListenerServiceTest extends TestCase { 51 52 // TODO: read from DnsEventListenerService after this constant is read from system property 53 static final int BATCH_SIZE = 100; 54 static final int EVENT_TYPE = IDnsEventListener.EVENT_GETADDRINFO; 55 // TODO: read from IDnsEventListener 56 static final int RETURN_CODE = 1; 57 58 static final byte[] EVENT_TYPES = new byte[BATCH_SIZE]; 59 static final byte[] RETURN_CODES = new byte[BATCH_SIZE]; 60 static final int[] LATENCIES = new int[BATCH_SIZE]; 61 static { 62 for (int i = 0; i < BATCH_SIZE; i++) { 63 EVENT_TYPES[i] = EVENT_TYPE; 64 RETURN_CODES[i] = RETURN_CODE; 65 LATENCIES[i] = i; 66 } 67 } 68 69 DnsEventListenerService mDnsService; 70 71 @Mock ConnectivityManager mCm; 72 @Mock IpConnectivityLog mLog; 73 ArgumentCaptor<NetworkCallback> mCallbackCaptor; 74 ArgumentCaptor<DnsEvent> mEvCaptor; 75 76 public void setUp() { 77 MockitoAnnotations.initMocks(this); 78 mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); 79 mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); 80 mDnsService = new DnsEventListenerService(mCm, mLog); 81 82 verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); 83 } 84 85 public void testOneBatch() throws Exception { 86 log(105, LATENCIES); 87 log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event 88 89 verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); 90 91 log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); 92 93 mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor 94 verifyLoggedEvents( 95 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 96 new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); 97 } 98 99 public void testSeveralBatches() throws Exception { 100 log(105, LATENCIES); 101 log(106, LATENCIES); 102 log(105, LATENCIES); 103 log(107, LATENCIES); 104 105 verifyLoggedEvents( 106 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 107 new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), 108 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 109 new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); 110 } 111 112 public void testBatchAndNetworkLost() throws Exception { 113 byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); 114 byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); 115 int[] latencies = Arrays.copyOf(LATENCIES, 20); 116 117 log(105, LATENCIES); 118 log(105, latencies); 119 mCallbackCaptor.getValue().onLost(new Network(105)); 120 log(105, LATENCIES); 121 122 verifyLoggedEvents( 123 new DnsEvent(105, eventTypes, returnCodes, latencies), 124 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 125 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); 126 } 127 128 public void testConcurrentBatchesAndDumps() throws Exception { 129 final long stop = System.currentTimeMillis() + 100; 130 final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); 131 new Thread() { 132 public void run() { 133 while (System.currentTimeMillis() < stop) { 134 mDnsService.dump(pw); 135 } 136 } 137 }.start(); 138 139 logAsync(105, LATENCIES); 140 logAsync(106, LATENCIES); 141 logAsync(107, LATENCIES); 142 143 verifyLoggedEvents(500, 144 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 145 new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), 146 new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); 147 } 148 149 public void testConcurrentBatchesAndNetworkLoss() throws Exception { 150 logAsync(105, LATENCIES); 151 Thread.sleep(10L); 152 // call onLost() asynchronously to logAsync's onDnsEvent() calls. 153 mCallbackCaptor.getValue().onLost(new Network(105)); 154 155 // do not verify unpredictable batch 156 verify(mLog, timeout(500).times(1)).log(any()); 157 } 158 159 void log(int netId, int[] latencies) { 160 for (int l : latencies) { 161 mDnsService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l); 162 } 163 } 164 165 void logAsync(int netId, int[] latencies) { 166 new Thread() { 167 public void run() { 168 log(netId, latencies); 169 } 170 }.start(); 171 } 172 173 void verifyLoggedEvents(DnsEvent... expected) { 174 verifyLoggedEvents(0, expected); 175 } 176 177 void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) { 178 verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture()); 179 for (DnsEvent got : mEvCaptor.getAllValues()) { 180 OptionalInt index = IntStream.range(0, expectedEvents.length) 181 .filter(i -> eventsEqual(expectedEvents[i], got)) 182 .findFirst(); 183 // Don't match same expected event more than once. 184 index.ifPresent(i -> expectedEvents[i] = null); 185 assertTrue(index.isPresent()); 186 } 187 } 188 189 /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ 190 static boolean eventsEqual(DnsEvent expected, DnsEvent got) { 191 return (expected == got) || ((expected != null) && (got != null) 192 && (expected.netId == got.netId) 193 && Arrays.equals(expected.eventTypes, got.eventTypes) 194 && Arrays.equals(expected.returnCodes, got.returnCodes) 195 && Arrays.equals(expected.latenciesMs, got.latenciesMs)); 196 } 197} 198