Java tutorial
/* * Copyright 2000-2010 JetBrains s.r.o. * * 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 org.jetbrains.android.run.testing; import com.android.builder.model.ArtifactInfo; import com.android.ddmlib.AdbCommandRejectedException; import com.android.ddmlib.IDevice; import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.TimeoutException; import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; import com.android.tools.idea.gradle.IdeaAndroidProject; import com.google.common.base.Predicate; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.execution.*; import com.intellij.execution.configurations.*; import com.intellij.execution.junit.JUnitUtil; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; import com.intellij.execution.testframework.ui.BaseTestsOutputConsoleView; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiPackage; import org.jetbrains.android.dom.manifest.Instrumentation; import org.jetbrains.android.dom.manifest.Manifest; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidFacetConfiguration; import org.jetbrains.android.run.AndroidApplicationLauncher; import org.jetbrains.android.run.AndroidRunConfigurationBase; import org.jetbrains.android.run.AndroidRunConfigurationEditor; import org.jetbrains.android.run.AndroidRunningState; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; /** * User: Eugene.Kudelevsky * Date: Aug 27, 2009 * Time: 2:23:56 PM */ public class AndroidTestRunConfiguration extends AndroidRunConfigurationBase { private static final Logger LOG = Logger .getInstance("#org.jetbrains.android.run.testing.AndroidTestRunConfiguration"); public static final int TEST_ALL_IN_MODULE = 0; public static final int TEST_ALL_IN_PACKAGE = 1; public static final int TEST_CLASS = 2; public static final int TEST_METHOD = 3; public int TESTING_TYPE = TEST_ALL_IN_MODULE; public String INSTRUMENTATION_RUNNER_CLASS = ""; public String METHOD_NAME = ""; public String CLASS_NAME = ""; public String PACKAGE_NAME = ""; public AndroidTestRunConfiguration(final Project project, final ConfigurationFactory factory) { super(project, factory); } @Override protected Pair<Boolean, String> supportsRunningLibraryProjects(@NotNull AndroidFacet facet) { if (!facet.isGradleProject()) { // Non Gradle projects always require an application return new Pair<Boolean, String>(Boolean.FALSE, AndroidBundle.message("android.cannot.run.library.project.error")); } final IdeaAndroidProject project = facet.getIdeaAndroidProject(); if (project == null) { return new Pair<Boolean, String>(Boolean.FALSE, AndroidBundle.message("android.cannot.run.library.project.error")); } // Gradle only supports testing against a single build type (which could be anything, but is "debug" build type by default) // Currently, the only information the model exports that we can use to detect whether the current build type // is testable is by looking at the test task name and checking whether it is null. ArtifactInfo testArtifactInfo = project.getSelectedVariant().getTestArtifactInfo(); String testTask = testArtifactInfo != null ? testArtifactInfo.getAssembleTaskName() : null; return new Pair<Boolean, String>(testTask != null, AndroidBundle.message("android.cannot.run.library.project.in.this.buildtype")); } @Override public boolean isGeneratedName() { final String name = getName(); if ((TESTING_TYPE == TEST_CLASS || TESTING_TYPE == TEST_METHOD) && (CLASS_NAME == null || CLASS_NAME.length() == 0)) { return JavaExecutionUtil.isNewName(name); } if (TESTING_TYPE == TEST_METHOD && (METHOD_NAME == null || METHOD_NAME.length() == 0)) { return JavaExecutionUtil.isNewName(name); } return Comparing.equal(name, getGeneratedName()); } @Nullable @Override public String getGeneratedName() { final JavaRunConfigurationModule confModule = getConfigurationModule(); final String moduleName = confModule.getModuleName(); if (TESTING_TYPE == TEST_ALL_IN_PACKAGE) { if (PACKAGE_NAME.length() == 0) { return ExecutionBundle.message("default.junit.config.name.all.in.module", moduleName); } if (moduleName.length() > 0) { return ExecutionBundle.message("default.junit.config.name.all.in.package.in.module", PACKAGE_NAME, moduleName); } return PACKAGE_NAME + " in " + moduleName; } else if (TESTING_TYPE == TEST_CLASS) { return JavaExecutionUtil.getPresentableClassName(CLASS_NAME, confModule); } else if (TESTING_TYPE == TEST_METHOD) { return JavaExecutionUtil.getPresentableClassName(CLASS_NAME, confModule) + "." + METHOD_NAME; } return moduleName; } @Override public String suggestedName() { if (TESTING_TYPE == TEST_ALL_IN_PACKAGE) { return isGeneratedName() ? ExecutionBundle.message("test.in.scope.presentable.text", PACKAGE_NAME) : "'" + getName() + "'"; } else if (TESTING_TYPE == TEST_CLASS) { return ProgramRunnerUtil.shortenName(JavaExecutionUtil.getShortClassName(CLASS_NAME), 0); } else if (TESTING_TYPE == TEST_METHOD) { return ProgramRunnerUtil.shortenName(METHOD_NAME, 2) + "()"; } return ExecutionBundle.message("all.tests.scope.presentable.text"); } @Override public void checkConfiguration(@NotNull AndroidFacet facet) throws RuntimeConfigurationException { Module module = facet.getModule(); JavaPsiFacade facade = JavaPsiFacade.getInstance(module.getProject()); switch (TESTING_TYPE) { case TEST_ALL_IN_PACKAGE: final PsiPackage testPackage = facade.findPackage(PACKAGE_NAME); if (testPackage == null) { throw new RuntimeConfigurationWarning( ExecutionBundle.message("package.does.not.exist.error.message", PACKAGE_NAME)); } break; case TEST_CLASS: final PsiClass testClass = getConfigurationModule().checkModuleAndClassName(CLASS_NAME, ExecutionBundle.message("no.test.class.specified.error.text")); if (!JUnitUtil.isTestClass(testClass)) { throw new RuntimeConfigurationWarning( ExecutionBundle.message("class.isnt.test.class.error.message", CLASS_NAME)); } break; case TEST_METHOD: checkTestMethod(); break; } if (INSTRUMENTATION_RUNNER_CLASS.length() > 0) { if (facade.findClass(INSTRUMENTATION_RUNNER_CLASS, module.getModuleWithDependenciesAndLibrariesScope(true)) == null) { throw new RuntimeConfigurationError( AndroidBundle.message("instrumentation.runner.class.not.specified.error")); } } } @Override public AndroidRunningState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException { final AndroidRunningState state = super.getState(executor, env); if (state == null) { return null; } final AndroidFacet facet = state.getFacet(); final AndroidFacetConfiguration configuration = facet.getConfiguration(); if (!facet.isGradleProject() && !configuration.getState().PACK_TEST_CODE) { final Module module = facet.getModule(); final int count = getTestSourceRootCount(module); if (count > 0) { final String message = "Code and resources under test source " + (count > 1 ? "roots" : "root") + " aren't included into debug APK.\nWould you like to include them and recompile " + module.getName() + " module?" + "\n(You may change this option in Android facet settings later)"; final int result = Messages.showYesNoCancelDialog(getProject(), message, "Test code not included into APK", Messages.getQuestionIcon()); if (result == Messages.YES) { configuration.getState().PACK_TEST_CODE = true; } else if (result == Messages.CANCEL) { return null; } } } return state; } private static int getTestSourceRootCount(@NotNull Module module) { final ModuleRootManager manager = ModuleRootManager.getInstance(module); return manager.getSourceRoots(true).length - manager.getSourceRoots(false).length; } private void checkTestMethod() throws RuntimeConfigurationException { JavaRunConfigurationModule configurationModule = getConfigurationModule(); final PsiClass testClass = configurationModule.checkModuleAndClassName(CLASS_NAME, ExecutionBundle.message("no.test.class.specified.error.text")); if (!JUnitUtil.isTestClass(testClass)) { throw new RuntimeConfigurationWarning( ExecutionBundle.message("class.isnt.test.class.error.message", CLASS_NAME)); } if (METHOD_NAME == null || METHOD_NAME.trim().length() == 0) { throw new RuntimeConfigurationError(ExecutionBundle.message("method.name.not.specified.error.message")); } final JUnitUtil.TestMethodFilter filter = new JUnitUtil.TestMethodFilter(testClass); boolean found = false; boolean testAnnotated = false; for (final PsiMethod method : testClass.findMethodsByName(METHOD_NAME, true)) { if (filter.value(method)) found = true; if (JUnitUtil.isTestAnnotated(method)) testAnnotated = true; } if (!found) { throw new RuntimeConfigurationWarning( ExecutionBundle.message("test.method.doesnt.exist.error.message", METHOD_NAME)); } if (!AnnotationUtil.isAnnotated(testClass, JUnitUtil.RUN_WITH, true) && !testAnnotated) { try { final PsiClass testCaseClass = JUnitUtil.getTestCaseClass(configurationModule.getModule()); if (!testClass.isInheritor(testCaseClass, true)) { throw new RuntimeConfigurationError( ExecutionBundle.message("class.isnt.inheritor.of.testcase.error.message", CLASS_NAME)); } } catch (JUnitUtil.NoJUnitException e) { throw new RuntimeConfigurationWarning( ExecutionBundle.message(AndroidBundle.message("cannot.find.testcase.error"))); } } } @NotNull @Override public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() { Project project = getProject(); AndroidRunConfigurationEditor<AndroidTestRunConfiguration> editor = new AndroidRunConfigurationEditor<AndroidTestRunConfiguration>( project, new Predicate<AndroidFacet>() { @Override public boolean apply(@Nullable AndroidFacet facet) { return facet != null && supportsRunningLibraryProjects(facet).getFirst(); } }); editor.setConfigurationSpecificEditor(new TestRunParameters(project, editor.getModuleSelector())); return editor; } @NotNull @Override protected ConsoleView attachConsole(AndroidRunningState state, Executor executor) throws ExecutionException { final AndroidTestConsoleProperties properties = new AndroidTestConsoleProperties(this, executor); //TODO[for android guys]: attach stactrace filter for "Open Source at Exception" feature: // properties.addStackTraceFilter(new ...); BaseTestsOutputConsoleView consoleView = SMTestRunnerConnectionUtil.createAndAttachConsole("Android", state.getProcessHandler(), properties, state.getEnvironment()); Disposer.register(state.getFacet().getModule().getProject(), consoleView); return consoleView; } @Override protected boolean supportMultipleDevices() { return false; } @Override protected AndroidApplicationLauncher getApplicationLauncher(AndroidFacet facet) { String runner = INSTRUMENTATION_RUNNER_CLASS.length() > 0 ? INSTRUMENTATION_RUNNER_CLASS : getRunnerFromManifest(facet); return new MyApplicationLauncher(runner); } @Nullable private static String getRunnerFromManifest(AndroidFacet facet) { Manifest manifest = facet.getManifest(); if (manifest != null) { for (Instrumentation instrumentation : manifest.getInstrumentations()) { if (instrumentation != null) { PsiClass instrumentationClass = instrumentation.getInstrumentationClass().getValue(); if (instrumentationClass != null) { return instrumentationClass.getQualifiedName(); } } } } return null; } private class MyApplicationLauncher extends AndroidApplicationLauncher { private final String myInstrumentationTestRunner; private MyApplicationLauncher(String instrumentationTestRunner) { this.myInstrumentationTestRunner = instrumentationTestRunner; } @Override public LaunchResult launch(@NotNull AndroidRunningState state, @NotNull IDevice device) throws IOException, AdbCommandRejectedException, TimeoutException { state.getProcessHandler().notifyTextAvailable("Running tests\n", ProcessOutputTypes.STDOUT); RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(state.getTestPackageName(), myInstrumentationTestRunner, device); switch (TESTING_TYPE) { case TEST_ALL_IN_PACKAGE: runner.setTestPackageName(PACKAGE_NAME); break; case TEST_CLASS: runner.setClassName(CLASS_NAME); break; case TEST_METHOD: runner.setMethodName(CLASS_NAME, METHOD_NAME); break; } runner.setDebug(state.isDebugMode()); try { runner.run(new AndroidTestListener(state)); } catch (ShellCommandUnresponsiveException e) { LOG.info(e); state.getProcessHandler().notifyTextAvailable("Error: time out", ProcessOutputTypes.STDERR); } return LaunchResult.SUCCESS; } } }