blob: d9492f379e36d890cd6a10696568a0ee174b4f64 [file] [log] [blame]
/*
* Copyright (C) 2015 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 com.android.tools.idea.tests.gui.framework;
import com.android.tools.idea.gradle.project.GradleExperimentalSettings;
import com.android.tools.idea.sdk.IdeSdks;
import com.google.common.collect.Maps;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.Sdk;
import org.fest.reflect.reference.TypeRef;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Map;
import static org.fest.reflect.core.Reflection.method;
import static org.junit.Assert.assertNotNull;
/**
* Collects configuration information from a UI test method's {@link IdeGuiTest} and {@link IdeGuiTestSetup} annotations and applies it
* to the test before execution, using the the IDE's {@code ClassLoader} (which is the {@code ClassLoader} used by UI tests, to be able to
* access IDE's services, state and components.)
*/
class GuiTestConfigurator {
private static final String CLOSE_PROJECT_BEFORE_EXECUTION_KEY = "closeProjectBeforeExecution";
private static final String RETRY_COUNT_KEY = "retryCount";
private static final String RUN_WITH_MINIMUM_JDK_VERSION_KEY = "runWithMinimumJdkVersion";
private static final String SKIP_SOURCE_GENERATION_ON_SYNC_KEY = "skipSourceGenerationOnSync";
private static final String TAKE_SCREENSHOT_ON_TEST_FAILURE_KEY = "takeScreenshotOnTestFailure";
@NotNull private final String myTestName;
@NotNull private final Object myTest;
@NotNull private final ClassLoader myClassLoader;
@Nullable private final Object myMinimumJdkVersion;
private final boolean myCloseProjectBeforeExecution;
private final int myRetryCount;
private final boolean mySkipSourceGenerationOnSync;
private final boolean myTakeScreenshotOnTestFailure;
@NotNull
static GuiTestConfigurator createNew(@NotNull Method testMethod, @NotNull Object test) throws Throwable {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> target = classLoader.loadClass(GuiTestConfigurator.class.getCanonicalName());
Map<String, Object> testConfig = method("extractTestConfiguration").withReturnType(new TypeRef<Map<String, Object>>() {})
.withParameterTypes(Method.class)
.in(target)
.invoke(testMethod);
assertNotNull(testConfig);
return new GuiTestConfigurator(testConfig, testMethod.getName(), test, classLoader);
}
// Invoked using reflection and the IDE's ClassLoader.
@NotNull
private static Map<String, Object> extractTestConfiguration(@NotNull Method testMethod) {
Map<String, Object> config = Maps.newHashMap();
IdeGuiTest guiTest = testMethod.getAnnotation(IdeGuiTest.class);
if (guiTest != null) {
config.put(CLOSE_PROJECT_BEFORE_EXECUTION_KEY, guiTest.closeProjectBeforeExecution());
config.put(RUN_WITH_MINIMUM_JDK_VERSION_KEY, guiTest.runWithMinimumJdkVersion());
config.put(RETRY_COUNT_KEY, guiTest.retryCount());
}
IdeGuiTestSetup guiTestSetup = testMethod.getDeclaringClass().getAnnotation(IdeGuiTestSetup.class);
if (guiTestSetup != null) {
config.put(SKIP_SOURCE_GENERATION_ON_SYNC_KEY, guiTestSetup.skipSourceGenerationOnSync());
config.put(TAKE_SCREENSHOT_ON_TEST_FAILURE_KEY, guiTestSetup.takeScreenshotOnTestFailure());
}
return config;
}
private GuiTestConfigurator(@NotNull Map<String, Object> configuration,
@NotNull String testName,
@NotNull Object test,
@NotNull ClassLoader classLoader) {
myCloseProjectBeforeExecution = getBooleanValue(CLOSE_PROJECT_BEFORE_EXECUTION_KEY, configuration, true);
myMinimumJdkVersion = getValue(RUN_WITH_MINIMUM_JDK_VERSION_KEY, configuration, JavaSdkVersion.class);
myRetryCount = getIntValue(RETRY_COUNT_KEY, configuration, 0);
mySkipSourceGenerationOnSync = getBooleanValue(SKIP_SOURCE_GENERATION_ON_SYNC_KEY, configuration, false);
myTakeScreenshotOnTestFailure = getBooleanValue(TAKE_SCREENSHOT_ON_TEST_FAILURE_KEY, configuration, true);
myTestName = testName;
myTest = test;
myClassLoader = classLoader;
}
private static boolean getBooleanValue(@NotNull String key, @NotNull Map<String, Object> configuration, boolean defaultValue) {
Object value = configuration.get(key);
if (value instanceof Boolean) {
return ((Boolean)value);
}
return defaultValue;
}
@Nullable
private static Object getValue(@NotNull String key, @NotNull Map<String, Object> configuration, @NotNull Class<?> type) {
Object value = configuration.get(key);
if (value != null && value.getClass().getCanonicalName().equals(type.getCanonicalName())) {
return value;
}
return null;
}
private static int getIntValue(@NotNull String key, @NotNull Map<String, Object> configuration, int defaultValue) {
Object value = configuration.get(key);
if (value instanceof Integer) {
return ((Integer)value);
}
return defaultValue;
}
void executeSetupTasks() throws Throwable {
closeAllProjects();
skipSourceGenerationOnSync();
}
private void closeAllProjects() {
if (myCloseProjectBeforeExecution) {
method("closeAllProjects").in(myTest).invoke();
}
}
private void skipSourceGenerationOnSync() throws Throwable {
if (mySkipSourceGenerationOnSync) {
Class<?> target = loadMyClassWithTestClassLoader();
method("doSkipSourceGenerationOnSync").in(target).invoke();
}
}
// Invoked using reflection and the IDE's ClassLoader.
private static void doSkipSourceGenerationOnSync() {
System.out.println("Skipping source generation on project sync.");
GradleExperimentalSettings.getInstance().SKIP_SOURCE_GEN_ON_PROJECT_SYNC = true;
}
boolean shouldSkipTest() throws Throwable {
if (myMinimumJdkVersion != null) {
Class<?> target = loadMyClassWithTestClassLoader();
Class<?> javaSdkVersionClass = myClassLoader.loadClass(JavaSdkVersion.class.getCanonicalName());
Boolean hasRequiredJdk = method("hasRequiredJdk").withReturnType(boolean.class)
.withParameterTypes(javaSdkVersionClass)
.in(target)
.invoke(myMinimumJdkVersion);
assertNotNull(hasRequiredJdk);
if (!hasRequiredJdk) {
String jdkVersion = method("getDescription").withReturnType(String.class).in(myMinimumJdkVersion).invoke();
System.out.println(String.format("Skipping test '%1$s'. It needs JDK %2$s or newer.", myTestName, jdkVersion));
return true;
}
}
return false;
}
// Invoked using reflection and the IDE's ClassLoader.
private static boolean hasRequiredJdk(@NotNull JavaSdkVersion jdkVersion) {
Sdk jdk = IdeSdks.getJdk();
assertNotNull("Expecting to have a JDK", jdk);
JavaSdkVersion currentVersion = JavaSdk.getInstance().getVersion(jdk);
return currentVersion != null && currentVersion.isAtLeast(jdkVersion);
}
boolean shouldTakeScreenshotOnFailure() {
return myTakeScreenshotOnTestFailure;
}
int getRetryCount() {
return myRetryCount;
}
@NotNull
private Class<?> loadMyClassWithTestClassLoader() throws ClassNotFoundException {
return myClassLoader.loadClass(GuiTestConfigurator.class.getCanonicalName());
}
}