/* * Copyright 2000-2009 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.kotlin.android; import com.android.SdkConstants; import com.android.tools.idea.rendering.RenderSecurityManager; import com.android.tools.idea.startup.AndroidCodeStyleSettingsModifier; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInspection.GlobalInspectionTool; import com.intellij.codeInspection.InspectionManager; import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; import com.intellij.codeInspection.ex.InspectionManagerEx; import com.intellij.facet.FacetManager; import com.intellij.facet.ModifiableFacetModel; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.impl.ComponentManagerImpl; import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.codeStyle.CodeStyleSchemes; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.testFramework.InspectionTestUtil; import com.intellij.testFramework.ThreadTracker; import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; import com.intellij.testFramework.fixtures.JavaTestFixtureFactory; import com.intellij.testFramework.fixtures.TestFixtureBuilder; import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; import com.intellij.testFramework.fixtures.impl.GlobalInspectionContextForTests; import com.intellij.util.ArrayUtil; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.formatter.AndroidXmlCodeStyleSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.picocontainer.MutablePicoContainer; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Copied from AS 2.3 sources */ @SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"}) public abstract class AndroidTestCase extends AndroidTestBase { protected Module myModule; protected List<Module> myAdditionalModules; protected AndroidFacet myFacet; protected CodeStyleSettings mySettings; private List<String> myAllowedRoots = new ArrayList<>(); private boolean myUseCustomSettings; @Override protected void setUp() throws Exception { super.setUp(); TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName()); myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture()); JavaModuleFixtureBuilder moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class); File moduleRoot = new File(myFixture.getTempDirPath()); if (!moduleRoot.exists()) { assertTrue(moduleRoot.mkdirs()); } initializeModuleFixtureBuilderWithSrcAndGen(moduleFixtureBuilder, moduleRoot.toString()); ArrayList<MyAdditionalModuleData> modules = new ArrayList<>(); configureAdditionalModules(projectBuilder, modules); myFixture.setUp(); myFixture.setTestDataPath(getTestDataPath()); myModule = moduleFixtureBuilder.getFixture().getModule(); // Must be done before addAndroidFacet, and must always be done, even if a test provides // its own custom manifest file. However, in that case, we will delete it shortly below. createManifest(); myFacet = addAndroidFacet(myModule); LanguageLevel languageLevel = getLanguageLevel(); if (languageLevel != null) { LanguageLevelProjectExtension extension = LanguageLevelProjectExtension.getInstance(myModule.getProject()); if (extension != null) { extension.setLanguageLevel(languageLevel); } } // TODO: myFixture.copyDirectoryToProject(getResDir(), "res"); myAdditionalModules = new ArrayList<>(); for (MyAdditionalModuleData data : modules) { Module additionalModule = data.myModuleFixtureBuilder.getFixture().getModule(); myAdditionalModules.add(additionalModule); AndroidFacet facet = addAndroidFacet(additionalModule); facet.setLibraryProject(data.myProjectType == 1); String rootPath = getAdditionalModulePath(data.myDirName); myFixture.copyDirectoryToProject(getResDir(), rootPath + "/res"); myFixture.copyFileToProject(SdkConstants.FN_ANDROID_MANIFEST_XML, rootPath + '/' + SdkConstants.FN_ANDROID_MANIFEST_XML); if (data.myIsMainModuleDependency) { ModuleRootModificationUtil.addDependency(myModule, additionalModule); } } if (providesCustomManifest()) { deleteManifest(); } if (RenderSecurityManager.RESTRICT_READS) { // Unit test class loader includes disk directories which security manager does not allow access to RenderSecurityManager.sEnabled = false; } ArrayList<String> allowedRoots = new ArrayList<>(); collectAllowedRoots(allowedRoots); // TODO: registerAllowedRoots(allowedRoots, myTestRootDisposable); mySettings = CodeStyleSettingsManager.getSettings(getProject()).clone(); AndroidCodeStyleSettingsModifier.modify(mySettings); CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(mySettings); myUseCustomSettings = getAndroidCodeStyleSettings().USE_CUSTOM_SETTINGS; getAndroidCodeStyleSettings().USE_CUSTOM_SETTINGS = true; // Layoutlib rendering thread will be shutdown when the app is closed so do not report it as a leak ThreadTracker.longRunningThreadCreated(ApplicationManager.getApplication(), "Layoutlib"); } @Override protected void tearDown() throws Exception { try { CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); myModule = null; myAdditionalModules = null; myFixture.tearDown(); myFixture = null; myFacet = null; getAndroidCodeStyleSettings().USE_CUSTOM_SETTINGS = myUseCustomSettings; if (RenderSecurityManager.RESTRICT_READS) { RenderSecurityManager.sEnabled = true; } } finally { super.tearDown(); } } private static void initializeModuleFixtureBuilderWithSrcAndGen(JavaModuleFixtureBuilder moduleFixtureBuilder, String moduleRoot) { moduleFixtureBuilder.addContentRoot(moduleRoot); //noinspection ResultOfMethodCallIgnored new File(moduleRoot + "/src/").mkdir(); moduleFixtureBuilder.addSourceRoot("src"); //noinspection ResultOfMethodCallIgnored new File(moduleRoot + "/gen/").mkdir(); moduleFixtureBuilder.addSourceRoot("gen"); } /** * Returns the path that any additional modules registered by * {@link #configureAdditionalModules(TestFixtureBuilder, List)} or * {@link #addModuleWithAndroidFacet(TestFixtureBuilder, List, String, int, boolean)} are * installed into. */ protected static String getAdditionalModulePath(@NotNull String moduleName) { return "/additionalModules/" + moduleName; } /** * Indicates whether this class provides its own {@code AndroidManifest.xml} for its tests. If * {@code true}, then {@link #setUp()} calls {@link #deleteManifest()} before finishing. */ protected boolean providesCustomManifest() { return false; } /** * Get the "res" directory for this SDK. Children classes can override this if they need to * provide a custom "res" location for tests. */ protected String getResDir() { return "res"; } /** * Defines the project level to set for the test project, or null to get the default language * level associated with the test project. */ @Nullable protected LanguageLevel getLanguageLevel() { return null; } protected static AndroidXmlCodeStyleSettings getAndroidCodeStyleSettings() { return AndroidXmlCodeStyleSettings.getInstance(CodeStyleSchemes.getInstance().getDefaultScheme().getCodeStyleSettings()); } /** * Hook point for child test classes to register directories that can be safely accessed by all * of its tests. * * @see {@link VfsRootAccess} */ protected void collectAllowedRoots(List<String> roots) throws IOException { } private void registerAllowedRoots(List<String> roots, @NotNull Disposable disposable) { List<String> newRoots = new ArrayList<>(roots); newRoots.removeAll(myAllowedRoots); String[] newRootsArray = ArrayUtil.toStringArray(newRoots); VfsRootAccess.allowRootAccess(newRootsArray); myAllowedRoots.addAll(newRoots); Disposer.register(disposable, () -> { VfsRootAccess.disallowRootAccess(newRootsArray); myAllowedRoots.removeAll(newRoots); }); } public static AndroidFacet addAndroidFacet(Module module) { return addAndroidFacet(module, true); } private static AndroidFacet addAndroidFacet(Module module, boolean attachSdk) { FacetManager facetManager = FacetManager.getInstance(module); AndroidFacet facet = facetManager.createFacet(AndroidFacet.getFacetType(), "Android", null); if (attachSdk) { addLatestAndroidSdk(module); } ModifiableFacetModel facetModel = facetManager.createModifiableModel(); facetModel.addFacet(facet); ApplicationManager.getApplication().runWriteAction(facetModel::commit); return facet; } protected void configureAdditionalModules( @NotNull TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder, @NotNull List<MyAdditionalModuleData> modules) { } protected final void addModuleWithAndroidFacet( @NotNull TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder, @NotNull List<MyAdditionalModuleData> modules, @NotNull String dirName, int projectType) { // By default, created module is declared as a main module's dependency addModuleWithAndroidFacet(projectBuilder, modules, dirName, projectType, true); } protected final void addModuleWithAndroidFacet( @NotNull TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder, @NotNull List<MyAdditionalModuleData> modules, @NotNull String dirName, int projectType, boolean isMainModuleDependency) { JavaModuleFixtureBuilder moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class); String moduleDirPath = myFixture.getTempDirPath() + getAdditionalModulePath(dirName); //noinspection ResultOfMethodCallIgnored new File(moduleDirPath).mkdirs(); initializeModuleFixtureBuilderWithSrcAndGen(moduleFixtureBuilder, moduleDirPath); modules.add(new MyAdditionalModuleData(moduleFixtureBuilder, dirName, projectType, isMainModuleDependency)); } protected void createManifest() throws IOException { myFixture.copyFileToProject(SdkConstants.FN_ANDROID_MANIFEST_XML, SdkConstants.FN_ANDROID_MANIFEST_XML); } protected final void createProjectProperties() throws IOException { myFixture.copyFileToProject(SdkConstants.FN_PROJECT_PROPERTIES, SdkConstants.FN_PROJECT_PROPERTIES); } protected final void deleteManifest() throws IOException { deleteManifest(myModule); } protected final void deleteManifest(final Module module) throws IOException { AndroidFacet facet = AndroidFacet.getInstance(module); assertNotNull(facet); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { String manifestRelativePath = facet.getProperties().MANIFEST_FILE_RELATIVE_PATH; VirtualFile manifest = AndroidRootUtil.getFileByRelativeModulePath(module, manifestRelativePath, true); if (manifest != null) { try { manifest.delete(this); } catch (IOException e) { fail("Could not delete default manifest"); } } } }); } protected final void doGlobalInspectionTest( @NotNull GlobalInspectionTool inspection, @NotNull String globalTestDir, @NotNull AnalysisScope scope) { doGlobalInspectionTest(new GlobalInspectionToolWrapper(inspection), globalTestDir, scope); } /** * Given an inspection and a path to a directory that contains an "expected.xml" file, run the * inspection on the current test project and verify that its output matches that of the * expected file. */ protected final void doGlobalInspectionTest( @NotNull GlobalInspectionToolWrapper wrapper, @NotNull String globalTestDir, @NotNull AnalysisScope scope) { myFixture.enableInspections(wrapper.getTool()); scope.invalidate(); InspectionManagerEx inspectionManager = (InspectionManagerEx)InspectionManager.getInstance(getProject()); GlobalInspectionContextForTests globalContext = CodeInsightTestFixtureImpl.createGlobalContextForTool(scope, getProject(), inspectionManager, wrapper); InspectionTestUtil.runTool(wrapper, scope, globalContext); InspectionTestUtil.compareToolResults(globalContext, wrapper, false, getTestDataPath() + globalTestDir); } protected static class MyAdditionalModuleData { final JavaModuleFixtureBuilder myModuleFixtureBuilder; final String myDirName; final int myProjectType; final boolean myIsMainModuleDependency; private MyAdditionalModuleData( @NotNull JavaModuleFixtureBuilder moduleFixtureBuilder, @NotNull String dirName, int projectType, boolean isMainModuleDependency) { myModuleFixtureBuilder = moduleFixtureBuilder; myDirName = dirName; myProjectType = projectType; myIsMainModuleDependency = isMainModuleDependency; } } @NotNull protected <T> T registerApplicationComponent(@NotNull Class<T> key, @NotNull T instance) throws Exception { MutablePicoContainer picoContainer = (MutablePicoContainer)ApplicationManager.getApplication().getPicoContainer(); @SuppressWarnings("unchecked") T old = (T)picoContainer.getComponentInstance(key.getName()); picoContainer.unregisterComponent(key.getName()); picoContainer.registerComponentInstance(key.getName(), instance); return old; } @NotNull protected <T> T registerProjectComponent(@NotNull Class<T> key, @NotNull T instance) { MutablePicoContainer picoContainer = (MutablePicoContainer)getProject().getPicoContainer(); @SuppressWarnings("unchecked") T old = (T)picoContainer.getComponentInstance(key.getName()); picoContainer.unregisterComponent(key.getName()); picoContainer.registerComponentInstance(key.getName(), instance); return old; } }