blob: d8e181c39daaae922e54042643e20a01d9596fb2 [file] [log] [blame]
Aaron Holden62198022016-04-18 16:37:28 -07001/*
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
17package com.android.compatibility.testtype;
18
19import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
20import com.android.compatibility.common.util.AbiUtils;
21import com.android.ddmlib.IShellOutputReceiver;
22import com.android.ddmlib.Log;
23import com.android.ddmlib.Log.LogLevel;
24import com.android.ddmlib.MultiLineReceiver;
25import com.android.ddmlib.testrunner.TestIdentifier;
26import com.android.tradefed.build.IBuildInfo;
27import com.android.tradefed.config.Option;
28import com.android.tradefed.config.OptionCopier;
29import com.android.tradefed.device.DeviceNotAvailableException;
30import com.android.tradefed.device.ITestDevice;
31import com.android.tradefed.result.ITestInvocationListener;
32import com.android.tradefed.testtype.IAbi;
33import com.android.tradefed.testtype.IAbiReceiver;
34import com.android.tradefed.testtype.IBuildReceiver;
35import com.android.tradefed.testtype.IDeviceTest;
36import com.android.tradefed.testtype.IRemoteTest;
37import com.android.tradefed.testtype.IRuntimeHintProvider;
38import com.android.tradefed.testtype.IShardableTest;
39import com.android.tradefed.testtype.ITestCollector;
Aaron Holden6d5d28c2016-06-03 12:15:25 -070040import com.android.tradefed.testtype.ITestFileFilterReceiver;
Aaron Holden62198022016-04-18 16:37:28 -070041import com.android.tradefed.testtype.ITestFilterReceiver;
42import com.android.tradefed.util.ArrayUtil;
43import com.android.tradefed.util.TimeVal;
44import com.google.common.base.Splitter;
45
46import vogar.ExpectationStore;
47import vogar.ModeId;
48
Aaron Holden6d5d28c2016-06-03 12:15:25 -070049import java.io.BufferedReader;
Aaron Holden62198022016-04-18 16:37:28 -070050import java.io.File;
51import java.io.FilenameFilter;
Aaron Holden6d5d28c2016-06-03 12:15:25 -070052import java.io.FileReader;
Aaron Holden62198022016-04-18 16:37:28 -070053import java.io.IOException;
54import java.io.PrintWriter;
55import java.util.ArrayList;
56import java.util.Arrays;
57import java.util.Collection;
58import java.util.Collections;
59import java.util.HashSet;
Aaron Holden62198022016-04-18 16:37:28 -070060import java.util.List;
61import java.util.Set;
62import java.util.concurrent.TimeUnit;
63
64/**
65 * A wrapper to run tests against Dalvik.
66 */
67public class DalvikTest implements IAbiReceiver, IBuildReceiver, IDeviceTest, IRemoteTest,
Aaron Holden6d5d28c2016-06-03 12:15:25 -070068 IRuntimeHintProvider, IShardableTest, ITestCollector, ITestFileFilterReceiver,
69 ITestFilterReceiver {
Aaron Holden62198022016-04-18 16:37:28 -070070
71 private static final String TAG = DalvikTest.class.getSimpleName();
72
73 /**
74 * TEST_PACKAGES is a Set containing the names of packages on the classpath known to contain
75 * tests to be run under DalvikTest. The TEST_PACKAGES set is used to shard DalvikTest into
76 * multiple DalvikTests, each responsible for running one of these packages' tests.
77 */
78 private static final Set<String> TEST_PACKAGES = new HashSet<>();
79 private static final String JDWP_PACKAGE_BASE = "org.apache.harmony.jpda.tests.jdwp.%s";
80 static {
81 // Though uppercase, these are package names, not class names
82 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ArrayReference"));
83 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ArrayType"));
84 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ClassLoaderReference"));
85 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ClassObjectReference"));
86 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ClassType"));
87 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "DebuggerOnDemand"));
88 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "Deoptimization"));
89 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "EventModifiers"));
90 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "Events"));
91 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "InterfaceType"));
92 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "Method"));
93 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "MultiSession"));
94 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ObjectReference"));
95 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ReferenceType"));
96 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "StackFrame"));
97 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "StringReference"));
98 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ThreadGroupReference"));
99 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "ThreadReference"));
100 TEST_PACKAGES.add(String.format(JDWP_PACKAGE_BASE, "VirtualMachine"));
101 }
102
103 private static final String EXPECTATIONS_EXT = ".expectations";
104 // Command to run the VM, args are bitness, classpath, dalvik-args, abi, runner-args,
105 // include and exclude filters, and exclude filters file.
106 private static final String COMMAND = "dalvikvm%s -classpath %s %s "
107 + "com.android.compatibility.dalvik.DalvikTestRunner --abi=%s %s %s %s %s %s %s";
108 private static final String INCLUDE_FILE = "/data/local/tmp/ctsjdwp/includes";
109 private static final String EXCLUDE_FILE = "/data/local/tmp/ctsjdwp/excludes";
110 private static String START_RUN = "start-run";
111 private static String END_RUN = "end-run";
112 private static String START_TEST = "start-test";
113 private static String END_TEST = "end-test";
114 private static String FAILURE = "failure";
115
116 @Option(name = "run-name", description = "The name to use when reporting results")
117 private String mRunName;
118
119 @Option(name = "classpath", description = "Holds the paths to search when loading tests")
120 private List<String> mClasspath = new ArrayList<>();
121
122 @Option(name = "dalvik-arg", description = "Holds arguments to pass to Dalvik")
123 private List<String> mDalvikArgs = new ArrayList<>();
124
125 @Option(name = "runner-arg",
126 description = "Holds arguments to pass to the device-side test runner")
127 private List<String> mRunnerArgs = new ArrayList<>();
128
129 @Option(name = "include-filter",
130 description = "The include filters of the test name to run.")
131 private List<String> mIncludeFilters = new ArrayList<>();
132
133 @Option(name = "exclude-filter",
134 description = "The exclude filters of the test name to run.")
135 private List<String> mExcludeFilters = new ArrayList<>();
136
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700137 @Option(name = "test-file-include-filter",
138 description="A file containing a list of line separated test classes and optionally"
139 + " methods to include")
140 private File mIncludeTestFile = null;
141
142 @Option(name = "test-file-exclude-filter",
143 description="A file containing a list of line separated test classes and optionally"
144 + " methods to exclude")
145 private File mExcludeTestFile = null;
146
Aaron Holden62198022016-04-18 16:37:28 -0700147 @Option(name = "runtime-hint",
148 isTimeVal = true,
149 description="The hint about the test's runtime.")
150 private long mRuntimeHint = 60000;// 1 minute
151
152 @Option(name = "known-failures",
153 description = "Comma-separated list of files specifying known-failures to be skipped")
154 private String mKnownFailures;
155
156 @Option(name = "collect-tests-only",
157 description = "Only invoke the instrumentation to collect list of applicable test "
158 + "cases. All test run callbacks will be triggered, but test execution will "
159 + "not be actually carried out.")
160 private boolean mCollectTestsOnly = false;
161
162 @Option(name = "per-test-timeout",
163 description = "The maximum amount of time during which the DalvikTestRunner may "
164 + "yield no output. Because the runner outputs results for each test, this "
165 + "is essentially a per-test timeout")
166 private long mPerTestTimeout = 10; // 10 minutes
167
168 private IAbi mAbi;
169 private CompatibilityBuildHelper mBuildHelper;
170 private ITestDevice mDevice;
171
172 /**
173 * {@inheritDoc}
174 */
175 @Override
176 public void setAbi(IAbi abi) {
177 mAbi = abi;
178 }
179
180 /**
181 * {@inheritDoc}
182 */
183 @Override
184 public void setBuild(IBuildInfo build) {
185 mBuildHelper = new CompatibilityBuildHelper(build);
186 }
187
188 /**
189 * {@inheritDoc}
190 */
191 @Override
192 public void setDevice(ITestDevice device) {
193 mDevice = device;
194 }
195
196 /**
197 * {@inheritDoc}
198 */
199 @Override
200 public ITestDevice getDevice() {
201 return mDevice;
202 }
203
204 /**
205 * {@inheritDoc}
206 */
207 @Override
208 public void addIncludeFilter(String filter) {
209 mIncludeFilters.add(filter);
210 }
211
212 /**
213 * {@inheritDoc}
214 */
215 @Override
216 public void addAllIncludeFilters(List<String> filters) {
217 mIncludeFilters.addAll(filters);
218 }
219
220 /**
221 * {@inheritDoc}
222 */
223 @Override
224 public void addExcludeFilter(String filter) {
225 mExcludeFilters.add(filter);
226 }
227
228 /**
229 * {@inheritDoc}
230 */
231 @Override
232 public void addAllExcludeFilters(List<String> filters) {
233 mExcludeFilters.addAll(filters);
234 }
235
236 /**
237 * {@inheritDoc}
238 */
239 @Override
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700240 public void setIncludeTestFile(File testFile) {
241 mIncludeTestFile = testFile;
242 }
243
244 /**
245 * {@inheritDoc}
246 */
247 @Override
248 public void setExcludeTestFile(File testFile) {
249 mExcludeTestFile = testFile;
250 }
251
252 /**
253 * {@inheritDoc}
254 */
255 @Override
Aaron Holden62198022016-04-18 16:37:28 -0700256 public long getRuntimeHint() {
257 return mRuntimeHint;
258 }
259
260 /**
261 * {@inheritDoc}
262 */
263 @Override
264 public void setCollectTestsOnly(boolean shouldCollectTest) {
265 mCollectTestsOnly = shouldCollectTest;
266 }
267
268 /**
269 * {@inheritDoc}
270 */
271 @Override
272 public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException {
273 String abiName = mAbi.getName();
274 String bitness = AbiUtils.getBitness(abiName);
275
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700276 File tmpExcludeFile = null;
Aaron Holden62198022016-04-18 16:37:28 -0700277 try {
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700278 // push one file of exclude filters to the device
279 tmpExcludeFile = getExcludeFile();
280 if (!mDevice.pushFile(tmpExcludeFile, EXCLUDE_FILE)) {
281 Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + tmpExcludeFile);
Aaron Holden62198022016-04-18 16:37:28 -0700282 }
283 } catch (IOException e) {
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700284 throw new RuntimeException("Failed to parse expectations", e);
Aaron Holden62198022016-04-18 16:37:28 -0700285 } finally {
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700286 if (tmpExcludeFile != null) {
287 tmpExcludeFile.delete();
Aaron Holden62198022016-04-18 16:37:28 -0700288 }
Aaron Holden62198022016-04-18 16:37:28 -0700289 }
290
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700291 // push one file of include filters to the device, if file exists
292 if (mIncludeTestFile != null) {
293 String path = mIncludeTestFile.getAbsolutePath();
294 if (!mIncludeTestFile.isFile() || !mIncludeTestFile.canRead()) {
295 throw new RuntimeException(String.format("Failed to read include file %s", path));
296 }
297 if (!mDevice.pushFile(mIncludeTestFile, INCLUDE_FILE)) {
298 Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + path);
299 }
300 }
301
302
Aaron Holden62198022016-04-18 16:37:28 -0700303 // Create command
304 mDalvikArgs.add("-Duser.name=shell");
305 mDalvikArgs.add("-Duser.language=en");
306 mDalvikArgs.add("-Duser.region=US");
307 mDalvikArgs.add("-Xcheck:jni");
308 mDalvikArgs.add("-Xjnigreflimit:2000");
309
310 String dalvikArgs = ArrayUtil.join(" ", mDalvikArgs);
311 dalvikArgs = dalvikArgs.replace("|#ABI#|", bitness);
312
313 String runnerArgs = ArrayUtil.join(" ", mRunnerArgs);
314 // Filters
315 StringBuilder includeFilters = new StringBuilder();
316 if (!mIncludeFilters.isEmpty()) {
317 includeFilters.append("--include-filter=");
318 includeFilters.append(ArrayUtil.join(",", mIncludeFilters));
319 }
320 StringBuilder excludeFilters = new StringBuilder();
321 if (!mExcludeFilters.isEmpty()) {
322 excludeFilters.append("--exclude-filter=");
323 excludeFilters.append(ArrayUtil.join(",", mExcludeFilters));
324 }
325 // Filter files
326 String includeFile = String.format("--include-filter-file=%s", INCLUDE_FILE);
327 String excludeFile = String.format("--exclude-filter-file=%s", EXCLUDE_FILE);
328 // Communicate with DalvikTestRunner if tests should only be collected
329 String collectTestsOnlyString = (mCollectTestsOnly) ? "--collect-tests-only" : "";
330 final String command = String.format(COMMAND, bitness,
331 ArrayUtil.join(File.pathSeparator, mClasspath),
332 dalvikArgs, abiName, runnerArgs,
333 includeFilters, excludeFilters, includeFile, excludeFile, collectTestsOnlyString);
334 IShellOutputReceiver receiver = new MultiLineReceiver() {
335 private TestIdentifier test;
336
337 @Override
338 public boolean isCancelled() {
339 return false;
340 }
341
342 @Override
343 public void processNewLines(String[] lines) {
344 for (String line : lines) {
345 String[] parts = line.split(":", 2);
346 String tag = parts[0];
347 if (tag.equals(START_RUN)) {
348 listener.testRunStarted(mRunName, Integer.parseInt(parts[1]));
349 Log.logAndDisplay(LogLevel.INFO, TAG, command);
350 Log.logAndDisplay(LogLevel.INFO, TAG, line);
351 } else if (tag.equals(END_RUN)) {
352 listener.testRunEnded(Integer.parseInt(parts[1]),
353 Collections.<String, String>emptyMap());
354 Log.logAndDisplay(LogLevel.INFO, TAG, line);
355 } else if (tag.equals(START_TEST)) {
356 test = getTestIdentifier(parts[1]);
357 listener.testStarted(test);
358 } else if (tag.equals(FAILURE)) {
359 listener.testFailed(test, parts[1]);
360 } else if (tag.equals(END_TEST)) {
361 listener.testEnded(getTestIdentifier(parts[1]),
362 Collections.<String, String>emptyMap());
363 } else {
364 Log.logAndDisplay(LogLevel.INFO, TAG, line);
365 }
366 }
367 }
368
369 private TestIdentifier getTestIdentifier(String name) {
370 String[] parts = name.split("#");
371 String className = parts[0];
372 String testName = "";
373 if (parts.length > 1) {
374 testName = parts[1];
375 }
376 return new TestIdentifier(className, testName);
377 }
378
379 };
380 mDevice.executeShellCommand(command, receiver, mPerTestTimeout, TimeUnit.MINUTES, 1);
381 }
382
Aaron Holden6d5d28c2016-06-03 12:15:25 -0700383 /*
384 * Due to known failures, there are typically too many excludes to pass via command line.
385 * Collect excludes from .expectation files in the testcases directory, from files in the
386 * module's resources directory, and from mExcludeTestFile, if set.
387 */
388 private File getExcludeFile() throws IOException {
389 File excludeFile = null;
390 PrintWriter out = null;
391
392 try {
393 excludeFile = File.createTempFile("excludes", "txt");
394 out = new PrintWriter(excludeFile);
395 // create expectation store from set of expectation files found in testcases dir
396 Set<File> expectationFiles = new HashSet<>();
397 for (File f : mBuildHelper.getTestsDir().listFiles(
398 new ExpectationFileFilter(mRunName))) {
399 expectationFiles.add(f);
400 }
401 ExpectationStore testsDirStore =
402 ExpectationStore.parse(expectationFiles, ModeId.DEVICE);
403 // create expectation store from expectation files found in module resources dir
404 ExpectationStore resourceStore = null;
405 if (mKnownFailures != null) {
406 Splitter splitter = Splitter.on(',').trimResults();
407 Set<String> knownFailuresFiles =
408 new HashSet<>(splitter.splitToList(mKnownFailures));
409 resourceStore = ExpectationStore.parseResources(
410 getClass(), knownFailuresFiles, ModeId.DEVICE);
411 }
412 // Add expectations from testcases dir
413 for (String exclude : testsDirStore.getAllFailures().keySet()) {
414 out.println(exclude);
415 }
416 for (String exclude : testsDirStore.getAllOutComes().keySet()) {
417 out.println(exclude);
418 }
419 // Add expectations from resources dir
420 if (resourceStore != null) {
421 for (String exclude : resourceStore.getAllFailures().keySet()) {
422 out.println(exclude);
423 }
424 for (String exclude : resourceStore.getAllOutComes().keySet()) {
425 out.println(exclude);
426 }
427 }
428 // Add excludes from test-file-exclude-filter option
429 for (String exclude : getFiltersFromFile(mExcludeTestFile)) {
430 out.println(exclude);
431 }
432 out.flush();
433 } finally {
434 if (out != null) {
435 out.close();
436 }
437 }
438 return excludeFile;
439 }
440
441
442 /*
443 * Helper method that reads filters from a file into a set.
444 * Returns an empty set given a null file
445 */
446 private static Set<String> getFiltersFromFile(File f) throws IOException {
447 Set<String> filters = new HashSet<String>();
448 if (f != null) {
449 BufferedReader reader = new BufferedReader(new FileReader(f));
450 String filter = null;
451 while ((filter = reader.readLine()) != null) {
452 filters.add(filter);
453 }
454 reader.close();
455 }
456 return filters;
457 }
458
Aaron Holden62198022016-04-18 16:37:28 -0700459 /**
460 * {@inheritDoc}
461 */
462 @Override
463 public Collection<IRemoteTest> split() {
464 List<IRemoteTest> shards = new ArrayList<>();
465 // A DalvikTest to run any tests not contained in packages from TEST_PACKAGES, may be empty
466 DalvikTest catchAll = new DalvikTest();
467 OptionCopier.copyOptionsNoThrow(this, catchAll);
468 shards.add(catchAll);
469 // estimate catchAll's runtime to be that of a single package in TEST_PACKAGES
470 long runtimeHint = mRuntimeHint / TEST_PACKAGES.size();
471 catchAll.mRuntimeHint = runtimeHint;
472 for (String packageName: TEST_PACKAGES) {
473 catchAll.addExcludeFilter(packageName);
474 // create one shard for package 'packageName'
475 DalvikTest test = new DalvikTest();
476 OptionCopier.copyOptionsNoThrow(this, test);
477 test.addIncludeFilter(packageName);
478 test.mRuntimeHint = runtimeHint;
479 shards.add(test);
480 }
481 // return a shard for each package in TEST_PACKAGE, plus a shard for any other tests
482 return shards;
483 }
484
485 /**
486 * A {@link FilenameFilter} to find all the expectation files in a directory.
487 */
488 public static class ExpectationFileFilter implements FilenameFilter {
489
490 private String mName;
491
492 public ExpectationFileFilter(String name) {
493 mName = name;
494 }
495 /**
496 * {@inheritDoc}
497 */
498 @Override
499 public boolean accept(File dir, String name) {
500 return name.startsWith(mName) && name.endsWith(EXPECTATIONS_EXT);
501 }
502 }
503}