1 /*
2  * Copyright (C) 2015 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.CollectingOutputReceiver;
20 import com.android.tradefed.config.IConfiguration;
21 import com.android.tradefed.config.IConfigurationReceiver;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.invoker.TestInformation;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.result.ByteArrayInputStreamSource;
28 import com.android.tradefed.result.CollectingTestListener;
29 import com.android.tradefed.result.ITestInvocationListener;
30 import com.android.tradefed.result.LogDataType;
31 import com.android.tradefed.result.TestDescription;
32 import com.android.tradefed.testtype.IDeviceTest;
33 import com.android.tradefed.testtype.IRemoteTest;
34 import com.android.tradefed.testtype.InstrumentationTest;
35 import com.android.tradefed.util.FileUtil;
36 import com.android.tradefed.util.IRunUtil;
37 import com.android.tradefed.util.RunUtil;
38 import com.android.tradefed.util.StreamUtil;
39 
40 import org.junit.Assert;
41 
42 import java.io.BufferedReader;
43 import java.io.BufferedWriter;
44 import java.io.File;
45 import java.io.FileWriter;
46 import java.io.IOException;
47 import java.io.StringReader;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.HashMap;
51 import java.util.Map;
52 import java.util.Timer;
53 import java.util.TimerTask;
54 import java.util.concurrent.TimeUnit;
55 
56 /**
57  * Camera test base class
58  *
59  * Camera2StressTest, CameraStartupTest, Camera2LatencyTest and CameraPerformanceTest use this base
60  * class for Camera ivvavik and later.
61  */
62 public class CameraTestBase implements IDeviceTest, IRemoteTest, IConfigurationReceiver {
63 
64     private static final long SHELL_TIMEOUT_MS = 60 * 1000;  // 1 min
65     private static final int SHELL_MAX_ATTEMPTS = 3;
66     protected static final String PROCESS_CAMERA_DAEMON = "mm-qcamera-daemon";
67     protected static final String PROCESS_MEDIASERVER = "mediaserver";
68     protected static final String PROCESS_CAMERA_APP = "com.google.android.GoogleCamera";
69     protected static final String DUMP_ION_HEAPS_COMMAND = "cat /d/ion/heaps/system";
70     protected static final String ARGUMENT_TEST_ITERATIONS = "iterations";
71 
72     @Option(name = "test-package", description = "Test package to run.")
73     private String mTestPackage = "com.google.android.camera";
74 
75     @Option(name = "test-class", description = "Test class to run.")
76     private String mTestClass = null;
77 
78     @Option(name = "test-methods", description = "Test method to run. May be repeated.")
79     private Collection<String> mTestMethods = new ArrayList<>();
80 
81     @Option(name = "test-runner", description = "Test runner for test instrumentation.")
82     private String mTestRunner = "android.test.InstrumentationTestRunner";
83 
84     @Option(name = "test-timeout", description = "Max time allowed in ms for a test run.")
85     private int mTestTimeoutMs = 60 * 60 * 1000; // 1 hour
86 
87     @Option(name = "shell-timeout",
88             description="The defined timeout (in milliseconds) is used as a maximum waiting time "
89                     + "when expecting the command output from the device. At any time, if the "
90                     + "shell command does not output anything for a period longer than defined "
91                     + "timeout the TF run terminates. For no timeout, set to 0.")
92     private long mShellTimeoutMs = 60 * 60 * 1000;  // default to 1 hour
93 
94     @Option(name = "ru-key", description = "Result key to use when posting to the dashboard.")
95     private String mRuKey = null;
96 
97     /** Rely on invocation level logcat capture rather than this. */
98     @Deprecated
99     @Option(
100         name = "logcat-on-failure",
101         description = "take a logcat snapshot on every test failure."
102     )
103     private boolean mLogcatOnFailure = false;
104 
105     @Option(
106         name = "instrumentation-arg",
107         description = "Additional instrumentation arguments to provide."
108     )
109     private Map<String, String> mInstrArgMap = new HashMap<>();
110 
111     @Option(name = "dump-meminfo", description =
112             "take a dumpsys meminfo at a given interval time.")
113     private boolean mDumpMeminfo = false;
114 
115     @Option(name="dump-meminfo-interval-ms",
116             description="Interval of calling dumpsys meminfo in milliseconds.")
117     private int mMeminfoIntervalMs = 5 * 60 * 1000;     // 5 minutes
118 
119     @Option(name = "dump-ion-heap", description =
120             "dump ION allocations at the end of test.")
121     private boolean mDumpIonHeap = false;
122 
123     @Option(name = "dump-thread-count", description =
124             "Count the number of threads in Camera process at a given interval time.")
125     private boolean mDumpThreadCount = false;
126 
127     @Option(name="dump-thread-count-interval-ms",
128             description="Interval of calling ps to count the number of threads in milliseconds.")
129     private int mThreadCountIntervalMs = 5 * 60 * 1000; // 5 minutes
130 
131     @Option(name="iterations", description="The number of iterations to run. Default to 1. "
132             + "This takes effect only when Camera2InstrumentationTestRunner is used to execute "
133             + "framework stress tests.")
134     private int mIterations = 1;
135 
136     private ITestDevice mDevice = null;
137 
138     // A base listener to collect the results from each test run. Test results will be forwarded
139     // to other listeners.
140     private CollectingTestListener mCollectingListener = null;
141     private ITestInvocationListener mListener = null;
142 
143     private long mStartTimeMs = 0;
144 
145     public MeminfoTimer mMeminfoTimer = null;
146     public ThreadTrackerTimer mThreadTrackerTimer = null;
147 
148     protected IConfiguration mConfiguration;
149 
150     /** {@inheritDoc} */
151     @Override
run(TestInformation testInfo, ITestInvocationListener listener)152     public void run(TestInformation testInfo, ITestInvocationListener listener)
153             throws DeviceNotAvailableException {
154         // ignore
155     }
156 
157     /**
158      * Run Camera instrumentation test with a listener to gather the metrics from the individual
159      * test runs.
160      *
161      * @param listener the ITestInvocationListener of test results
162      * @param collectingListener the {@link CollectingTestListener} to collect the metrics from test
163      *     runs
164      * @throws DeviceNotAvailableException
165      */
runInstrumentationTest( TestInformation testInfo, ITestInvocationListener listener, CameraTestMetricsCollectionListener.DefaultCollectingListener collectingListener)166     protected void runInstrumentationTest(
167             TestInformation testInfo,
168             ITestInvocationListener listener,
169             CameraTestMetricsCollectionListener.DefaultCollectingListener collectingListener)
170             throws DeviceNotAvailableException {
171         Assert.assertNotNull(collectingListener);
172         mCollectingListener = collectingListener;
173         mListener = listener;
174         InstrumentationTest instr = new InstrumentationTest();
175         instr.setDevice(getDevice());
176         instr.setConfiguration(mConfiguration);
177         instr.setPackageName(getTestPackage());
178         instr.setRunnerName(getTestRunner());
179         instr.setClassName(getTestClass());
180         instr.setTestTimeout(getTestTimeoutMs());
181         instr.setShellTimeout(getShellTimeoutMs());
182         instr.setRunName(getRuKey());
183         instr.setRerunMode(false);
184         if (!getIsolatedStorageFlag()) {
185             instr.setIsolatedStorage(false);
186         }
187 
188         // Set test iteration.
189         if (getIterationCount() > 1) {
190             CLog.v("Setting test iterations: %d", getIterationCount());
191             Map<String, String> instrArgMap = getInstrumentationArgMap();
192             instrArgMap.put(ARGUMENT_TEST_ITERATIONS, String.valueOf(getIterationCount()));
193         }
194 
195         for (Map.Entry<String, String> entry : getInstrumentationArgMap().entrySet()) {
196             instr.addInstrumentationArg(entry.getKey(), entry.getValue());
197         }
198 
199         // Check if dumpheap needs to be taken for native processes before test runs.
200         if (shouldDumpMeminfo()) {
201             mMeminfoTimer = new MeminfoTimer(0, mMeminfoIntervalMs);
202         }
203         if (shouldDumpThreadCount()) {
204             long delayMs = mThreadCountIntervalMs / 2;  // Not to run all dump at the same interval.
205             mThreadTrackerTimer = new ThreadTrackerTimer(delayMs, mThreadCountIntervalMs);
206         }
207 
208         // Run tests.
209         mStartTimeMs = System.currentTimeMillis();
210         if (mTestMethods.size() > 0) {
211             CLog.d(String.format("The number of test methods is: %d", mTestMethods.size()));
212             for (String testName : mTestMethods) {
213                 instr.setMethodName(testName);
214                 instr.run(testInfo, mCollectingListener);
215             }
216         } else {
217             instr.run(testInfo, mCollectingListener);
218         }
219 
220         dumpIonHeaps(mCollectingListener, getTestClass());
221     }
222 
223         private Map<String, String> mMetrics = new HashMap<>();
224         private Map<String, String> mFatalErrors = new HashMap<>();
225 
226         private static final String INCOMPLETE_TEST_ERR_MSG_PREFIX =
227                 "Test failed to run to completion. Reason: 'Instrumentation run failed";
228 
229 
getAggregatedMetrics()230         public Map<String, String> getAggregatedMetrics() {
231             return mMetrics;
232         }
233 
getListeners()234         public ITestInvocationListener getListeners() {
235             return mListener;
236         }
237 
238     // TODO: Leverage AUPT to collect system logs (meminfo, ION allocations and processes/threads)
239     public class MeminfoTimer {
240 
241         private static final String LOG_HEADER =
242                 "uptime,pssCameraDaemon,pssCameraApp,ramTotal,ramFree,ramUsed";
243         private static final String DUMPSYS_MEMINFO_COMMAND =
244                 "dumpsys meminfo -c | grep -w -e " + "^ram -e ^time";
245         private String[] mDumpsysMemInfoProc = {
246             PROCESS_CAMERA_DAEMON, PROCESS_CAMERA_APP, PROCESS_MEDIASERVER
247         };
248         private static final int STATE_STOPPED = 0;
249         private static final int STATE_SCHEDULED = 1;
250         private static final int STATE_RUNNING = 2;
251 
252         private int mState = STATE_STOPPED;
253         private Timer mTimer = new Timer(true); // run as a daemon thread
254         private long mDelayMs = 0;
255         private long mPeriodMs = 60 * 1000;  // 60 sec
256         private File mOutputFile = null;
257         private String mCommand;
258 
MeminfoTimer(long delayMs, long periodMs)259         public MeminfoTimer(long delayMs, long periodMs) {
260             mDelayMs = delayMs;
261             mPeriodMs = periodMs;
262             mCommand = DUMPSYS_MEMINFO_COMMAND;
263             for (String process : mDumpsysMemInfoProc) {
264                 mCommand += " -e " + process;
265             }
266         }
267 
start(TestDescription test)268         synchronized void start(TestDescription test) {
269             if (isRunning()) {
270                 stop();
271             }
272             // Create an output file.
273             if (createOutputFile(test) == null) {
274                 CLog.w("Stop collecting meminfo since meminfo log file not found.");
275                 mState = STATE_STOPPED;
276                 return; // No-op
277             }
278             mTimer.scheduleAtFixedRate(new TimerTask() {
279                 @Override
280                 public void run() {
281                     mState = STATE_RUNNING;
282                     dumpMeminfo(mCommand, mOutputFile);
283                 }
284             }, mDelayMs, mPeriodMs);
285             mState = STATE_SCHEDULED;
286         }
287 
stop()288         synchronized void stop() {
289             mState = STATE_STOPPED;
290             mTimer.cancel();
291         }
292 
isRunning()293         synchronized boolean isRunning() {
294             return (mState == STATE_RUNNING);
295         }
296 
getOutputFile()297         public File getOutputFile() {
298             return mOutputFile;
299         }
300 
createOutputFile(TestDescription test)301         private File createOutputFile(TestDescription test) {
302             try {
303                 mOutputFile = FileUtil.createTempFile(
304                         String.format("meminfo_%s", test.getTestName()), "csv");
305                 BufferedWriter writer = new BufferedWriter(new FileWriter(mOutputFile, false));
306                 writer.write(LOG_HEADER);
307                 writer.newLine();
308                 writer.flush();
309                 writer.close();
310             } catch (IOException e) {
311                 CLog.w("Failed to create meminfo log file %s:", mOutputFile.getAbsolutePath());
312                 CLog.e(e);
313                 return null;
314             }
315             return mOutputFile;
316         }
317     }
318 
dumpMeminfo(String command, File outputFile)319     void dumpMeminfo(String command, File outputFile) {
320         try {
321             CollectingOutputReceiver receiver = new CollectingOutputReceiver();
322             // Dump meminfo in a compact form.
323             getDevice().executeShellCommand(command, receiver,
324                     SHELL_TIMEOUT_MS, TimeUnit.MILLISECONDS, SHELL_MAX_ATTEMPTS);
325             printMeminfo(outputFile, receiver.getOutput());
326         } catch (DeviceNotAvailableException e) {
327             CLog.w("Failed to dump meminfo:");
328             CLog.e(e);
329         }
330     }
331 
printMeminfo(File outputFile, String meminfo)332     void printMeminfo(File outputFile, String meminfo) {
333         // Parse meminfo and print each iteration in a line in a .csv format. The meminfo output
334         // are separated into three different formats:
335         //
336         // Format: time,<uptime>,<realtime>
337         // eg. "time,59459911,63354673"
338         //
339         // Format: proc,<oom_label>,<process_name>,<pid>,<pss>,<hasActivities>
340         // eg. "proc,native,mm-qcamera-daemon,522,12881,e"
341         //     "proc,fore,com.google.android.GoogleCamera,26560,70880,a"
342         //
343         // Format: ram,<total>,<free>,<used>
344         // eg. "ram,1857364,810189,541516"
345         BufferedWriter writer = null;
346         BufferedReader reader = null;
347         try {
348             final String DELIMITER = ",";
349             writer = new BufferedWriter(new FileWriter(outputFile, true));
350             reader = new BufferedReader(new StringReader(meminfo));
351             String line;
352             String uptime = null;
353             String pssCameraNative = null;
354             String pssCameraApp = null;
355             String ramTotal = null;
356             String ramFree = null;
357             String ramUsed = null;
358             while ((line = reader.readLine()) != null) {
359                 if (line.startsWith("time")) {
360                     uptime = line.split(DELIMITER)[1];
361                 } else if (line.startsWith("ram")) {
362                     String[] ram = line.split(DELIMITER);
363                     ramTotal = ram[1];
364                     ramFree = ram[2];
365                     ramUsed = ram[3];
366                 } else if (line.contains(PROCESS_CAMERA_DAEMON)) {
367                     pssCameraNative = line.split(DELIMITER)[4];
368                 } else if (line.contains(PROCESS_CAMERA_APP)) {
369                     pssCameraApp = line.split(DELIMITER)[4];
370                 }
371             }
372             String printMsg = String.format(
373                     "%s,%s,%s,%s,%s,%s", uptime, pssCameraNative, pssCameraApp,
374                     ramTotal, ramFree, ramUsed);
375             writer.write(printMsg);
376             writer.newLine();
377             writer.flush();
378         } catch (IOException e) {
379             CLog.w("Failed to print meminfo to %s:", outputFile.getAbsolutePath());
380             CLog.e(e);
381         } finally {
382             StreamUtil.close(writer);
383         }
384     }
385 
386     // TODO: Leverage AUPT to collect system logs (meminfo, ION allocations and processes/threads)
387     public class ThreadTrackerTimer {
388 
389         // list all threads in a given process, remove the first header line, squeeze whitespaces,
390         // select thread name (in 14th column), then sort and group by its name.
391         // Examples:
392         //    3 SoundPoolThread
393         //    3 SoundPool
394         //    2 Camera Job Disp
395         //    1 pool-7-thread-1
396         //    1 pool-6-thread-1
397         // FIXME: Resolve the error "sh: syntax error: '|' unexpected" using the command below
398         // $ /system/bin/ps -t -p %s | tr -s ' ' | cut -d' ' -f13- | sort | uniq -c | sort -nr"
399         private static final String PS_COMMAND_FORMAT = "/system/bin/ps -t -p %s";
400         private static final String PGREP_COMMAND_FORMAT = "pgrep %s";
401         private static final int STATE_STOPPED = 0;
402         private static final int STATE_SCHEDULED = 1;
403         private static final int STATE_RUNNING = 2;
404 
405         private int mState = STATE_STOPPED;
406         private Timer mTimer = new Timer(true); // run as a daemon thread
407         private long mDelayMs = 0;
408         private long mPeriodMs = 60 * 1000;  // 60 sec
409         private File mOutputFile = null;
410 
ThreadTrackerTimer(long delayMs, long periodMs)411         public ThreadTrackerTimer(long delayMs, long periodMs) {
412             mDelayMs = delayMs;
413             mPeriodMs = periodMs;
414         }
415 
start(TestDescription test)416         synchronized void start(TestDescription test) {
417             if (isRunning()) {
418                 stop();
419             }
420             // Create an output file.
421             if (createOutputFile(test) == null) {
422                 CLog.w("Stop collecting thread counts since log file not found.");
423                 mState = STATE_STOPPED;
424                 return; // No-op
425             }
426             mTimer.scheduleAtFixedRate(new TimerTask() {
427                 @Override
428                 public void run() {
429                     mState = STATE_RUNNING;
430                     dumpThreadCount(PS_COMMAND_FORMAT, getPid(PROCESS_CAMERA_APP), mOutputFile);
431                 }
432             }, mDelayMs, mPeriodMs);
433             mState = STATE_SCHEDULED;
434         }
435 
stop()436         synchronized void stop() {
437             mState = STATE_STOPPED;
438             mTimer.cancel();
439         }
440 
isRunning()441         synchronized boolean isRunning() {
442             return (mState == STATE_RUNNING);
443         }
444 
getOutputFile()445         public File getOutputFile() {
446             return mOutputFile;
447         }
448 
createOutputFile(TestDescription test)449         File createOutputFile(TestDescription test) {
450             try {
451                 mOutputFile = FileUtil.createTempFile(
452                         String.format("ps_%s", test.getTestName()), "txt");
453                 new BufferedWriter(new FileWriter(mOutputFile, false)).close();
454             } catch (IOException e) {
455                 CLog.w("Failed to create processes and threads file %s:",
456                         mOutputFile.getAbsolutePath());
457                 CLog.e(e);
458                 return null;
459             }
460             return mOutputFile;
461         }
462 
getPid(String processName)463         String getPid(String processName) {
464             String result = null;
465             try {
466                 result = getDevice().executeShellCommand(String.format(PGREP_COMMAND_FORMAT,
467                         processName));
468             } catch (DeviceNotAvailableException e) {
469                 CLog.w("Failed to get pid %s:", processName);
470                 CLog.e(e);
471             }
472             return result;
473         }
474 
getUptime()475         String getUptime() {
476             String uptime = null;
477             try {
478                 // uptime will typically have a format like "5278.73 1866.80".  Use the first one
479                 // (which is wall-time)
480                 uptime = getDevice().executeShellCommand("cat /proc/uptime").split(" ")[0];
481                 Float.parseFloat(uptime);
482             } catch (NumberFormatException e) {
483                 CLog.w("Failed to get valid uptime %s: %s", uptime, e);
484             } catch (DeviceNotAvailableException e) {
485                 CLog.w("Failed to get valid uptime: %s", e);
486             }
487             return uptime;
488         }
489 
dumpThreadCount(String commandFormat, String pid, File outputFile)490         void dumpThreadCount(String commandFormat, String pid, File outputFile) {
491             try {
492                 if ("".equals(pid)) {
493                     return;
494                 }
495                 String result = getDevice().executeShellCommand(String.format(commandFormat, pid));
496                 String header = String.format("UPTIME: %s", getUptime());
497                 BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile, true));
498                 writer.write(header);
499                 writer.newLine();
500                 writer.write(result);
501                 writer.newLine();
502                 writer.flush();
503                 writer.close();
504             } catch (DeviceNotAvailableException | IOException e) {
505                 CLog.w("Failed to dump thread count:");
506                 CLog.e(e);
507             }
508         }
509     }
510 
511     // TODO: Leverage AUPT to collect system logs (meminfo, ION allocations and
512     // processes/threads)
dumpIonHeaps(ITestInvocationListener listener, String testClass)513     protected void dumpIonHeaps(ITestInvocationListener listener, String testClass) {
514         if (!shouldDumpIonHeap()) {
515             return; // No-op if option is not set.
516         }
517         try {
518             String result = getDevice().executeShellCommand(DUMP_ION_HEAPS_COMMAND);
519             if (!"".equals(result)) {
520                 String fileName = String.format("ionheaps_%s_onEnd", testClass);
521                 listener.testLog(fileName, LogDataType.TEXT,
522                         new ByteArrayInputStreamSource(result.getBytes()));
523             }
524         } catch (DeviceNotAvailableException e) {
525             CLog.w("Failed to dump ION heaps:");
526             CLog.e(e);
527         }
528     }
529 
530     /**
531      * {@inheritDoc}
532      */
533     @Override
setDevice(ITestDevice device)534     public void setDevice(ITestDevice device) {
535         mDevice = device;
536     }
537 
538     /**
539      * {@inheritDoc}
540      */
541     @Override
getDevice()542     public ITestDevice getDevice() {
543         return mDevice;
544     }
545 
546     /**
547      * {@inheritDoc}
548      */
549     @Override
setConfiguration(IConfiguration configuration)550     public void setConfiguration(IConfiguration configuration) {
551         mConfiguration = configuration;
552     }
553 
554     /**
555      * Get the {@link IRunUtil} instance to use.
556      * <p/>
557      * Exposed so unit tests can mock.
558      */
getRunUtil()559     IRunUtil getRunUtil() {
560         return RunUtil.getDefault();
561     }
562 
563     /**
564      * Get the duration of Camera test instrumentation in milliseconds.
565      *
566      * @return the duration of Camera instrumentation test until it is called.
567      */
getTestDurationMs()568     public long getTestDurationMs() {
569         return System.currentTimeMillis() - mStartTimeMs;
570     }
571 
getTestPackage()572     public String getTestPackage() {
573         return mTestPackage;
574     }
575 
setTestPackage(String testPackage)576     public void setTestPackage(String testPackage) {
577         mTestPackage = testPackage;
578     }
579 
getTestClass()580     public String getTestClass() {
581         return mTestClass;
582     }
583 
setTestClass(String testClass)584     public void setTestClass(String testClass) {
585         mTestClass = testClass;
586     }
587 
getTestRunner()588     public String getTestRunner() {
589         return mTestRunner;
590     }
591 
setTestRunner(String testRunner)592     public void setTestRunner(String testRunner) {
593         mTestRunner = testRunner;
594     }
595 
getTestTimeoutMs()596     public int getTestTimeoutMs() {
597         return mTestTimeoutMs;
598     }
599 
setTestTimeoutMs(int testTimeoutMs)600     public void setTestTimeoutMs(int testTimeoutMs) {
601         mTestTimeoutMs = testTimeoutMs;
602     }
603 
getShellTimeoutMs()604     public long getShellTimeoutMs() {
605         return mShellTimeoutMs;
606     }
607 
setShellTimeoutMs(long shellTimeoutMs)608     public void setShellTimeoutMs(long shellTimeoutMs) {
609         mShellTimeoutMs = shellTimeoutMs;
610     }
611 
getRuKey()612     public String getRuKey() {
613         return mRuKey;
614     }
615 
setRuKey(String ruKey)616     public void setRuKey(String ruKey) {
617         mRuKey = ruKey;
618     }
619 
shouldDumpMeminfo()620     public boolean shouldDumpMeminfo() {
621         return mDumpMeminfo;
622     }
623 
shouldDumpIonHeap()624     public boolean shouldDumpIonHeap() {
625         return mDumpIonHeap;
626     }
627 
shouldDumpThreadCount()628     public boolean shouldDumpThreadCount() {
629         return mDumpThreadCount;
630     }
631 
getCollectingListener()632     public ITestInvocationListener getCollectingListener() {
633         return mCollectingListener;
634     }
635 
setLogcatOnFailure(boolean logcatOnFailure)636     public void setLogcatOnFailure(boolean logcatOnFailure) {
637         mLogcatOnFailure = logcatOnFailure;
638     }
639 
getIterationCount()640     public int getIterationCount() {
641         return mIterations;
642     }
643 
getIsolatedStorageFlag()644     public boolean getIsolatedStorageFlag() {
645         return mIsolatedStorageFlag;
646     }
647 
setIsolatedStorageFlag(boolean isolatedStorage)648     public void setIsolatedStorageFlag(boolean isolatedStorage) {
649         mIsolatedStorageFlag = isolatedStorage;
650     }
651     boolean mIsolatedStorageFlag = true;
652 
653 
getInstrumentationArgMap()654     public Map<String, String> getInstrumentationArgMap() { return mInstrArgMap; }
655 }
656