/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dalvik.runner; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** * Compiles, installs, runs and reports tests. */ final class Driver { private static final Logger logger = Logger.getLogger(Driver.class.getName()); private final File localTemp; private final Set<File> expectationFiles; private final List<CodeFinder> codeFinders; private final Mode mode; private final File xmlReportsDirectory; private final Map<String, ExpectedResult> expectedResults = new HashMap<String, ExpectedResult>(); /** * The number of tests that weren't run because they aren't supported by * this runner. */ private int unsupportedTests = 0; public Driver(File localTemp, Mode mode, Set<File> expectationFiles, File xmlReportsDirectory, List<CodeFinder> codeFinders) { this.localTemp = localTemp; this.expectationFiles = expectationFiles; this.mode = mode; this.xmlReportsDirectory = xmlReportsDirectory; this.codeFinders = codeFinders; } public void loadExpectations() throws IOException { for (File f : expectationFiles) { if (f.exists()) { expectedResults.putAll(ExpectedResult.parse(f)); } } } /** * Builds and executes all tests in the test directory. */ public void buildAndRunAllTests(Collection<File> testFiles) { new Mkdir().mkdirs(localTemp); Set<TestRun> tests = new LinkedHashSet<TestRun>(); for (File testFile : testFiles) { Set<TestRun> testsForFile = Collections.emptySet(); for (CodeFinder codeFinder : codeFinders) { testsForFile = codeFinder.findTests(testFile); // break as soon as we find any match. We don't need multiple // matches for the same file, since that would run it twice. if (!testsForFile.isEmpty()) { break; } } tests.addAll(testsForFile); } // compute TestRunner java and classpath to pass to mode.prepare Set<File> testRunnerJava = new HashSet<File>(); Classpath testRunnerClasspath = new Classpath(); for (final TestRun testRun : tests) { testRunnerJava.add(testRun.getRunnerJava()); testRunnerClasspath.addAll(testRun.getRunnerClasspath()); } // mode.prepare before mode.buildAndInstall to ensure test // runner is built. packaging of activity APK files needs the // test runner along with the test specific files. mode.prepare(testRunnerJava, testRunnerClasspath); logger.info("Running " + tests.size() + " tests."); // build and install tests in a background thread. Using lots of // threads helps for packages that contain many unsupported tests final BlockingQueue<TestRun> readyToRun = new ArrayBlockingQueue<TestRun>(4); ExecutorService builders = Threads.threadPerCpuExecutor(); int t = 0; for (final TestRun testRun : tests) { final int runIndex = t++; builders.submit(new Runnable() { public void run() { try { ExpectedResult expectedResult = lookupExpectedResult(testRun); testRun.setExpectedResult(expectedResult); if (expectedResult.getResult() == Result.UNSUPPORTED) { testRun.setResult(Result.UNSUPPORTED, Collections.<String>emptyList()); logger.fine("skipping test " + testRun + " because the expectations file says it is unsupported."); } else { mode.buildAndInstall(testRun); logger.fine("installed test " + runIndex + "; " + readyToRun.size() + " are ready to run"); } readyToRun.put(testRun); } catch (Throwable throwable) { testRun.setResult(Result.ERROR, throwable); } } }); } builders.shutdown(); List<TestRun> runs = new ArrayList<TestRun>(tests.size()); for (int i = 0; i < tests.size(); i++) { logger.fine("executing test " + i + "; " + readyToRun.size() + " are ready to run"); // if it takes 5 minutes for build and install, something is broken TestRun testRun; try { testRun = readyToRun.poll(5 * 60, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException("Unexpected interruption waiting for build and install", e); } if (testRun == null) { throw new IllegalStateException("Expected " + tests.size() + " tests but found only " + i); } runs.add(testRun); execute(testRun); mode.cleanup(testRun); } if (unsupportedTests > 0) { logger.info("Skipped " + unsupportedTests + " unsupported tests."); } if (xmlReportsDirectory != null) { logger.info("Printing XML Reports... "); int numFiles = new XmlReportPrinter().generateReports(xmlReportsDirectory, runs); logger.info(numFiles + " XML files written."); } } /** * Finds the expected result for the specified test run. This strips off * parts of the test's qualified name until it either finds a match or runs * out of name. */ private ExpectedResult lookupExpectedResult(TestRun testRun) { String name = testRun.getQualifiedName(); while (true) { ExpectedResult expectedResult = expectedResults.get(name); if (expectedResult != null) { return expectedResult; } int dot = name.lastIndexOf('.'); if (dot == -1) { return ExpectedResult.SUCCESS; } name = name.substring(0, dot); } } /** * Executes a single test and then prints the result. */ private void execute(TestRun testRun) { if (testRun.getResult() == Result.UNSUPPORTED) { logger.fine("skipping " + testRun.getQualifiedName()); unsupportedTests++; return; } if (testRun.isRunnable()) { mode.runTest(testRun); } printResult(testRun); } private void printResult(TestRun testRun) { if (testRun.isExpectedResult()) { logger.info("OK " + testRun.getQualifiedName() + " (" + testRun.getResult() + ")"); // In --verbose mode, show the output even on success. logger.fine(" " + testRun.getFailureMessage().replace("\n", "\n ")); return; } logger.info("FAIL " + testRun.getQualifiedName() + " (" + testRun.getResult() + ")"); String description = testRun.getDescription(); if (description != null) { logger.info(" \"" + description + "\""); } logger.info(" " + testRun.getFailureMessage().replace("\n", "\n ")); } }