xref: /aosp_15_r20/cts/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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 
17 package com.android.compatibility.testtype;
18 
19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
20 import com.android.ddmlib.Log;
21 import com.android.ddmlib.Log.LogLevel;
22 import com.android.ddmlib.MultiLineReceiver;
23 import com.android.tradefed.build.IBuildInfo;
24 import com.android.tradefed.config.Option;
25 import com.android.tradefed.config.OptionCopier;
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.device.ITestDevice;
28 import com.android.tradefed.invoker.TestInformation;
29 import com.android.tradefed.log.LogUtil.CLog;
30 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
31 import com.android.tradefed.result.FailureDescription;
32 import com.android.tradefed.result.ITestInvocationListener;
33 import com.android.tradefed.result.TestDescription;
34 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
35 import com.android.tradefed.testtype.IAbi;
36 import com.android.tradefed.testtype.IAbiReceiver;
37 import com.android.tradefed.testtype.IBuildReceiver;
38 import com.android.tradefed.testtype.IDeviceTest;
39 import com.android.tradefed.testtype.IRemoteTest;
40 import com.android.tradefed.testtype.IRuntimeHintProvider;
41 import com.android.tradefed.testtype.IShardableTest;
42 import com.android.tradefed.testtype.ITestCollector;
43 import com.android.tradefed.testtype.ITestFileFilterReceiver;
44 import com.android.tradefed.testtype.ITestFilterReceiver;
45 import com.android.tradefed.util.AbiUtils;
46 import com.android.tradefed.util.ArrayUtil;
47 import com.android.tradefed.util.FileUtil;
48 
49 import com.google.common.base.Splitter;
50 
51 import java.io.BufferedReader;
52 import java.io.File;
53 import java.io.FileReader;
54 import java.io.FilenameFilter;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.LinkedHashSet;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.concurrent.TimeUnit;
66 
67 import vogar.expect.ExpectationStore;
68 import vogar.expect.ModeId;
69 
70 /**
71  * A wrapper to run tests against Dalvik.
72  */
73 public class DalvikTest implements IAbiReceiver, IBuildReceiver, IDeviceTest, IRemoteTest,
74         IRuntimeHintProvider, IShardableTest, ITestCollector, ITestFileFilterReceiver,
75         ITestFilterReceiver {
76 
77     private static final String TAG = DalvikTest.class.getSimpleName();
78 
79     /**
80      * TEST_PACKAGES is a Set containing the names of packages on the classpath known to contain
81      * tests to be run under DalvikTest. The TEST_PACKAGES set is used to shard DalvikTest into
82      * multiple DalvikTests, each responsible for running one of these packages' tests.
83      */
84     private static final Set<String> TEST_PACKAGES = new HashSet<>();
85     private static final String JDWP_PACKAGE_BASE = "org.apache.harmony.jpda.tests.jdwp.%s";
86     static {
87         // Though uppercase, these are package names, not class names
String.format(JDWP_PACKAGE_BASE, "ArrayReference")88         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ArrayReference"));
String.format(JDWP_PACKAGE_BASE, "ArrayType")89         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ArrayType"));
String.format(JDWP_PACKAGE_BASE, "ClassLoaderReference")90         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ClassLoaderReference"));
String.format(JDWP_PACKAGE_BASE, "ClassObjectReference")91         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ClassObjectReference"));
String.format(JDWP_PACKAGE_BASE, "ClassType")92         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ClassType"));
String.format(JDWP_PACKAGE_BASE, "DebuggerOnDemand")93         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "DebuggerOnDemand"));
String.format(JDWP_PACKAGE_BASE, "Deoptimization")94         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "Deoptimization"));
String.format(JDWP_PACKAGE_BASE, "EventModifiers")95         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "EventModifiers"));
String.format(JDWP_PACKAGE_BASE, "Events")96         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "Events"));
String.format(JDWP_PACKAGE_BASE, "InterfaceType")97         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "InterfaceType"));
String.format(JDWP_PACKAGE_BASE, "Method")98         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "Method"));
String.format(JDWP_PACKAGE_BASE, "MultiSession")99         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "MultiSession"));
String.format(JDWP_PACKAGE_BASE, "ObjectReference")100         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ObjectReference"));
String.format(JDWP_PACKAGE_BASE, "ReferenceType")101         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ReferenceType"));
String.format(JDWP_PACKAGE_BASE, "StackFrame")102         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "StackFrame"));
String.format(JDWP_PACKAGE_BASE, "StringReference")103         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "StringReference"));
String.format(JDWP_PACKAGE_BASE, "ThreadGroupReference")104         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ThreadGroupReference"));
String.format(JDWP_PACKAGE_BASE, "ThreadReference")105         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ThreadReference"));
String.format(JDWP_PACKAGE_BASE, "VirtualMachine")106         TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "VirtualMachine"));
107     }
108 
109     private static final String EXPECTATIONS_EXT = ".expectations";
110     // Command to run the VM, args are bitness, classpath, dalvik-args, abi, runner-args,
111     // include and exclude filters, and exclude filters file.
112     private static final String COMMAND = "dalvikvm%s -classpath %s %s "
113             + "com.android.compatibility.dalvik.DalvikTestRunner --abi=%s %s %s %s %s %s %s";
114     private static final String INCLUDE_FILE = "/data/local/tmp/dalvik/includes";
115     private static final String EXCLUDE_FILE = "/data/local/tmp/dalvik/excludes";
116     private static String START_RUN = "start-run";
117     private static String END_RUN = "end-run";
118     private static String START_TEST = "start-test";
119     private static String END_TEST = "end-test";
120     private static String FAILURE = "failure";
121 
122     // If we are running with adbconnection jdwp provider (hence a libjdwp agent).
123     private boolean mIsAdbConnection = true;
124 
125     @Option(name = "run-name", description = "The name to use when reporting results")
126     private String mRunName;
127 
128     @Option(name = "classpath", description = "Holds the paths to search when loading tests")
129     private List<String> mClasspath = new ArrayList<>();
130 
131     @Option(name = "dalvik-arg", description = "Holds arguments to pass to Dalvik")
132     private List<String> mDalvikArgs = new ArrayList<>();
133 
134     @Option(name = "dalvik-arg-adbconnection",
135             description = "Holds arguments to pass to Dalvik when " +
136                           "dalvik.vm.jdwp-provider == adbconnection or default or is empty")
137     private List<String> mDalvikArgsAdbconnection = new ArrayList<>();
138 
139     @Option(name = "dalvik-arg-internal",
140             description = "Holds arguments to pass to Dalvik only when " +
141                           "dalvik.vm.jdwp-provider == internal")
142     private List<String> mDalvikArgsInternal = new ArrayList<>();
143 
144     @Option(name = "runner-arg",
145             description = "Holds arguments to pass to the device-side test runner")
146     private List<String> mRunnerArgs = new ArrayList<>();
147 
148     @Option(name = "include-filter",
149             description = "The include filters of the test name to run.")
150     private Set<String> mIncludeFilters = new LinkedHashSet<>();
151 
152     @Option(name = "exclude-filter",
153             description = "The exclude filters of the test name to run.")
154     private Set<String> mExcludeFilters = new LinkedHashSet<>();
155 
156     @Option(name = "test-file-include-filter",
157             description="A file containing a list of line separated test classes and optionally"
158             + " methods to include")
159     private File mIncludeTestFile = null;
160 
161     @Option(name = "test-file-exclude-filter",
162             description="A file containing a list of line separated test classes and optionally"
163             + " methods to exclude")
164     private File mExcludeTestFile = null;
165 
166     @Option(name = "runtime-hint",
167             isTimeVal = true,
168             description="The hint about the test's runtime.")
169     private long mRuntimeHint = 60000;// 1 minute
170 
171     @Option(name = "known-failures-adbconnection",
172             description = "Comma-separated list of files specifying known-failures to be skipped")
173     private String mKnownFailuresAdbconnection;
174     @Option(name = "known-failures-internal",
175             description = "Comma-separated list of files specifying known-failures to be skipped")
176     private String mKnownFailuresInternal;
177 
178     @Option(name = "known-failures",
179             description = "Comma-separated list of files specifying known-failures to be skipped")
180     private String mKnownFailures;
181 
182     @Option(name = "collect-tests-only",
183             description = "Only invoke the instrumentation to collect list of applicable test "
184                     + "cases. All test run callbacks will be triggered, but test execution will "
185                     + "not be actually carried out.")
186     private boolean mCollectTestsOnly = false;
187 
188     @Option(name = "per-test-timeout",
189             description = "The maximum amount of time during which the DalvikTestRunner may "
190                     + "yield no output. Because the runner outputs results for each test, this "
191                     + "is essentially a per-test timeout")
192     private long mPerTestTimeout = 10; // 10 minutes
193 
194     private IAbi mAbi;
195     private CompatibilityBuildHelper mBuildHelper;
196     private ITestDevice mDevice;
197 
198     /**
199      * {@inheritDoc}
200      */
201     @Override
setAbi(IAbi abi)202     public void setAbi(IAbi abi) {
203         mAbi = abi;
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
getAbi()210     public IAbi getAbi() {
211         return mAbi;
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     @Override
setBuild(IBuildInfo build)218     public void setBuild(IBuildInfo build) {
219         mBuildHelper = new CompatibilityBuildHelper(build);
220     }
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
setDevice(ITestDevice device)226     public void setDevice(ITestDevice device) {
227         mDevice = device;
228     }
229 
230     /**
231      * {@inheritDoc}
232      */
233     @Override
getDevice()234     public ITestDevice getDevice() {
235         return mDevice;
236     }
237 
238     /**
239      * {@inheritDoc}
240      */
241     @Override
addIncludeFilter(String filter)242     public void addIncludeFilter(String filter) {
243         mIncludeFilters.add(filter);
244     }
245 
246     /**
247      * {@inheritDoc}
248      */
249     @Override
addAllIncludeFilters(Set<String> filters)250     public void addAllIncludeFilters(Set<String> filters) {
251         mIncludeFilters.addAll(filters);
252     }
253 
254     /**
255      * {@inheritDoc}
256      */
257     @Override
addExcludeFilter(String filter)258     public void addExcludeFilter(String filter) {
259         mExcludeFilters.add(filter);
260     }
261 
262     /**
263      * {@inheritDoc}
264      */
265     @Override
addAllExcludeFilters(Set<String> filters)266     public void addAllExcludeFilters(Set<String> filters) {
267         mExcludeFilters.addAll(filters);
268     }
269 
270     /**
271      * {@inheritDoc}
272      */
273     @Override
getIncludeFilters()274     public Set<String> getIncludeFilters() {
275         return mIncludeFilters;
276     }
277 
278     /**
279      * {@inheritDoc}
280      */
281     @Override
getExcludeFilters()282     public Set<String> getExcludeFilters() {
283         return mExcludeFilters;
284     }
285 
286     /**
287      * {@inheritDoc}
288      */
289     @Override
clearIncludeFilters()290     public void clearIncludeFilters() {
291         mIncludeFilters.clear();
292     }
293 
294     /**
295      * {@inheritDoc}
296      */
297     @Override
clearExcludeFilters()298     public void clearExcludeFilters() {
299         mExcludeFilters.clear();
300     }
301 
302     /**
303      * {@inheritDoc}
304      */
305     @Override
setIncludeTestFile(File testFile)306     public void setIncludeTestFile(File testFile) {
307         mIncludeTestFile = testFile;
308     }
309 
310     /**
311      * {@inheritDoc}
312      */
313     @Override
setExcludeTestFile(File testFile)314     public void setExcludeTestFile(File testFile) {
315         mExcludeTestFile = testFile;
316     }
317 
318     /**
319      * {@inheritDoc}
320      */
321     @Override
getRuntimeHint()322     public long getRuntimeHint() {
323         return mRuntimeHint;
324     }
325 
326     /**
327      * {@inheritDoc}
328      */
329     @Override
setCollectTestsOnly(boolean shouldCollectTest)330     public void setCollectTestsOnly(boolean shouldCollectTest) {
331         mCollectTestsOnly = shouldCollectTest;
332     }
333 
isAdbconnection(ITestDevice device)334     private static boolean isAdbconnection(ITestDevice device) throws DeviceNotAvailableException {
335         String provider = device.getProperty("dalvik.vm.jdwp-provider");
336         if (provider == null || provider.equals("default") || provider.equals("adbconnection")) {
337             return true;
338         } else if (provider.equals("internal")) {
339             return false;
340         } else {
341             throw new RuntimeException("Unknown dalvik.vm.jdwp-provider = " + provider);
342         }
343     }
344 
345     /**
346      * {@inheritDoc}
347      */
348     @Override
run(final TestInformation testInfo, final ITestInvocationListener listener)349     public void run(final TestInformation testInfo, final ITestInvocationListener listener) throws DeviceNotAvailableException {
350         String abiName = mAbi.getName();
351         String bitness = AbiUtils.getBitness(abiName);
352         mIsAdbConnection = isAdbconnection(getDevice());
353 
354         File tmpExcludeFile = null;
355         String excludeFile = "";
356         try {
357             // push one file of exclude filters to the device
358             tmpExcludeFile = getExcludeFile();
359             if (!mDevice.pushFile(tmpExcludeFile, EXCLUDE_FILE)) {
360                 Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + tmpExcludeFile);
361             } else {
362                 CLog.d("exclude-filter-file: %s", safeReadContentFromFile(tmpExcludeFile));
363                 // If sucessfully pushed then add it as a filter.
364                 excludeFile = String.format("--exclude-filter-file=%s", EXCLUDE_FILE);
365             }
366         } catch (IOException e) {
367             throw new RuntimeException("Failed to parse expectations", e);
368         } finally {
369             FileUtil.deleteFile(tmpExcludeFile);
370         }
371 
372         // push one file of include filters to the device, if file exists
373         String includeFile = "";
374         if (mIncludeTestFile != null) {
375             String path = mIncludeTestFile.getAbsolutePath();
376             if (!mIncludeTestFile.isFile() || !mIncludeTestFile.canRead()) {
377                 throw new RuntimeException(String.format("Failed to read include file %s", path));
378             }
379             if (!mDevice.pushFile(mIncludeTestFile, INCLUDE_FILE)) {
380                 Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + path);
381             } else {
382                 CLog.d("include-filter-file: %s", safeReadContentFromFile(mIncludeTestFile));
383                 // If sucessfully pushed then add it as a filter.
384                 includeFile = String.format("--include-filter-file=%s", INCLUDE_FILE);
385             }
386         }
387 
388         if (mIsAdbConnection) {
389             Log.logAndDisplay(LogLevel.INFO, TAG, "Running with ADBConnection/libjdwp agent");
390             mDalvikArgs.addAll(mDalvikArgsAdbconnection);
391         } else {
392             Log.logAndDisplay(LogLevel.INFO, TAG, "Running with internal jdwp implementation");
393             mDalvikArgs.addAll(mDalvikArgsInternal);
394         }
395 
396         // Create command
397         mDalvikArgs.add("-Duser.name=shell");
398         mDalvikArgs.add("-Duser.language=en");
399         mDalvikArgs.add("-Duser.region=US");
400         mDalvikArgs.add("-Xcheck:jni");
401         mDalvikArgs.add("-Xjnigreflimit:2000");
402 
403         String dalvikArgs = ArrayUtil.join(" ", mDalvikArgs);
404         dalvikArgs = dalvikArgs.replace("|#ABI#|", bitness);
405 
406         String runnerArgs = ArrayUtil.join(" ", mRunnerArgs);
407         // Filters
408         StringBuilder includeFilters = new StringBuilder();
409         if (!mIncludeFilters.isEmpty()) {
410             includeFilters.append("--include-filter=");
411             includeFilters.append(ArrayUtil.join(",", mIncludeFilters));
412         }
413         StringBuilder excludeFilters = new StringBuilder();
414         if (!mExcludeFilters.isEmpty()) {
415             excludeFilters.append("--exclude-filter=");
416             excludeFilters.append(ArrayUtil.join(",", mExcludeFilters));
417         }
418 
419         // Communicate with DalvikTestRunner if tests should only be collected
420         String collectTestsOnlyString = (mCollectTestsOnly) ? "--collect-tests-only" : "";
421         final String command = String.format(COMMAND, bitness,
422                 ArrayUtil.join(File.pathSeparator, mClasspath),
423                 dalvikArgs, abiName, runnerArgs,
424                 includeFilters, excludeFilters, includeFile, excludeFile, collectTestsOnlyString);
425         Log.logAndDisplay(LogLevel.INFO, TAG, command);
426         DalvikOutputParser receiver = new DalvikOutputParser(listener);
427         try {
428             mDevice.executeShellCommand(command, receiver, mPerTestTimeout, TimeUnit.MINUTES, 1);
429         } finally {
430             receiver.flush();
431             receiver.completeEvents();
432         }
433     }
434 
435     /*
436      * Due to known failures, there are typically too many excludes to pass via command line.
437      * Collect excludes from .expectation files in the testcases directory, from files in the
438      * module's resources directory, and from mExcludeTestFile, if set.
439      */
getExcludeFile()440     private File getExcludeFile() throws IOException {
441         File excludeFile = null;
442         PrintWriter out = null;
443 
444         try {
445             excludeFile = File.createTempFile("excludes", "txt");
446             out = new PrintWriter(excludeFile);
447             // create expectation store from set of expectation files found in testcases dir
448             Set<File> expectationFiles = new HashSet<>();
449             for (File f : mBuildHelper.getTestsDir().listFiles(
450                     new ExpectationFileFilter(mRunName))) {
451                 expectationFiles.add(f);
452             }
453             ExpectationStore testsDirStore =
454                     ExpectationStore.parse(expectationFiles, ModeId.DEVICE);
455             // create expectation store from expectation files found in module resources dir
456             ExpectationStore resourceStore = null;
457             Set<String> knownFailuresFiles = new HashSet<String>();
458             Splitter splitter = Splitter.on(',').trimResults();
459             if (mKnownFailures != null) {
460                 knownFailuresFiles.addAll(splitter.splitToList(mKnownFailures));
461             }
462             if (mIsAdbConnection && mKnownFailuresAdbconnection != null) {
463                 knownFailuresFiles.addAll(splitter.splitToList(mKnownFailuresAdbconnection));
464             }
465             if (!mIsAdbConnection && mKnownFailuresInternal != null) {
466                 knownFailuresFiles.addAll(splitter.splitToList(mKnownFailuresInternal));
467             }
468             if (knownFailuresFiles.size() != 0) {
469                 resourceStore = ExpectationStore.parseResources(
470                         getClass(), knownFailuresFiles, ModeId.DEVICE);
471             }
472             // Add expectations from testcases dir
473             for (String exclude : testsDirStore.getAllFailures().keySet()) {
474                 out.println(exclude);
475             }
476             for (String exclude : testsDirStore.getAllOutComes().keySet()) {
477                 out.println(exclude);
478             }
479             // Add expectations from resources dir
480             if (resourceStore != null) {
481                 for (String exclude : resourceStore.getAllFailures().keySet()) {
482                     out.println(exclude);
483                 }
484                 for (String exclude : resourceStore.getAllOutComes().keySet()) {
485                     out.println(exclude);
486                 }
487             }
488             // Add excludes from test-file-exclude-filter option
489             for (String exclude : getFiltersFromFile(mExcludeTestFile)) {
490                 out.println(exclude);
491             }
492             out.flush();
493         } finally {
494             if (out != null) {
495                 out.close();
496             }
497         }
498         return excludeFile;
499     }
500 
501 
502     /*
503      * Helper method that reads filters from a file into a set.
504      * Returns an empty set given a null file
505      */
getFiltersFromFile(File f)506     private static Set<String> getFiltersFromFile(File f) throws IOException {
507         Set<String> filters = new HashSet<String>();
508         if (f != null) {
509             BufferedReader reader = new BufferedReader(new FileReader(f));
510             String filter = null;
511             while ((filter = reader.readLine()) != null) {
512                 filters.add(filter);
513             }
514             reader.close();
515         }
516         return filters;
517     }
518 
519     /**
520      * {@inheritDoc}
521      */
522     @Override
split()523     public Collection<IRemoteTest> split() {
524         List<IRemoteTest> shards = new ArrayList<>();
525         // A DalvikTest to run any tests not contained in packages from TEST_PACKAGES, may be empty
526         DalvikTest catchAll = new DalvikTest();
527         OptionCopier.copyOptionsNoThrow(this, catchAll);
528         catchAll.mAbi = mAbi;
529         shards.add(catchAll);
530         // estimate catchAll's runtime to be that of a single package in TEST_PACKAGES
531         long runtimeHint = mRuntimeHint / TEST_PACKAGES.size();
532         catchAll.mRuntimeHint = runtimeHint;
533         for (String packageName: TEST_PACKAGES) {
534             catchAll.addExcludeFilter(packageName);
535             // create one shard for package 'packageName'
536             DalvikTest test = new DalvikTest();
537             OptionCopier.copyOptionsNoThrow(this, test);
538             test.addIncludeFilter(packageName);
539             test.mRuntimeHint = runtimeHint / TEST_PACKAGES.size();
540             test.mAbi = mAbi;
541             shards.add(test);
542         }
543         // return a shard for each package in TEST_PACKAGE, plus a shard for any other tests
544         return shards;
545     }
546 
547     /**
548      * A {@link FilenameFilter} to find all the expectation files in a directory.
549      */
550     public static class ExpectationFileFilter implements FilenameFilter {
551 
552         private String mName;
553 
ExpectationFileFilter(String name)554         public ExpectationFileFilter(String name) {
555             mName = name;
556         }
557         /**
558          * {@inheritDoc}
559          */
560         @Override
accept(File dir, String name)561         public boolean accept(File dir, String name) {
562             return name.startsWith(mName) && name.endsWith(EXPECTATIONS_EXT);
563         }
564     }
565 
safeReadContentFromFile(File file)566     private String safeReadContentFromFile(File file) {
567         try {
568             return FileUtil.readStringFromFile(file);
569         } catch (IOException e) {
570             CLog.e(e);
571             return null;
572         }
573     }
574 
575     private class DalvikOutputParser extends MultiLineReceiver {
576 
577         private final ITestInvocationListener mListener;
578         private TestDescription mTest;
579         private Long mStartTime;
580 
DalvikOutputParser(ITestInvocationListener listener)581         public DalvikOutputParser(ITestInvocationListener listener) {
582             this.mListener = listener;
583         }
584 
585         @Override
isCancelled()586         public boolean isCancelled() {
587             return false;
588         }
589 
590         @Override
processNewLines(String[] lines)591         public void processNewLines(String[] lines) {
592             for (String line : lines) {
593                 String[] parts = line.split(":", 2);
594                 String tag = parts[0];
595                 if (tag.equals(START_RUN)) {
596                     mListener.testRunStarted(mRunName, Integer.parseInt(parts[1]));
597                     mStartTime = System.currentTimeMillis();
598                 } else if (tag.equals(END_RUN)) {
599                     mListener.testRunEnded(Integer.parseInt(parts[1]),
600                             Collections.<String, String>emptyMap());
601                     mStartTime = null;
602                 } else if (tag.equals(START_TEST)) {
603                     mTest = getTestDescription(parts[1]);
604                     mListener.testStarted(mTest);
605                 } else if (tag.equals(FAILURE)) {
606                     mListener.testFailed(mTest, processSerializedValue(parts[1]));
607                 } else if (tag.equals(END_TEST)) {
608                     mListener.testEnded(getTestDescription(parts[1]),
609                             Collections.<String, String>emptyMap());
610                     mTest = null;
611                 }
612                 // Always log the output for debugging
613                 CLog.d(line);
614             }
615         }
616 
processSerializedValue(String input)617         private String processSerializedValue(String input) {
618             // Opposite of stringify.
619             return input.replace("^~^", "\n");
620         }
621 
getTestDescription(String name)622         private TestDescription getTestDescription(String name) {
623             String[] parts = name.split("#");
624             String className = parts[0];
625             String testName = "";
626             if (parts.length > 1) {
627                 testName = parts[1];
628             }
629             return new TestDescription(className, testName);
630         }
631 
632         /**
633          * In case some inconsistent events are left, close them for proper reporting.
634          */
completeEvents()635         private void completeEvents() {
636             if (mTest != null) {
637                 mListener.testFailed(mTest, "Test started but no end events received.");
638                 mListener.testEnded(mTest, new HashMap<String, Metric>());
639             }
640             if (mStartTime != null) {
641                 mListener.testRunFailed(
642                         FailureDescription.create("Dalvik test session did not properly terminate.").setFailureStatus(FailureStatus.TEST_FAILURE));
643                 mListener.testRunEnded(System.currentTimeMillis() - mStartTime, new HashMap<String, Metric>());
644             }
645         }
646     }
647 }
648