1 /* 2 * Copyright (C) 2019 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 package android.device.collectors; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertTrue; 21 import static org.mockito.Mockito.atLeast; 22 import static org.mockito.Mockito.doReturn; 23 import static org.mockito.Mockito.eq; 24 import static org.mockito.Mockito.verify; 25 import static org.mockito.Mockito.when; 26 27 import android.app.Instrumentation; 28 import android.device.collectors.util.SendToInstrumentation; 29 import android.os.Bundle; 30 import android.os.Environment; 31 import androidx.test.InstrumentationRegistry; 32 import androidx.test.runner.AndroidJUnit4; 33 34 import com.android.helpers.ICollectorHelper; 35 36 import java.io.File; 37 import java.nio.charset.Charset; 38 import java.nio.file.Files; 39 import java.nio.file.Path; 40 import java.nio.file.Paths; 41 import java.util.Arrays; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.regex.Matcher; 46 import java.util.regex.Pattern; 47 48 import org.junit.After; 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.Description; 52 import org.junit.runner.Result; 53 import org.junit.runner.RunWith; 54 import org.mockito.ArgumentCaptor; 55 import org.mockito.Mock; 56 import org.mockito.MockitoAnnotations; 57 58 /** Android Unit tests for {@link ScheduledRunCollectionListener}. */ 59 @RunWith(AndroidJUnit4.class) 60 public class ScheduledRunCollectionListenerTest { 61 62 private static final String TEST_METRIC_KEY = "test_metric_key"; 63 private static final Integer[] TEST_METRIC_VALUES = {0, 1, 2, 3, 4}; 64 private static final long TEST_INTERVAL = 100L; 65 private static final long TEST_DURATION = 450L; 66 // Collects at 0ms, 100ms, 200ms, and so on. 67 private static final long NUMBER_OF_COLLECTIONS = TEST_DURATION / TEST_INTERVAL + 1; 68 private static final String DATA_REGEX = 69 "(?<timestamp>[0-9]+)\\s+," + TEST_METRIC_KEY + "\\s+,(?<value>[0-9])\\s+"; 70 71 @Mock private ICollectorHelper mHelper; 72 73 @Mock private Instrumentation mInstrumentation; 74 75 private ScheduledRunCollectionListener mListener; 76 initListener()77 private ScheduledRunCollectionListener initListener() { 78 Bundle b = new Bundle(); 79 b.putString(ScheduledRunCollectionListener.INTERVAL_ARG_KEY, Long.toString(TEST_INTERVAL)); 80 doReturn(true).when(mHelper).startCollecting(); 81 Map<String, Integer> first = new HashMap<>(); 82 first.put(TEST_METRIC_KEY, TEST_METRIC_VALUES[0]); 83 Map<String, Integer>[] rest = 84 Arrays.stream(TEST_METRIC_VALUES) 85 .skip(1) 86 .map( 87 testMetricValue -> { 88 Map<String, Integer> m = new HashMap<>(); 89 m.put(TEST_METRIC_KEY, testMetricValue); 90 return m; 91 }) 92 .toArray(Map[]::new); 93 // <code>thenReturn</code> call signature requires thenReturn(T value, T... values). 94 when(mHelper.getMetrics()).thenReturn(first, rest); 95 doReturn(true).when(mHelper).stopCollecting(); 96 ScheduledRunCollectionListener listener = 97 new ScheduledRunCollectionListener<Integer>(b, mHelper); 98 // Mock getUiAutomation method for the purpose of enabling createAndEmptyDirectory method 99 // from BaseMetricListener. 100 doReturn(InstrumentationRegistry.getInstrumentation().getUiAutomation()) 101 .when(mInstrumentation) 102 .getUiAutomation(); 103 listener.setInstrumentation(mInstrumentation); 104 return listener; 105 } 106 107 @Before setUp()108 public void setUp() { 109 MockitoAnnotations.initMocks(this); 110 mListener = initListener(); 111 } 112 113 @After tearDown()114 public void tearDown() { 115 // Remove files/directories that have been created. 116 Path outputFilePath = 117 Paths.get( 118 Environment.getExternalStorageDirectory().toString(), 119 ScheduledRunCollectionListener.OUTPUT_ROOT, 120 ScheduledRunCollectionListener.class.getSimpleName()); 121 mListener.executeCommandBlocking("rm -rf " + outputFilePath.toString()); 122 } 123 124 @Test testCompleteRun()125 public void testCompleteRun() throws Exception { 126 testRun(true); 127 } 128 129 @Test testIncompleteRun()130 public void testIncompleteRun() throws Exception { 131 testRun(false); 132 } 133 134 @Test testInstrumentationResult()135 public void testInstrumentationResult() throws Exception { 136 Description runDescription = Description.createSuiteDescription("run"); 137 mListener.testRunStarted(runDescription); 138 139 Thread.sleep(TEST_DURATION); 140 mListener.testRunFinished(new Result()); 141 // AJUR runner is then gonna call instrumentationRunFinished. 142 Bundle result = new Bundle(); 143 mListener.instrumentationRunFinished(System.out, result, new Result()); 144 int expectedMin = Arrays.stream(TEST_METRIC_VALUES).min(Integer::compare).get(); 145 assertEquals( 146 expectedMin, 147 Integer.parseInt( 148 result.getString( 149 TEST_METRIC_KEY + ScheduledRunCollectionListener.MIN_SUFFIX))); 150 int expectedMax = Arrays.stream(TEST_METRIC_VALUES).max(Integer::compare).get(); 151 assertEquals( 152 expectedMax, 153 Integer.parseInt( 154 result.getString( 155 TEST_METRIC_KEY + ScheduledRunCollectionListener.MAX_SUFFIX))); 156 double expectedMean = 157 Arrays.stream(TEST_METRIC_VALUES).mapToDouble(i -> i.doubleValue()).sum() 158 / NUMBER_OF_COLLECTIONS; 159 assertEquals( 160 expectedMean, 161 Double.parseDouble( 162 result.getString( 163 TEST_METRIC_KEY + ScheduledRunCollectionListener.MEAN_SUFFIX)), 164 0.1); 165 } 166 testRun(boolean isComplete)167 private void testRun(boolean isComplete) throws Exception { 168 Description runDescription = Description.createSuiteDescription("run"); 169 mListener.testRunStarted(runDescription); 170 171 Thread.sleep(TEST_DURATION); 172 // If incomplete run happens, for example, when a system crash happens half way through the 173 // run, <code>testRunFinished</code> method will be skipped, but the output file should 174 // still be present, and the time-series up to the point when the crash happens should still 175 // be recorded. 176 if (isComplete) { 177 mListener.testRunFinished(new Result()); 178 } 179 180 ArgumentCaptor<Bundle> bundle = ArgumentCaptor.forClass(Bundle.class); 181 182 // Verify that the path of the time-series output file has been sent to instrumentation. 183 verify(mInstrumentation, atLeast(1)) 184 .sendStatus(eq(SendToInstrumentation.INST_STATUS_IN_PROGRESS), bundle.capture()); 185 Bundle pathBundle = bundle.getAllValues().get(0); 186 String pathKey = 187 String.format( 188 ScheduledRunCollectionListener.OUTPUT_FILE_PATH, 189 ScheduledRunCollectionListener.class.getSimpleName()); 190 String path = pathBundle.getString(pathKey); 191 assertNotNull(path); 192 193 // Check the output file exists. 194 File outputFile = new File(path); 195 assertTrue(outputFile.exists()); 196 197 // Check that output file contains some of the periodic run results, sample results are 198 // like: 199 // 200 // time ,metric_key ,value 201 // 2 ,test_metric_key ,0 202 // 102 ,test_metric_key ,0 203 // 203 ,test_metric_key ,0 204 // ... 205 List<String> lines = Files.readAllLines(outputFile.toPath(), Charset.defaultCharset()); 206 assertEquals(NUMBER_OF_COLLECTIONS, lines.size() - 1); 207 assertEquals(lines.get(0), ScheduledRunCollectionListener.TIME_SERIES_HEADER); 208 for (int i = 1; i != lines.size(); ++i) { 209 Pattern p = Pattern.compile(DATA_REGEX); 210 Matcher m = p.matcher(lines.get(i)); 211 assertTrue(m.matches()); 212 long timestamp = Long.parseLong(m.group("timestamp")); 213 long delta = TEST_INTERVAL / 2; 214 assertEquals((i - 1) * TEST_INTERVAL, timestamp, delta); 215 Integer value = Integer.valueOf(m.group("value")); 216 assertEquals(TEST_METRIC_VALUES[i - 1], value); 217 } 218 219 // For incomplete run, invoke testRunFinished in the end to prevent resource leak. 220 if (!isComplete) { 221 mListener.testRunFinished(new Result()); 222 } 223 } 224 } 225