blob: d31f014e8ab4d4c5f11a3acce712c1a899fda905 [file] [log] [blame]
/*
* 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 vogar.target;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import vogar.Result;
import vogar.RunnerType;
import vogar.TestProperties;
import vogar.monitor.TargetMonitor;
import vogar.target.junit.JUnitRunnerFactory;
/**
* Runs an action, in process on the target.
*/
public final class TestRunner {
private final String qualifiedClassOrPackageName;
/** the monitor port if a monitor is expected, or null for no monitor */
@VisibleForTesting final Integer monitorPort;
/** use an atomic reference so the runner can null it out when it is encountered. */
private final AtomicReference<String> skipPastReference;
private final int timeoutSeconds;
private final RunnerFactory runnerFactory;
private final boolean profile;
private final int profileDepth;
private final int profileInterval;
private final File profileFile;
private final boolean profileThreadGroup;
private final String[] args;
private boolean useSocketMonitor;
public TestRunner(Properties properties, List<String> argsList) {
qualifiedClassOrPackageName = properties.getProperty(TestProperties.TEST_CLASS_OR_PACKAGE);
timeoutSeconds = Integer.parseInt(properties.getProperty(TestProperties.TIMEOUT));
int monitorPort = Integer.parseInt(properties.getProperty(TestProperties.MONITOR_PORT));
String skipPast = null;
boolean profile = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE));
int profileDepth = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_DEPTH));
int profileInterval
= Integer.parseInt(properties.getProperty(TestProperties.PROFILE_INTERVAL));
File profileFile = new File(properties.getProperty(TestProperties.PROFILE_FILE));
boolean profileThreadGroup
= Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE_THREAD_GROUP));
for (Iterator<String> i = argsList.iterator(); i.hasNext(); ) {
String arg = i.next();
if (arg.equals("--monitorPort")) {
i.remove();
monitorPort = Integer.parseInt(i.next());
i.remove();
}
if (arg.equals("--skipPast")) {
i.remove();
skipPast = i.next();
i.remove();
}
}
// Select the RunnerFactory instances to use based on the selected runner type.
RunnerType runnerType =
RunnerType.valueOf(properties.getProperty(TestProperties.RUNNER_TYPE));
List<RunnerFactory> runnerFactories = new ArrayList<>();
if (runnerType.supportsCaliper()) {
runnerFactories.add(new CaliperRunnerFactory(argsList));
}
if (runnerType.supportsJUnit()) {
runnerFactories.add(new JUnitRunnerFactory());
}
if (runnerType.supportsMain()) {
runnerFactories.add(new MainRunnerFactory());
}
runnerFactory = new CompositeRunnerFactory(runnerFactories);
this.monitorPort = monitorPort;
this.skipPastReference = new AtomicReference<>(skipPast);
this.profile = profile;
this.profileDepth = profileDepth;
this.profileInterval = profileInterval;
this.profileFile = profileFile;
this.profileThreadGroup = profileThreadGroup;
this.args = argsList.toArray(new String[argsList.size()]);
}
/**
* Load the properties that were either encapsulated in the APK (if using
* {@link vogar.android.ActivityMode}), or encapsulated in the JAR compiled by Vogar (in other
* modes).
*
* @return The {@link Properties} that were loaded.
*/
public static Properties loadProperties() {
try {
InputStream in = getPropertiesStream();
Properties properties = new Properties();
properties.load(in);
in.close();
return properties;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Configure this test runner to await an incoming socket connection when
* writing test results. Otherwise all communication happens over
* System.out.
*/
public void useSocketMonitor() {
this.useSocketMonitor = true;
}
/**
* Attempt to load the test properties file from both the application and system classloader.
* This is necessary because sometimes we run tests from the boot classpath.
*/
private static InputStream getPropertiesStream() throws IOException {
for (Class<?> classToLoadFrom : new Class<?>[] { TestRunner.class, Object.class }) {
InputStream propertiesStream = classToLoadFrom.getResourceAsStream(
"/" + TestProperties.FILE);
if (propertiesStream != null) {
return propertiesStream;
}
}
throw new IOException(TestProperties.FILE + " missing!");
}
public void run() throws IOException {
final TargetMonitor monitor = useSocketMonitor
? TargetMonitor.await(monitorPort)
: TargetMonitor.forPrintStream(System.out);
PrintStream monitorPrintStream = new PrintStreamDecorator(System.out) {
@Override public void print(String str) {
monitor.output(str != null ? str : "null");
}
};
System.setOut(monitorPrintStream);
System.setErr(monitorPrintStream);
try {
run(monitor);
} catch (Throwable internalError) {
internalError.printStackTrace(monitorPrintStream);
} finally {
monitor.close();
}
}
private void run(final TargetMonitor monitor) {
TestEnvironment testEnvironment = new TestEnvironment();
testEnvironment.reset();
String classOrPackageName;
String qualification;
// Check whether the class or package is qualified and, if so, strip it off and pass it
// separately to the runners. For instance, may qualify a junit class by appending
// #method_name, where method_name is the name of a single test of the class to run.
int hash_position = qualifiedClassOrPackageName.indexOf("#");
if (hash_position != -1) {
classOrPackageName = qualifiedClassOrPackageName.substring(0, hash_position);
qualification = qualifiedClassOrPackageName.substring(hash_position + 1);
} else {
classOrPackageName = qualifiedClassOrPackageName;
qualification = null;
}
Set<Class<?>> classes = new ClassFinder().find(classOrPackageName);
// if there is more than one class in the set, this must be a package. Since we're
// running everything in the package already, remove any class called AllTests.
if (classes.size() > 1) {
Set<Class<?>> toRemove = new HashSet<>();
for (Class<?> klass : classes) {
if (klass.getName().endsWith(".AllTests")) {
toRemove.add(klass);
}
}
classes.removeAll(toRemove);
}
Profiler profiler = null;
if (profile) {
try {
profiler = Profiler.getInstance();
} catch (Exception e) {
System.out.println("Profiling is disabled: " + e);
}
}
if (profiler != null) {
profiler.setup(profileThreadGroup, profileDepth, profileInterval);
}
for (Class<?> klass : classes) {
TargetRunner targetRunner;
try {
targetRunner = runnerFactory.newRunner(monitor, qualification, klass,
skipPastReference, testEnvironment, timeoutSeconds, profile, args);
} catch (RuntimeException e) {
monitor.outcomeStarted(klass.getName());
e.printStackTrace();
monitor.outcomeFinished(Result.ERROR);
return;
}
if (targetRunner == null) {
monitor.outcomeStarted(klass.getName());
System.out.println("Skipping " + klass.getName()
+ ": no associated runner class");
monitor.outcomeFinished(Result.UNSUPPORTED);
continue;
}
boolean completedNormally = targetRunner.run(profiler);
if (!completedNormally) {
return; // let the caller start another process
}
}
if (profiler != null) {
profiler.shutdown(profileFile);
}
monitor.completedNormally(true);
}
public static void main(String[] args) throws IOException {
new TestRunner(loadProperties(), new ArrayList<>(Arrays.asList(args))).run();
System.exit(0);
}
/**
* A {@link RunnerFactory} that will traverse a list of {@link RunnerFactory} instances to find
* one that can be used to run the code.
*/
private static class CompositeRunnerFactory implements RunnerFactory {
private final List<? extends RunnerFactory> runnerFactories;
private CompositeRunnerFactory(List<RunnerFactory> factories) {
this.runnerFactories = factories;
}
@Override @Nullable
public TargetRunner newRunner(TargetMonitor monitor, String qualification,
Class<?> klass, AtomicReference<String> skipPastReference,
TestEnvironment testEnvironment, int timeoutSeconds, boolean profile, String[] args) {
for (RunnerFactory runnerFactory : runnerFactories) {
TargetRunner targetRunner = runnerFactory.newRunner(monitor, qualification, klass,
skipPastReference, testEnvironment, timeoutSeconds, profile, args);
if (targetRunner != null) {
return targetRunner;
}
}
return null;
}
}
}