1 /* 2 * Copyright (C) 2011 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 17 package com.android.media.tests; 18 19 import com.android.ddmlib.IDevice; 20 import com.android.ddmlib.Log; 21 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 22 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 23 import com.android.tradefed.config.Option; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.result.CollectingTestListener; 27 import com.android.tradefed.result.FileInputStreamSource; 28 import com.android.tradefed.result.ITestInvocationListener; 29 import com.android.tradefed.result.InputStreamSource; 30 import com.android.tradefed.result.LogDataType; 31 import com.android.tradefed.testtype.IDeviceTest; 32 import com.android.tradefed.testtype.IRemoteTest; 33 import com.android.tradefed.util.FileUtil; 34 import com.android.tradefed.util.RegexTrie; 35 import com.android.tradefed.util.StreamUtil; 36 37 import junit.framework.TestCase; 38 39 import org.junit.Assert; 40 41 import java.io.ByteArrayInputStream; 42 import java.io.File; 43 import java.io.FileInputStream; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.ListIterator; 51 import java.util.Map; 52 import java.util.Set; 53 import java.util.concurrent.TimeUnit; 54 55 /** 56 * Runs the Camera stress testcases. 57 * FIXME: more details 58 * <p/> 59 * Note that this test will not run properly unless /sdcard is mounted and writable. 60 */ 61 public class CameraStressTest implements IDeviceTest, IRemoteTest { 62 private static final String LOG_TAG = "CameraStressTest"; 63 64 ITestDevice mTestDevice = null; 65 66 // Constants for running the tests 67 private static final String TEST_PACKAGE_NAME = "com.google.android.camera.tests"; 68 private static final String TEST_RUNNER = "com.android.camera.stress.CameraStressTestRunner"; 69 70 //Max test timeout - 3 hrs 71 private static final int MAX_TEST_TIMEOUT = 3 * 60 * 60 * 1000; 72 73 private final String mOutputPath = "mediaStressOut.txt"; 74 75 /** 76 * Stores the test cases that we should consider running. 77 * 78 * <p>This currently consists of "startup" and "latency" 79 */ 80 private List<TestInfo> mTestCases = new ArrayList<>(); 81 82 // Options for the running the gCam test 83 @Option(name = "gCam", description = "Run gCam back image capture test") 84 private boolean mGcam = false; 85 86 /** 87 * A struct that contains useful info about the tests to run 88 */ 89 static class TestInfo { 90 public String mTestName = null; 91 public String mClassName = null; 92 public String mTestMetricsName = null; 93 public Map<String, String> mInstrumentationArgs = new HashMap<>(); 94 public RegexTrie<String> mPatternMap = new RegexTrie<>(); 95 96 @Override 97 public String toString() { 98 return String.format("TestInfo: name(%s) class(%s) metric(%s) patterns(%s)", mTestName, 99 mClassName, mTestMetricsName, mPatternMap); 100 } 101 } 102 103 /** 104 * Set up the pattern map for parsing output files 105 * <p/> 106 * Exposed for unit meta-testing 107 */ 108 static RegexTrie<String> getPatternMap() { 109 RegexTrie<String> patMap = new RegexTrie<>(); 110 patMap.put("SwitchPreview", "^Camera Switch Mode:"); 111 112 // For versions of the on-device test that don't differentiate between front and back camera 113 patMap.put("ImageCapture", "^Camera Image Capture"); 114 patMap.put("VideoRecording", "^Camera Video Capture"); 115 116 // For versions that do differentiate 117 patMap.put("FrontImageCapture", "^Front Camera Image Capture"); 118 patMap.put("ImageCapture", "^Back Camera Image Capture"); 119 patMap.put("FrontVideoRecording", "^Front Camera Video Capture"); 120 patMap.put("VideoRecording", "^Back Camera Video Capture"); 121 122 // Actual metrics to collect for a given key 123 patMap.put("loopCount", "^No of loops :(\\d+)"); 124 patMap.put("iters", "^loop:.+,(\\d+)"); 125 126 return patMap; 127 } 128 129 /** 130 * Set up the configurations for the test cases we want to run 131 */ 132 private void testInfoSetup() { 133 RegexTrie<String> patMap = getPatternMap(); 134 TestInfo t = new TestInfo(); 135 136 if (mGcam) { 137 // Back Image capture stress test for gCam 138 t.mTestName = "testBackImageCapture"; 139 t.mClassName = "com.android.camera.stress.ImageCapture"; 140 t.mTestMetricsName = "GCamApplicationStress"; 141 t.mInstrumentationArgs.put("image_iterations", Integer.toString(100)); 142 t.mPatternMap = patMap; 143 mTestCases.add(t); 144 145 } else { 146 // Image capture stress test 147 t.mTestName = "imagecap"; 148 t.mClassName = "com.android.camera.stress.ImageCapture"; 149 t.mTestMetricsName = "CameraApplicationStress"; 150 t.mInstrumentationArgs.put("image_iterations", Integer.toString(100)); 151 t.mPatternMap = patMap; 152 mTestCases.add(t); 153 154 // Image capture stress test 155 t = new TestInfo(); 156 t.mTestName = "videocap"; 157 t.mClassName = "com.android.camera.stress.VideoCapture"; 158 t.mTestMetricsName = "CameraApplicationStress"; 159 t.mInstrumentationArgs.put("video_iterations", Integer.toString(100)); 160 t.mPatternMap = patMap; 161 mTestCases.add(t); 162 163 // "SwitchPreview" stress test 164 t = new TestInfo(); 165 t.mTestName = "switch"; 166 t.mClassName = "com.android.camera.stress.SwitchPreview"; 167 t.mTestMetricsName = "CameraApplicationStress"; 168 t.mPatternMap = patMap; 169 mTestCases.add(t); 170 } 171 } 172 173 @Override 174 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 175 Assert.assertNotNull(mTestDevice); 176 testInfoSetup(); 177 for (TestInfo test : mTestCases) { 178 cleanTmpFiles(); 179 executeTest(test, listener); 180 logOutputFiles(test, listener); 181 } 182 183 cleanTmpFiles(); 184 } 185 186 private void executeTest(TestInfo test, ITestInvocationListener listener) 187 throws DeviceNotAvailableException { 188 IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(TEST_PACKAGE_NAME, 189 TEST_RUNNER, mTestDevice.getIDevice()); 190 CollectingTestListener auxListener = new CollectingTestListener(); 191 192 runner.setClassName(test.mClassName); 193 runner.setMaxTimeToOutputResponse(MAX_TEST_TIMEOUT, TimeUnit.MILLISECONDS); 194 if (mGcam){ 195 runner.setMethodName(test.mClassName, test.mTestName); 196 } 197 198 Set<String> argumentKeys = test.mInstrumentationArgs.keySet(); 199 for (String s : argumentKeys) { 200 runner.addInstrumentationArg(s, test.mInstrumentationArgs.get(s)); 201 } 202 203 mTestDevice.runInstrumentationTests(runner, listener, auxListener); 204 205 // Grab a bugreport if warranted 206 if (auxListener.hasFailedTests()) { 207 Log.e(LOG_TAG, String.format("Grabbing bugreport after test '%s' finished with " + 208 "%d failures.", test.mTestName, auxListener.getNumAllFailedTests())); 209 InputStreamSource bugreport = mTestDevice.getBugreport(); 210 listener.testLog(String.format("bugreport-%s.txt", test.mTestName), 211 LogDataType.BUGREPORT, bugreport); 212 bugreport.cancel(); 213 } 214 } 215 216 /** 217 * Clean up temp files from test runs 218 * <p /> 219 * Note that all photos on the test device will be removed 220 */ 221 private void cleanTmpFiles() throws DeviceNotAvailableException { 222 String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 223 mTestDevice.executeShellCommand(String.format("rm -r %s/DCIM/Camera", extStore)); 224 mTestDevice.executeShellCommand(String.format("rm %s/%s", extStore, mOutputPath)); 225 } 226 227 /** 228 * Pull the output file from the device, add it to the logs, and also parse out the relevant 229 * test metrics and report them. Additionally, pull the memory file (if it exists) and report 230 * it. 231 */ 232 private void logOutputFiles(TestInfo test, ITestInvocationListener listener) 233 throws DeviceNotAvailableException { 234 File outputFile = null; 235 InputStreamSource outputSource = null; 236 try { 237 outputFile = mTestDevice.pullFileFromExternal(mOutputPath); 238 239 if (outputFile == null) { 240 return; 241 } 242 243 // Upload a verbatim copy of the output file 244 Log.d(LOG_TAG, String.format("Sending %d byte file %s into the logosphere!", 245 outputFile.length(), outputFile)); 246 outputSource = new FileInputStreamSource(outputFile); 247 listener.testLog(String.format("output-%s.txt", test.mTestName), LogDataType.TEXT, 248 outputSource); 249 250 // Parse the output file to upload aggregated metrics 251 parseOutputFile(test, new FileInputStream(outputFile), listener); 252 } catch (IOException e) { 253 Log.e(LOG_TAG, String.format("IOException while reading or parsing output file: %s", e)); 254 } finally { 255 FileUtil.deleteFile(outputFile); 256 StreamUtil.cancel(outputSource); 257 } 258 } 259 260 /** 261 * Parse the relevant metrics from the Instrumentation test output file 262 */ 263 private void parseOutputFile(TestInfo test, InputStream dataStream, 264 ITestInvocationListener listener) { 265 Map<String, String> runMetrics = new HashMap<>(); 266 267 String contents; 268 try { 269 contents = StreamUtil.getStringFromStream(dataStream); 270 } catch (IOException e) { 271 Log.e(LOG_TAG, String.format("Got IOException during %s test processing: %s", 272 test.mTestName, e)); 273 return; 274 } 275 276 String key = null; 277 Integer countExpected = null; 278 Integer countActual = null; 279 280 List<String> lines = Arrays.asList(contents.split("\n")); 281 ListIterator<String> lineIter = lines.listIterator(); 282 String line; 283 while (lineIter.hasNext()) { 284 line = lineIter.next(); 285 List<List<String>> capture = new ArrayList<>(1); 286 String pattern = test.mPatternMap.retrieve(capture, line); 287 if (pattern != null) { 288 if ("loopCount".equals(pattern)) { 289 // First capture in first (only) string 290 countExpected = Integer.parseInt(capture.get(0).get(0)); 291 } else if ("iters".equals(pattern)) { 292 // First capture in first (only) string 293 countActual = Integer.parseInt(capture.get(0).get(0)); 294 295 if (countActual != null) { 296 // countActual starts counting at 0 297 countActual += 1; 298 } 299 } else { 300 // Assume that the pattern is the name of a key 301 302 // commit, if there was a previous key 303 if (key != null) { 304 int value = coalesceLoopCounts(countActual, countExpected); 305 runMetrics.put(key, Integer.toString(value)); 306 } 307 308 key = pattern; 309 countExpected = null; 310 countActual = null; 311 } 312 313 Log.d(LOG_TAG, String.format("Got %s key '%s' and captures '%s'", 314 test.mTestName, key, capture.toString())); 315 } else if (line.isEmpty()) { 316 // ignore 317 continue; 318 } else { 319 Log.e(LOG_TAG, String.format("Got unmatched line: %s", line)); 320 continue; 321 } 322 323 // commit the final key, if there was one 324 if (key != null) { 325 int value = coalesceLoopCounts(countActual, countExpected); 326 runMetrics.put(key, Integer.toString(value)); 327 } 328 } 329 330 reportMetrics(listener, test, runMetrics); 331 } 332 333 /** 334 * Given an actual and an expected iteration count, determine a single metric to report. 335 */ 336 private int coalesceLoopCounts(Integer actual, Integer expected) { 337 if (expected == null || expected <= 0) { 338 return -1; 339 } else if (actual == null) { 340 return expected; 341 } else { 342 return actual; 343 } 344 } 345 346 /** 347 * Report run metrics by creating an empty test run to stick them in 348 * <p /> 349 * Exposed for unit testing 350 */ 351 void reportMetrics(ITestInvocationListener listener, TestInfo test, 352 Map<String, String> metrics) { 353 // Create an empty testRun to report the parsed runMetrics 354 Log.e(LOG_TAG, String.format("About to report metrics for %s: %s", test.mTestMetricsName, 355 metrics)); 356 listener.testRunStarted(test.mTestMetricsName, 0); 357 listener.testRunEnded(0, metrics); 358 } 359 360 @Override 361 public void setDevice(ITestDevice device) { 362 mTestDevice = device; 363 } 364 365 @Override 366 public ITestDevice getDevice() { 367 return mTestDevice; 368 } 369 370 /** 371 * A meta-test to ensure that bits of the BluetoothStressTest are working properly 372 */ 373 public static class MetaTest extends TestCase { 374 private CameraStressTest mTestInstance = null; 375 376 private TestInfo mTestInfo = null; 377 378 private TestInfo mReportedTestInfo = null; 379 private Map<String, String> mReportedMetrics = null; 380 381 private static String join(String... pieces) { 382 StringBuilder sb = new StringBuilder(); 383 for (String piece : pieces) { 384 sb.append(piece); 385 sb.append("\n"); 386 } 387 return sb.toString(); 388 } 389 390 @Override 391 public void setUp() throws Exception { 392 mTestInstance = new CameraStressTest() { 393 @Override 394 void reportMetrics(ITestInvocationListener l, TestInfo test, 395 Map<String, String> metrics) { 396 mReportedTestInfo = test; 397 mReportedMetrics = metrics; 398 } 399 }; 400 401 // Image capture stress test 402 mTestInfo = new TestInfo(); 403 TestInfo t = mTestInfo; // for convenience 404 t.mTestName = "capture"; 405 t.mClassName = "com.android.camera.stress.ImageCapture"; 406 t.mTestMetricsName = "camera_application_stress"; 407 t.mPatternMap = getPatternMap(); 408 } 409 410 /** 411 * Make sure that parsing works for devices sending output in the old format 412 */ 413 public void testParse_old() throws Exception { 414 String output = join( 415 "Camera Image Capture", 416 "No of loops :100", 417 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 418 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 " + 419 ",36 ,37 ,38 ,39 ,40 ,41 ,42", 420 "Camera Video Capture", 421 "No of loops :100", 422 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 423 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 " + 424 ",36 ,37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ,51 ,52 " + 425 ",53 ,54 ,55 ,56 ,57 ,58 ,59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 " + 426 ",70 ,71 ,72 ,73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 ,83 ,84 ,85 ,86 " + 427 ",87 ,88 ,89 ,90 ,91 ,92 ,93 ,94 ,95 ,96 ,97 ,98 ,99", 428 "Camera Switch Mode:", 429 "No of loops :200", 430 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13"); 431 432 InputStream iStream = new ByteArrayInputStream(output.getBytes()); 433 mTestInstance.parseOutputFile(mTestInfo, iStream, null); 434 assertEquals(mTestInfo, mReportedTestInfo); 435 assertNotNull(mReportedMetrics); 436 Log.e(LOG_TAG, String.format("Got reported metrics: %s", mReportedMetrics.toString())); 437 assertEquals(3, mReportedMetrics.size()); 438 assertEquals("43", mReportedMetrics.get("ImageCapture")); 439 assertEquals("100", mReportedMetrics.get("VideoRecording")); 440 assertEquals("14", mReportedMetrics.get("SwitchPreview")); 441 } 442 443 /** 444 * Make sure that parsing works for devices sending output in the new format 445 */ 446 public void testParse_new() throws Exception { 447 String output = join( 448 "Camera Stress Test result", 449 "/folder/subfolder/data/CameraStressTest_git_honeycomb-mr1-release_" + 450 "1700614441c02617_109535_CameraStressOut.txt", 451 "Back Camera Image Capture", 452 "No of loops :100", 453 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 454 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 " + 455 ",37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ,51 ,52 ,53 ,54 " + 456 ",55 ,56 ,57 ,58 ,59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 " + 457 ",73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 ,83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 " + 458 ",91 ,92 ,93 ,94 ,95 ,96 ,97 ,98 ,99", 459 "Front Camera Image Capture", 460 "No of loops :100", 461 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 462 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 " + 463 ",37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ,51 ,52 ,53 ,54 " + 464 ",55 ,56 ,57 ,58 ,59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 " + 465 ",73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 ,83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 " + 466 ",91 ,92 ,93 ,94 ,95 ,96 ,97 ,98", 467 "Back Camera Video Capture", 468 "No of loops :100", 469 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 470 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 " + 471 ",37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ,51 ,52 ,53 ,54 " + 472 ",55 ,56 ,57 ,58 ,59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 " + 473 ",73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 ,83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 " + 474 ",91 ,92 ,93 ,94 ,95 ,96 ,97", 475 "Front Camera Video Capture", 476 "No of loops :100", 477 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 478 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 " + 479 ",37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ,51 ,52 ,53 ,54 " + 480 ",55 ,56 ,57 ,58 ,59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 " + 481 ",73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 ,83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 " + 482 ",91 ,92 ,93 ,94 ,95 ,96 ,97 ,98 ,99", 483 "Camera Switch Mode:", 484 "No of loops :200", 485 "loop: ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 " + 486 ",19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 " + 487 ",37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ,51 ,52 ,53 ,54 " + 488 ",55 ,56 ,57 ,58 ,59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 " + 489 ",73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 ,83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 " + 490 ",91 ,92 ,93 ,94 ,95 ,96 ,97 ,98 ,99 ,100 ,101 ,102 ,103 ,104 ,105 ,106 " + 491 ",107 ,108 ,109 ,110 ,111 ,112 ,113 ,114 ,115 ,116 ,117 ,118 ,119 ,120 " + 492 ",121 ,122 ,123 ,124 ,125 ,126 ,127 ,128 ,129 ,130 ,131 ,132 ,133 ,134 " + 493 ",135 ,136 ,137 ,138 ,139 ,140 ,141 ,142 ,143 ,144 ,145 ,146 ,147 ,148 " + 494 ",149 ,150 ,151 ,152 ,153 ,154 ,155 ,156 ,157 ,158 ,159 ,160 ,161 ,162 " + 495 ",163 ,164 ,165 ,166 ,167 ,168 ,169 ,170 ,171 ,172 ,173 ,174 ,175 ,176 " + 496 ",177 ,178 ,179 ,180 ,181 ,182 ,183 ,184 ,185 ,186 ,187 ,188 ,189 ,190 " + 497 ",191 ,192 ,193 ,194 ,195 ,196 ,197 ,198 ,199"); 498 499 InputStream iStream = new ByteArrayInputStream(output.getBytes()); 500 mTestInstance.parseOutputFile(mTestInfo, iStream, null); 501 assertEquals(mTestInfo, mReportedTestInfo); 502 assertNotNull(mReportedMetrics); 503 Log.e(LOG_TAG, String.format("Got reported metrics: %s", mReportedMetrics.toString())); 504 assertEquals(5, mReportedMetrics.size()); 505 assertEquals("100", mReportedMetrics.get("ImageCapture")); 506 assertEquals("99", mReportedMetrics.get("FrontImageCapture")); 507 assertEquals("98", mReportedMetrics.get("VideoRecording")); 508 assertEquals("100", mReportedMetrics.get("FrontVideoRecording")); 509 assertEquals("200", mReportedMetrics.get("SwitchPreview")); 510 } 511 } 512 } 513 514