• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.tradefed.device.metric;
17 
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.ArgumentMatchers.anyLong;
20 import static org.mockito.ArgumentMatchers.argThat;
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Mockito.doReturn;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.times;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 import static org.mockito.MockitoAnnotations.initMocks;
28 
29 import com.android.os.AtomsProto.AppCrashOccurred;
30 import com.android.os.AtomsProto.Atom;
31 import com.android.os.StatsLog.EventMetricData;
32 import com.android.os.StatsLog.StatsdStatsReport;
33 import com.android.tradefed.device.ITestDevice;
34 import com.android.tradefed.device.TestDeviceState;
35 import com.android.tradefed.invoker.IInvocationContext;
36 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
37 import com.android.tradefed.result.ITestInvocationListener;
38 
39 import org.junit.Assert;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 import org.mockito.ArgumentMatchers;
45 import org.mockito.Mock;
46 import org.mockito.Spy;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Date;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.concurrent.TimeUnit;
55 import java.util.stream.Collectors;
56 
57 /** Unit tests for {@link RuntimeRestartCollector}. */
58 @RunWith(JUnit4.class)
59 public class RuntimeRestartCollectorTest {
60     private static final String DEVICE_SERIAL_1 = "device_serial_1";
61     private static final String DEVICE_SERIAL_2 = "device_serial_2";
62 
63     private static final long CONFIG_ID_1 = 1;
64     private static final long CONFIG_ID_2 = 2;
65 
66     private static final int TIMESTAMP_1_SECS = 1554764010;
67     private static final String TIMESTAMP_1_STR =
68             RuntimeRestartCollector.TIME_FORMATTER.format(
69                     new Date(TimeUnit.SECONDS.toMillis(TIMESTAMP_1_SECS)));
70     private static final int TIMESTAMP_2_SECS = 1554764135;
71     private static final String TIMESTAMP_2_STR =
72             RuntimeRestartCollector.TIME_FORMATTER.format(
73                     new Date(TimeUnit.SECONDS.toMillis(TIMESTAMP_2_SECS)));
74 
75     private static final long UPTIME_1_NANOS =
76             TimeUnit.HOURS.toNanos(1) + TimeUnit.MINUTES.toNanos(2) + TimeUnit.SECONDS.toNanos(3);
77     private static final String UPTIME_1_STR = "01:02:03";
78     private static final long UPTIME_2_NANOS =
79             TimeUnit.HOURS.toNanos(1) + TimeUnit.MINUTES.toNanos(4) + TimeUnit.SECONDS.toNanos(8);
80     private static final String UPTIME_2_STR = "01:04:08";
81 
82     private static final EventMetricData RUNTIME_RESTART_DATA_1 =
83             EventMetricData.newBuilder()
84                     .setElapsedTimestampNanos(UPTIME_1_NANOS)
85                     .setAtom(
86                             Atom.newBuilder()
87                                     .setAppCrashOccurred(
88                                             AppCrashOccurred.newBuilder()
89                                                     .setProcessName(
90                                                             RuntimeRestartCollector
91                                                                     .SYSTEM_SERVER_KEYWORD)))
92                     .build();
93     private static final EventMetricData RUNTIME_RESTART_DATA_2 =
94             RUNTIME_RESTART_DATA_1.toBuilder().setElapsedTimestampNanos(UPTIME_2_NANOS).build();
95     private static final EventMetricData NOT_RUNTIME_RESTART_DATA =
96             EventMetricData.newBuilder()
97                     .setElapsedTimestampNanos(111)
98                     .setAtom(
99                             Atom.newBuilder()
100                                     .setAppCrashOccurred(
101                                             AppCrashOccurred.newBuilder()
102                                                     .setProcessName("not_system_server")))
103                     .build();
104 
105     @Spy private RuntimeRestartCollector mCollector;
106     @Mock private IInvocationContext mContext;
107     @Mock private ITestInvocationListener mListener;
108 
109     @Before
setUp()110     public void setUp() throws Exception {
111         initMocks(this);
112     }
113 
114     /**
115      * Test that the collector makes the correct calls to the statsd utilities for a single device.
116      *
117      * <p>During testRunStarted() it should push the config and pull the statsd metadata. During
118      * testRunEnded() it should pull the event metric data, remove the statsd config, and pull the
119      * statsd metadata again.
120      */
121     @Test
testStatsdInteractions_singleDevice()122     public void testStatsdInteractions_singleDevice() throws Exception {
123         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
124         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
125         doReturn(CONFIG_ID_1)
126                 .when(mCollector)
127                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
128         doReturn(new ArrayList<EventMetricData>())
129                 .when(mCollector)
130                 .getEventMetricData(any(ITestDevice.class), anyLong());
131         doReturn(StatsdStatsReport.newBuilder().build())
132                 .when(mCollector)
133                 .getStatsdMetadata(any(ITestDevice.class));
134 
135         mCollector.init(mContext, mListener);
136 
137         mCollector.testRunStarted("test run", 1);
138         verify(mCollector, times(1))
139                 .pushStatsConfig(
140                         eq(testDevice),
141                         argThat(l -> l.contains(Atom.APP_CRASH_OCCURRED_FIELD_NUMBER)));
142         verify(mCollector, times(1)).getStatsdMetadata(eq(testDevice));
143 
144         mCollector.testRunEnded(0, new HashMap<String, Metric>());
145         verify(mCollector, times(1)).getEventMetricData(eq(testDevice), eq(CONFIG_ID_1));
146         verify(mCollector, times(1)).removeConfig(eq(testDevice), eq(CONFIG_ID_1));
147         verify(mCollector, times(2)).getStatsdMetadata(eq(testDevice));
148     }
149 
150     /**
151      * Test that the collector makes the correct calls to the statsd utilities for multiple devices.
152      *
153      * <p>During testRunStarted() it should push the config and pull the statsd metadata for each
154      * device. During testRunEnded() it should pull the event metric data, remove the statsd config,
155      * and pull the statsd metadata again for each device.
156      */
157     @Test
testStatsdInteractions_multiDevice()158     public void testStatsdInteractions_multiDevice() throws Exception {
159         ITestDevice testDevice1 = mockTestDevice(DEVICE_SERIAL_1);
160         ITestDevice testDevice2 = mockTestDevice(DEVICE_SERIAL_2);
161         doReturn(Arrays.asList(testDevice1, testDevice2)).when(mContext).getDevices();
162         doReturn(CONFIG_ID_1)
163                 .when(mCollector)
164                 .pushStatsConfig(eq(testDevice1), ArgumentMatchers.<List<Integer>>any());
165         doReturn(CONFIG_ID_2)
166                 .when(mCollector)
167                 .pushStatsConfig(eq(testDevice2), ArgumentMatchers.<List<Integer>>any());
168         doReturn(new ArrayList<EventMetricData>())
169                 .when(mCollector)
170                 .getEventMetricData(any(ITestDevice.class), anyLong());
171         doReturn(StatsdStatsReport.newBuilder().build())
172                 .when(mCollector)
173                 .getStatsdMetadata(any(ITestDevice.class));
174 
175         mCollector.init(mContext, mListener);
176 
177         mCollector.testRunStarted("test run", 1);
178         verify(mCollector, times(1))
179                 .pushStatsConfig(
180                         eq(testDevice1),
181                         argThat(l -> l.contains(Atom.APP_CRASH_OCCURRED_FIELD_NUMBER)));
182         verify(mCollector, times(1))
183                 .pushStatsConfig(
184                         eq(testDevice2),
185                         argThat(l -> l.contains(Atom.APP_CRASH_OCCURRED_FIELD_NUMBER)));
186         verify(mCollector, times(1)).getStatsdMetadata(eq(testDevice1));
187         verify(mCollector, times(1)).getStatsdMetadata(eq(testDevice2));
188 
189         mCollector.testRunEnded(0, new HashMap<String, Metric>());
190         verify(mCollector, times(1)).getEventMetricData(eq(testDevice1), eq(CONFIG_ID_1));
191         verify(mCollector, times(1)).getEventMetricData(eq(testDevice2), eq(CONFIG_ID_2));
192         verify(mCollector, times(1)).removeConfig(eq(testDevice1), eq(CONFIG_ID_1));
193         verify(mCollector, times(1)).removeConfig(eq(testDevice2), eq(CONFIG_ID_2));
194         verify(mCollector, times(2)).getStatsdMetadata(eq(testDevice1));
195         verify(mCollector, times(2)).getStatsdMetadata(eq(testDevice2));
196     }
197 
198     /**
199      * Test that the collector collects a count of zero and no timestamps when no runtime restarts
200      * occur during the test run and there were no prior runtime restarts.
201      */
202     @Test
testAddingMetrics_noRuntimeRestart_noPriorRuntimeRestart()203     public void testAddingMetrics_noRuntimeRestart_noPriorRuntimeRestart() throws Exception {
204         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
205         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
206         doReturn(CONFIG_ID_1)
207                 .when(mCollector)
208                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
209         doReturn(new ArrayList<EventMetricData>())
210                 .when(mCollector)
211                 .getEventMetricData(any(ITestDevice.class), anyLong());
212         doReturn(StatsdStatsReport.newBuilder().build(), StatsdStatsReport.newBuilder().build())
213                 .when(mCollector)
214                 .getStatsdMetadata(any(ITestDevice.class));
215 
216         HashMap<String, Metric> runMetrics = new HashMap<>();
217         mCollector.init(mContext, mListener);
218         mCollector.testRunStarted("test run", 1);
219         mCollector.testRunEnded(0, runMetrics);
220 
221         // A count should always be present, and should be zero in this case.
222         // If a count is not present, .findFirst().get() throws and the test will fail.
223         int count = getCount(runMetrics);
224         Assert.assertEquals(0, count);
225         // No other metric keys should be present as there is no data.
226         ensureNoMetricWithKeySuffix(
227                 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS);
228         ensureNoMetricWithKeySuffix(
229                 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED);
230         ensureNoMetricWithKeySuffix(runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS);
231         ensureNoMetricWithKeySuffix(
232                 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED);
233     }
234 
235     /**
236      * Test that the collector collects a count of zero and no timestamps when no runtime restarts
237      * and there were prior runtime restarts.
238      */
239     @Test
testAddingMetrics_noRuntimeRestart_withPriorRuntimeRestart()240     public void testAddingMetrics_noRuntimeRestart_withPriorRuntimeRestart() throws Exception {
241         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
242         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
243         doReturn(CONFIG_ID_1)
244                 .when(mCollector)
245                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
246         doReturn(new ArrayList<EventMetricData>())
247                 .when(mCollector)
248                 .getEventMetricData(any(ITestDevice.class), anyLong());
249         doReturn(
250                         StatsdStatsReport.newBuilder()
251                                 .addAllSystemRestartSec(Arrays.asList(1, 2))
252                                 .build(),
253                         StatsdStatsReport.newBuilder()
254                                 .addAllSystemRestartSec(Arrays.asList(1, 2))
255                                 .build())
256                 .when(mCollector)
257                 .getStatsdMetadata(any(ITestDevice.class));
258 
259         HashMap<String, Metric> runMetrics = new HashMap<>();
260         mCollector.init(mContext, mListener);
261         mCollector.testRunStarted("test run", 1);
262         mCollector.testRunEnded(0, runMetrics);
263 
264         // A count should always be present, and should be zero in this case.
265         // If a count is not present, .findFirst().get() throws and the test will fail.
266         int count = getCount(runMetrics);
267         Assert.assertEquals(0, count);
268         // No other metric keys should be present as there is no data.
269         ensureNoMetricWithKeySuffix(
270                 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS);
271         ensureNoMetricWithKeySuffix(
272                 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED);
273         ensureNoMetricWithKeySuffix(runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS);
274         ensureNoMetricWithKeySuffix(
275                 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED);
276     }
277 
278     /**
279      * Test that the collector collects counts and timestamps correctly when there are runtime
280      * restarts and there were no prior runtime restarts.
281      */
282     @Test
testAddingMetrics_withRuntimeRestart_noPriorRuntimeRestart()283     public void testAddingMetrics_withRuntimeRestart_noPriorRuntimeRestart() throws Exception {
284         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
285         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
286         doReturn(CONFIG_ID_1)
287                 .when(mCollector)
288                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
289         doReturn(Arrays.asList(RUNTIME_RESTART_DATA_1, RUNTIME_RESTART_DATA_2))
290                 .when(mCollector)
291                 .getEventMetricData(any(ITestDevice.class), anyLong());
292         doReturn(
293                         StatsdStatsReport.newBuilder().build(),
294                         StatsdStatsReport.newBuilder()
295                                 .addAllSystemRestartSec(
296                                         Arrays.asList(TIMESTAMP_1_SECS, TIMESTAMP_2_SECS))
297                                 .build())
298                 .when(mCollector)
299                 .getStatsdMetadata(any(ITestDevice.class));
300 
301         HashMap<String, Metric> runMetrics = new HashMap<>();
302         mCollector.init(mContext, mListener);
303         mCollector.testRunStarted("test run", 1);
304         mCollector.testRunEnded(0, runMetrics);
305 
306         // Count should be two.
307         int count = getCount(runMetrics);
308         Assert.assertEquals(2, count);
309         // There should be two timestamps that match TIMESTAMP_1_SECS and TIMESTAMP_2_SECS
310         // respectively.
311         List<Integer> timestampSecs =
312                 getIntMetricValuesByKeySuffix(
313                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS);
314         Assert.assertEquals(Arrays.asList(TIMESTAMP_1_SECS, TIMESTAMP_2_SECS), timestampSecs);
315         // There should be two timestamp strings that match TIMESTAMP_1_STR and TIMESTAMP_2_STR
316         // respectively.
317         List<String> timestampStrs =
318                 getStringMetricValuesByKeySuffix(
319                         runMetrics,
320                         RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED);
321         Assert.assertEquals(Arrays.asList(TIMESTAMP_1_STR, TIMESTAMP_2_STR), timestampStrs);
322         // There should be two uptime timestsmps that match UPTIME_1_NANOS and UPTIME_2_NANOS
323         // respectively.
324         List<Long> uptimeNanos =
325                 getLongMetricValuesByKeySuffix(
326                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS);
327         Assert.assertEquals(Arrays.asList(UPTIME_1_NANOS, UPTIME_2_NANOS), uptimeNanos);
328         // There should be two uptime timestamp strings that match UPTIME_1_STR and UPTIME_2_STR
329         // respectively.
330         List<String> uptimeStrs =
331                 getStringMetricValuesByKeySuffix(
332                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED);
333         Assert.assertEquals(Arrays.asList(UPTIME_1_STR, UPTIME_2_STR), uptimeStrs);
334     }
335 
336     /**
337      * Test that the collector collects counts and metrics correctly when there are runtime restarts
338      * and there were prior runtime restarts.
339      */
340     @Test
testAddingMetrics_withRuntimeRestart_withPriorRuntimeRestart()341     public void testAddingMetrics_withRuntimeRestart_withPriorRuntimeRestart() throws Exception {
342         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
343         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
344         doReturn(CONFIG_ID_1)
345                 .when(mCollector)
346                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
347         doReturn(Arrays.asList(RUNTIME_RESTART_DATA_1, RUNTIME_RESTART_DATA_2))
348                 .when(mCollector)
349                 .getEventMetricData(any(ITestDevice.class), anyLong());
350         doReturn(
351                         StatsdStatsReport.newBuilder()
352                                 .addAllSystemRestartSec(Arrays.asList(1, 2, 3))
353                                 .build(),
354                         StatsdStatsReport.newBuilder()
355                                 .addAllSystemRestartSec(
356                                         Arrays.asList(2, 3, TIMESTAMP_1_SECS, TIMESTAMP_2_SECS))
357                                 .build())
358                 .when(mCollector)
359                 .getStatsdMetadata(any(ITestDevice.class));
360 
361         HashMap<String, Metric> runMetrics = new HashMap<>();
362         mCollector.init(mContext, mListener);
363         mCollector.testRunStarted("test run", 1);
364         mCollector.testRunEnded(0, runMetrics);
365 
366         // Count should be two.
367         int count = getCount(runMetrics);
368         Assert.assertEquals(2, count);
369         // There should be two timestamps that match TIMESTAMP_1_SECS and TIMESTAMP_2_SECS
370         // respectively.
371         List<Integer> timestampSecs =
372                 getIntMetricValuesByKeySuffix(
373                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS);
374         Assert.assertEquals(Arrays.asList(TIMESTAMP_1_SECS, TIMESTAMP_2_SECS), timestampSecs);
375         // There should be two timestamp strings that match TIMESTAMP_1_STR and TIMESTAMP_2_STR
376         // respectively.
377         List<String> timestampStrs =
378                 getStringMetricValuesByKeySuffix(
379                         runMetrics,
380                         RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED);
381         Assert.assertEquals(Arrays.asList(TIMESTAMP_1_STR, TIMESTAMP_2_STR), timestampStrs);
382         // There should be two uptime timestsmps that match UPTIME_1_NANOS and UPTIME_2_NANOS
383         // respectively.
384         List<Long> uptimeNanos =
385                 getLongMetricValuesByKeySuffix(
386                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS);
387         Assert.assertEquals(Arrays.asList(UPTIME_1_NANOS, UPTIME_2_NANOS), uptimeNanos);
388         // There should be two uptime timestamp strings that match UPTIME_1_STR and UPTIME_2_STR
389         // respectively.
390         List<String> uptimeStrs =
391                 getStringMetricValuesByKeySuffix(
392                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED);
393         Assert.assertEquals(Arrays.asList(UPTIME_1_STR, UPTIME_2_STR), uptimeStrs);
394     }
395 
396     /**
397      * Test that the {@link AppCrashOccurred}-based collection only collects runtime restarts (i.e.
398      * system server crashes) and not other crashes.
399      */
400     @Test
testAddingMetrics_withRuntimeRestart_reportsSystemServerCrashesOnly()401     public void testAddingMetrics_withRuntimeRestart_reportsSystemServerCrashesOnly()
402             throws Exception {
403         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
404         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
405         doReturn(CONFIG_ID_1)
406                 .when(mCollector)
407                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
408         doReturn(
409                         Arrays.asList(
410                                 RUNTIME_RESTART_DATA_1,
411                                 NOT_RUNTIME_RESTART_DATA,
412                                 RUNTIME_RESTART_DATA_2))
413                 .when(mCollector)
414                 .getEventMetricData(any(ITestDevice.class), anyLong());
415         doReturn(
416                         StatsdStatsReport.newBuilder()
417                                 .addAllSystemRestartSec(Arrays.asList(1, 2, 3))
418                                 .build(),
419                         StatsdStatsReport.newBuilder()
420                                 .addAllSystemRestartSec(
421                                         Arrays.asList(2, 3, TIMESTAMP_1_SECS, TIMESTAMP_2_SECS))
422                                 .build())
423                 .when(mCollector)
424                 .getStatsdMetadata(any(ITestDevice.class));
425 
426         HashMap<String, Metric> runMetrics = new HashMap<>();
427         mCollector.init(mContext, mListener);
428         mCollector.testRunStarted("test run", 1);
429         mCollector.testRunEnded(0, runMetrics);
430 
431         // We only check for the uptime timestamps coming from the AppCrashOccurred atoms.
432 
433         // There should be two uptime timestamps that match UPTIME_1_NANOS and UPTIME_2_NANOS
434         // respectively.
435         List<Long> uptimeNanos =
436                 getLongMetricValuesByKeySuffix(
437                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS);
438         Assert.assertEquals(Arrays.asList(UPTIME_1_NANOS, UPTIME_2_NANOS), uptimeNanos);
439         // There should be two uptime timestamp strings that match UPTIME_1_STR and UPTIME_2_STR
440         // respectively.
441         List<String> uptimeStrs =
442                 getStringMetricValuesByKeySuffix(
443                         runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED);
444         Assert.assertEquals(Arrays.asList(UPTIME_1_STR, UPTIME_2_STR), uptimeStrs);
445     }
446 
447     /**
448      * Test that the collector reports counts based on the {@link StatsdStatsReport} results when it
449      * disagrees with info from the {@link AppCrashOccurred} atom.
450      */
451     @Test
testAddingMetrics_withRuntimeRestart_useStatsdMetadataResultsForCount()452     public void testAddingMetrics_withRuntimeRestart_useStatsdMetadataResultsForCount()
453             throws Exception {
454         ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
455         doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
456         // Two data points from the AppCrashOccurred data.
457         doReturn(CONFIG_ID_1)
458                 .when(mCollector)
459                 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any());
460         doReturn(Arrays.asList(RUNTIME_RESTART_DATA_1, RUNTIME_RESTART_DATA_2))
461                 .when(mCollector)
462                 .getEventMetricData(any(ITestDevice.class), anyLong());
463         // Data from statsd metadata only has one timestamp.
464         doReturn(
465                         StatsdStatsReport.newBuilder()
466                                 .addAllSystemRestartSec(Arrays.asList())
467                                 .build(),
468                         StatsdStatsReport.newBuilder()
469                                 .addAllSystemRestartSec(Arrays.asList(TIMESTAMP_1_SECS))
470                                 .build())
471                 .when(mCollector)
472                 .getStatsdMetadata(any(ITestDevice.class));
473 
474         HashMap<String, Metric> runMetrics = new HashMap<>();
475         mCollector.init(mContext, mListener);
476         mCollector.testRunStarted("test run", 1);
477         mCollector.testRunEnded(0, runMetrics);
478 
479         // Count should be two as in the stubbed EventMetricDataResults, even though statsd metadata
480         // only reported one timestamp.
481         int count = getCount(runMetrics);
482         Assert.assertEquals(1, count);
483     }
484 
485     /**
486      * Test that the device serial number is included in the metric key when multiple devices are
487      * under test.
488      */
489     @Test
testAddingMetrics_includesSerialForMultipleDevices()490     public void testAddingMetrics_includesSerialForMultipleDevices() throws Exception {
491         ITestDevice testDevice1 = mockTestDevice(DEVICE_SERIAL_1);
492         ITestDevice testDevice2 = mockTestDevice(DEVICE_SERIAL_2);
493         doReturn(Arrays.asList(testDevice1, testDevice2)).when(mContext).getDevices();
494         doReturn(CONFIG_ID_1)
495                 .when(mCollector)
496                 .pushStatsConfig(eq(testDevice1), ArgumentMatchers.<List<Integer>>any());
497         doReturn(CONFIG_ID_2)
498                 .when(mCollector)
499                 .pushStatsConfig(eq(testDevice2), ArgumentMatchers.<List<Integer>>any());
500         doReturn(new ArrayList<EventMetricData>())
501                 .when(mCollector)
502                 .getEventMetricData(any(ITestDevice.class), anyLong());
503         doReturn(StatsdStatsReport.newBuilder().build())
504                 .when(mCollector)
505                 .getStatsdMetadata(any(ITestDevice.class));
506 
507         HashMap<String, Metric> runMetrics = new HashMap<>();
508         mCollector.init(mContext, mListener);
509         mCollector.testRunStarted("test run", 1);
510         mCollector.testRunEnded(0, runMetrics);
511 
512         // Check that the count metrics contain the serial numbers for the two devices,
513         // respectively.
514         List<String> countMetricKeys =
515                 runMetrics
516                         .keySet()
517                         .stream()
518                         .filter(
519                                 key ->
520                                         hasPrefixAndSuffix(
521                                                 key,
522                                                 RuntimeRestartCollector.METRIC_PREFIX,
523                                                 RuntimeRestartCollector.METRIC_SUFFIX_COUNT))
524                         .collect(Collectors.toList());
525         // One key for each device.
526         Assert.assertEquals(2, countMetricKeys.size());
527         Assert.assertTrue(
528                 countMetricKeys
529                         .stream()
530                         .anyMatch(
531                                 key ->
532                                         (key.contains(DEVICE_SERIAL_1)
533                                                 && !key.contains(DEVICE_SERIAL_2))));
534         Assert.assertTrue(
535                 countMetricKeys
536                         .stream()
537                         .anyMatch(
538                                 key ->
539                                         (key.contains(DEVICE_SERIAL_2)
540                                                 && !key.contains(DEVICE_SERIAL_1))));
541     }
542 
543     /** Helper method to get count from metrics. */
getCount(Map<String, Metric> metrics)544     private static int getCount(Map<String, Metric> metrics) {
545         return Integer.parseInt(
546                 metrics.entrySet()
547                         .stream()
548                         .filter(
549                                 entry ->
550                                         hasPrefixAndSuffix(
551                                                 entry.getKey(),
552                                                 RuntimeRestartCollector.METRIC_PREFIX,
553                                                 RuntimeRestartCollector.METRIC_SUFFIX_COUNT))
554                         .map(entry -> entry.getValue())
555                         .findFirst()
556                         .get()
557                         .getMeasurements()
558                         .getSingleString());
559     }
560 
561     /** Helper method to check that no metric key with the given suffix is in the metrics. */
ensureNoMetricWithKeySuffix(Map<String, Metric> metrics, String suffix)562     private static void ensureNoMetricWithKeySuffix(Map<String, Metric> metrics, String suffix) {
563         Assert.assertTrue(
564                 metrics.keySet()
565                         .stream()
566                         .noneMatch(
567                                 key ->
568                                         hasPrefixAndSuffix(
569                                                 key,
570                                                 RuntimeRestartCollector.METRIC_PREFIX,
571                                                 suffix)));
572     }
573 
574     /**
575      * Helper method to find a comma-separated String metric value by its key suffix, and return its
576      * values as a list.
577      */
getStringMetricValuesByKeySuffix( Map<String, Metric> metrics, String suffix)578     private static List<String> getStringMetricValuesByKeySuffix(
579             Map<String, Metric> metrics, String suffix) {
580         return metrics.entrySet()
581                 .stream()
582                 .filter(
583                         entry ->
584                                 hasPrefixAndSuffix(
585                                         entry.getKey(),
586                                         RuntimeRestartCollector.METRIC_PREFIX,
587                                         suffix))
588                 .map(entry -> entry.getValue().getMeasurements().getSingleString().split(","))
589                 .flatMap(arr -> Arrays.stream(arr))
590                 .collect(Collectors.toList());
591     }
592 
593     /**
594      * Helper method to find a comma-separated int metric value by its key suffix, and return its
595      * values as a list.
596      */
getIntMetricValuesByKeySuffix( Map<String, Metric> metrics, String suffix)597     private static List<Integer> getIntMetricValuesByKeySuffix(
598             Map<String, Metric> metrics, String suffix) {
599         return getStringMetricValuesByKeySuffix(metrics, suffix)
600                 .stream()
601                 .map(val -> Integer.valueOf(val))
602                 .collect(Collectors.toList());
603     }
604 
605     /**
606      * Helper method to find a comma-separated long metric value by its key suffix, and return its
607      * values as a list.
608      */
getLongMetricValuesByKeySuffix( Map<String, Metric> metrics, String suffix)609     private static List<Long> getLongMetricValuesByKeySuffix(
610             Map<String, Metric> metrics, String suffix) {
611         return getStringMetricValuesByKeySuffix(metrics, suffix)
612                 .stream()
613                 .map(val -> Long.valueOf(val))
614                 .collect(Collectors.toList());
615     }
616 
hasPrefixAndSuffix(String target, String prefix, String suffix)617     private static boolean hasPrefixAndSuffix(String target, String prefix, String suffix) {
618         return target.startsWith(prefix) && target.endsWith(suffix);
619     }
620 
mockTestDevice(String serial)621     private ITestDevice mockTestDevice(String serial) {
622         ITestDevice device = mock(ITestDevice.class);
623         when(device.getSerialNumber()).thenReturn(serial);
624         when(device.getDeviceState()).thenReturn(TestDeviceState.ONLINE);
625         return device;
626     }
627 }
628