| /* |
| * 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.google.appindexing.api; |
| |
| import com.android.SdkConstants; |
| import com.google.common.base.CharMatcher; |
| import com.google.common.base.Splitter; |
| import com.intellij.codeInsight.template.impl.TemplateManagerImpl; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.PathManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiClass; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiJavaFile; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import com.intellij.psi.util.InheritanceUtil; |
| import org.jetbrains.android.AndroidTestCase; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| public class ApiCreatorTest extends AndroidTestCase { |
| private static final String BASE_PATH = PathManager.getHomePath() + "/../studio/google/appindexing/testData/ApiCreator"; |
| |
| private static final String ANDROID_MANIFEST = "AndroidManifest.xml"; |
| private static final String JAVA_FILE_NOT_ACTIVITY = "src/NotActivity.java"; |
| private static final String JAVA_FILE_HAS_APP_INDEXING_START_AND_END = "src/HasAppIndexingStartAndEnd.java"; |
| private static final String JAVA_FILE_SPECIAL_STATEMENT = "src/SpecialStatement.java"; |
| private static final String JAVA_FILE_EMPTY_ACTIVITY = "src/EmptyActivity.java"; |
| private static final String JAVA_FILE_EMPTY_ACTIVITY_AFTER = "src/EmptyActivity_after.java"; |
| private static final String JAVA_FILE_CONFLICT_IMPORTS = "src/ConflictImports.java"; |
| private static final String JAVA_FILE_CONFLICT_IMPORTS_AFTER = "src/ConflictImports_after.java"; |
| private static final String JAVA_FILE_WILD_CARD_IMPORTS = "src/WildCardImports.java"; |
| private static final String JAVA_FILE_WILD_CARD_IMPORTS_AFTER = "src/WildCardImports_after.java"; |
| private static final String JAVA_FILE_BOTH_CALL_ON_START = "src/BothCallsInOnStart.java"; |
| private static final String JAVA_FILE_BOTH_CALL_ON_START_AFTER = "src/BothCallsInOnStart_after.java"; |
| private static final String JAVA_FILE_ONLY_UPDATE_CALL = "src/OnlyStartCall.java"; |
| private static final String JAVA_FILE_ONLY_UPDATE_CALL_AFTER = "src/OnlyStartCall.java"; |
| private static final String JAVA_FILE_ONLY_START_CALL = "src/OnlyStartCall.java"; |
| private static final String JAVA_FILE_ONLY_START_CALL_AFTER = "src/OnlyStartCall_after.java"; |
| private static final String JAVA_FILE_ONLY_END_CALL = "src/OnlyEndCall.java"; |
| private static final String JAVA_FILE_ONLY_END_CALL_AFTER = "src/OnlyEndCall_after.java"; |
| private static final String JAVA_FILE_OVERRIDE_METHOD = "src/OverrideMethod.java"; |
| private static final String JAVA_FILE_OVERRIDE_METHOD_AFTER = "src/OverrideMethod_after.java"; |
| private static final String JAVA_FILE_NO_KEYWORD_SUPER = "src/NoKeywordSuper.java"; |
| private static final String JAVA_FILE_NO_KEYWORD_SUPER_AFTER = "src/NoKeywordSuper_after.java"; |
| private static final String JAVA_FILE_CONFLICT_METHOD_NAME = "src/ConflictGetActionMethodName.java"; |
| private static final String JAVA_FILE_CONFLICT_METHOD_NAME_AFTER = "src/ConflictGetActionMethodName_after.java"; |
| |
| @Override |
| protected boolean providesCustomManifest() { |
| return true; |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| myFixture.setTestDataPath(BASE_PATH); |
| TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable()); |
| // Set an empty temporary code style. Otherwise, the variable name will follow users' custom setting. |
| CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(myFixture.getProject()); |
| codeStyleSettings.clearCodeStyleSettings(); |
| CodeStyleSettingsManager.getInstance().setTemporarySettings(codeStyleSettings); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| try { |
| CodeStyleSettingsManager.getInstance().dropTemporarySettings(); |
| } |
| finally { |
| super.tearDown(); |
| } |
| } |
| |
| public void testClassNotInheritActivity() throws Exception { |
| doEnableTest(JAVA_FILE_NOT_ACTIVITY, ApiCreator.InsertStatusCode.JAVA_FILE_WITHOUT_ACTIVITY); |
| } |
| |
| public void testHasAppIndexingApiStartAndEnd() throws Exception { |
| doEnableTest(JAVA_FILE_HAS_APP_INDEXING_START_AND_END, ApiCreator.InsertStatusCode.DEPENDENCY_MISSING); |
| } |
| |
| /** |
| * The app indexing APIs are used in special statements, |
| * such as block statements, declaration statements and loop statement. |
| */ |
| public void testSpecialStatement() throws Exception { |
| doEnableTest(JAVA_FILE_SPECIAL_STATEMENT, ApiCreator.InsertStatusCode.DEPENDENCY_MISSING); |
| } |
| |
| public void testEmptyActivity() throws Exception { |
| doJavaFileTest(JAVA_FILE_EMPTY_ACTIVITY, JAVA_FILE_EMPTY_ACTIVITY_AFTER); |
| } |
| |
| public void testConflictImports() throws Exception { |
| doJavaFileTest(JAVA_FILE_CONFLICT_IMPORTS, JAVA_FILE_CONFLICT_IMPORTS_AFTER); |
| } |
| |
| public void testWildCardImports() throws Exception { |
| doJavaFileTest(JAVA_FILE_WILD_CARD_IMPORTS, JAVA_FILE_WILD_CARD_IMPORTS_AFTER); |
| } |
| |
| public void testOnlyAppIndexingStart() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_START_CALL, JAVA_FILE_ONLY_START_CALL_AFTER); |
| } |
| |
| public void testOnlyAppIndexingUpdate() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_UPDATE_CALL, JAVA_FILE_ONLY_UPDATE_CALL_AFTER); |
| } |
| |
| public void testBothCallsInOnStart() throws Exception { |
| doJavaFileTest(JAVA_FILE_BOTH_CALL_ON_START, JAVA_FILE_BOTH_CALL_ON_START_AFTER); |
| } |
| |
| public void testOnlyAppIndexingEnd() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_END_CALL, JAVA_FILE_ONLY_END_CALL_AFTER); |
| } |
| |
| public void testWithOverridingMethod() throws Exception { |
| doJavaFileTest(JAVA_FILE_OVERRIDE_METHOD, JAVA_FILE_OVERRIDE_METHOD_AFTER); |
| } |
| |
| /** |
| * Tests class with overriding method but not invoking overridden method through keyword super |
| */ |
| public void testNoKeywordSuper() throws Exception { |
| doJavaFileTest(JAVA_FILE_NO_KEYWORD_SUPER, JAVA_FILE_NO_KEYWORD_SUPER_AFTER); |
| } |
| |
| /** |
| * Tests class with conflicted method name. |
| */ |
| public void testConflictMethodName() throws Exception { |
| doJavaFileTest(JAVA_FILE_CONFLICT_METHOD_NAME, JAVA_FILE_CONFLICT_METHOD_NAME_AFTER); |
| } |
| |
| /** |
| * Tests whether the project is eligible for App Indexing API code inserting. |
| * |
| * @param filePath The path of the Java source file. |
| * @param enable If the creator (generator / intention) should be enabled. |
| */ |
| private void doEnableTest(@NotNull String filePath, ApiCreator.InsertStatusCode statusCode) { |
| myFixture.configureByFiles(filePath); |
| myFixture.copyFileToProject(ANDROID_MANIFEST); |
| PsiClass activity = getActivityClass(myFixture.getFile()); |
| if (activity == null) { |
| assertEquals(statusCode, ApiCreator.InsertStatusCode.JAVA_FILE_WITHOUT_ACTIVITY); |
| return; |
| } |
| final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getFile(), activity); |
| assertEquals(statusCode, creator.eligibleForInsertingAppIndexingApiCode()); |
| } |
| |
| /** |
| * Test for creation in Java source file. |
| */ |
| private void doJavaFileTest(@NotNull String testFilePath, @NotNull String expectedFilePath) { |
| myFixture.configureByFiles(testFilePath); |
| myFixture.copyFileToProject(ANDROID_MANIFEST); |
| final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getFile(), getActivityClass(myFixture.getFile())); |
| CommandProcessor.getInstance().executeCommand(null, new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| creator.insertAppIndexingApiCodeInJavaFile(); |
| } |
| }); |
| } |
| }, null, null); |
| |
| VirtualFile expectedFile = myFixture.copyFileToProject(expectedFilePath); |
| PsiFile expectedPsiFile = PsiManager.getInstance(myFixture.getProject()).findFile(expectedFile); |
| assertNotNull(expectedPsiFile); |
| String normalizedTestFile = getNormalizedContent(myFixture.getFile()).replace("<caret>", ""); |
| assertEquals(getNormalizedContent(expectedPsiFile), normalizedTestFile); |
| } |
| |
| |
| /** |
| * Normalizes file content: remove java.lang imports, comments, white space and line terminators. |
| * |
| * @param file The file to normalize. |
| * @return The normalized content. |
| */ |
| protected static String getNormalizedContent(@NotNull final PsiFile file) { |
| StringBuilder stringBuilder = new StringBuilder(); |
| // Removes line terminators. |
| Iterable<String> lines = Splitter.onPattern("\r?\n").omitEmptyStrings().split(file.getText()); |
| for (String line : lines) { |
| line = CharMatcher.WHITESPACE.removeFrom(line); |
| // Removes import statements of classes from java.lang package if it is a Java file. |
| // Because Android Studio will automatically import them. |
| if (line.startsWith("importjava.lang.")) { |
| continue; |
| } |
| stringBuilder.append(line); |
| } |
| return stringBuilder.toString(); |
| } |
| |
| @NotNull |
| protected String getGradleRelativePath() { |
| StringBuilder builder = new StringBuilder(); |
| String projectPath = myFixture.getProject().getBasePath(); |
| while (projectPath != null && projectPath.contains("/")) { |
| projectPath = projectPath.substring(0, projectPath.lastIndexOf("/")); |
| builder.append("../"); |
| } |
| builder.deleteCharAt(builder.length() - 1); |
| String moduleFilePath = myModule.getModuleFilePath(); |
| builder.append(moduleFilePath.substring(0, moduleFilePath.lastIndexOf("/"))); |
| builder.append("/build.gradle"); |
| return builder.toString(); |
| } |
| |
| /** |
| * Gets the activity class contained by the psi file. |
| * |
| * @param file psi file which may contain activity class |
| * @return the first class which is inherited from CLASS_ACTIVITY |
| */ |
| @Nullable |
| static PsiClass getActivityClass(@NotNull PsiFile file) { |
| if (!(file instanceof PsiJavaFile)) return null; |
| PsiClass[] classes = ((PsiJavaFile)file).getClasses(); |
| for (PsiClass candidate : classes) { |
| if (InheritanceUtil.isInheritor(candidate, SdkConstants.CLASS_ACTIVITY)) { |
| return candidate; |
| } |
| } |
| return null; |
| } |
| } |