| /* |
| * 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.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.PsiFile; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import com.intellij.psi.xml.XmlFile; |
| 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 ANDROID_MANIFEST_AFTER = "AndroidManifest_after.xml"; |
| private static final String ANDROID_MANIFEST_HAS_GMS_TAG = "has_gms_tag.xml"; |
| private static final String BUILD_GRADLE = "build.gradle"; |
| private static final String BUILD_GRADLE_AFTER = "build_after.gradle"; |
| private static final String BUILD_GRADLE_HAS_APP_INDEXING_DEPENDENCY = "has_app_indexing_dependency.gradle"; |
| private static final String BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY = "low_version_app_indexing_dependency.gradle"; |
| private static final String BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY_AFTER = "low_version_app_indexing_dependency_after.gradle"; |
| private static final String BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY = "low_version_play_services_dependency.gradle"; |
| private static final String BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY_AFTER = "low_version_play_services_dependency_after.gradle"; |
| private static final String CARET_NOT_IN_JAVA_FILE = "caret_not_in_java_file.xml"; |
| private static final String JAVA_FILE_CARET_OUTSIDE_ACTIVITY = "CaretOutsideActivity.java"; |
| private static final String JAVA_FILE_NOT_ACTIVITY = "NotActivity.java"; |
| private static final String JAVA_FILE_HAS_APP_INDEXING_START_AND_END = "HasAppIndexingStartAndEnd.java"; |
| private static final String JAVA_FILE_HAS_APP_INDEXING_VIEW_AND_VIEW_END = "HasAppIndexingViewAndViewEnd.java"; |
| private static final String JAVA_FILE_SPECIAL_STATEMENT = "SpecialStatement.java"; |
| private static final String JAVA_FILE_EMPTY_ACTIVITY = "EmptyActivity.java"; |
| private static final String JAVA_FILE_EMPTY_ACTIVITY_AFTER = "EmptyActivity_after.java"; |
| private static final String JAVA_FILE_CONFLICT_IMPORTS = "ConflictImports.java"; |
| private static final String JAVA_FILE_CONFLICT_IMPORTS_AFTER = "ConflictImports_after.java"; |
| private static final String JAVA_FILE_WILD_CARD_IMPORTS = "WildCardImports.java"; |
| private static final String JAVA_FILE_WILD_CARD_IMPORTS_AFTER = "WildCardImports_after.java"; |
| private static final String JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER = |
| "OnlyStartCallAndAdjustClientFieldInitializer.java"; |
| private static final String JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER_AFTER = |
| "OnlyStartCallAndAdjustClientFieldInitializer_after.java"; |
| private static final String JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT = |
| "OnlyEndCallAndAdjustClientInitializingStatement.java"; |
| private static final String JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT_AFTER = |
| "OnlyEndCallAndAdjustClientInitializingStatement_after.java"; |
| private static final String JAVA_FILE_ONLY_VIEW_CALL = "OnlyViewCall.java"; |
| private static final String JAVA_FILE_ONLY_VIEW_CALL_AFTER = "OnlyViewCall_after.java"; |
| private static final String JAVA_FILE_ONLY_VIEW_END_CALL = "OnlyViewEndCall.java"; |
| private static final String JAVA_FILE_ONLY_VIEW_END_CALL_AFTER = "OnlyViewEndCall_after.java"; |
| private static final String JAVA_FILE_ACTION_NOT_INITIALIZED = "ActionNotInitialized.java"; |
| private static final String JAVA_FILE_ACTION_NOT_INITIALIZED_AFTER = "ActionNotInitialized_after.java"; |
| private static final String JAVA_FILE_CLIENT_FIELD = "ClientField.java"; |
| private static final String JAVA_FILE_CLIENT_FIELD_AFTER = "ClientField_after.java"; |
| private static final String JAVA_FILE_CLIENT_LOCAL_VARIABLE = "ClientLocalVariable.java"; |
| private static final String JAVA_FILE_CLIENT_LOCAL_VARIABLE_FIELD_AFTER = "ClientLocalVariable_after.java"; |
| private static final String JAVA_FILE_CLIENT_NOT_INITIALIZED = "ClientNotInitialized.java"; |
| private static final String JAVA_FILE_CLIENT_NOT_INITIALIZED_AFTER = "ClientNotInitialized_after.java"; |
| private static final String JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER = "ClientInitializedWithoutBuilder.java"; |
| private static final String JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER_AFTER = "ClientInitializedWithoutBuilder_after.java"; |
| private static final String JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT = "ClientInDeclarationStatement.java"; |
| private static final String JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT_AFTER = "ClientInDeclarationStatement_after.java"; |
| private static final String JAVA_FILE_OVERRIDE_METHOD = "OverrideMethod.java"; |
| private static final String JAVA_FILE_OVERRIDE_METHOD_AFTER = "OverrideMethod_after.java"; |
| private static final String JAVA_FILE_NO_KEYWORD_SUPER = "NoKeywordSuper.java"; |
| private static final String JAVA_FILE_NO_KEYWORD_SUPER_AFTER = "NoKeywordSuper_after.java"; |
| private static final String ACTION_WITH_NULL_DEEP_LINK = |
| "com.google.android.gms.appindexing.Action viewAction = com.google.android.gms.appindexing.Action.newAction(\n" + |
| " com.google.android.gms.appindexing.Action.TYPE_VIEW, // TODO: choose an action type.\n" + |
| " \"Main Page\", // TODO: Define a title for the content shown.\n" + |
| " // TODO: If you have web page content that matches this app activity's content,\n" + |
| " // make sure this auto-generated web page URL is correct.\n" + |
| " // Otherwise, set the URL to null.\n" + |
| " android.net.Uri.parse(\"http://host/path\"), \n" + |
| " // TODO: Make sure this auto-generated app deep link URI is correct.\n" + |
| " android.net.Uri.parse(\"android-app://com.example.creation/http/host/path\")\n" + |
| ");"; |
| private static final String ACTION_WITH_SCHEME_ONLY = ACTION_WITH_NULL_DEEP_LINK; |
| private static final String ACTION_WITHOUT_PATH = |
| "com.google.android.gms.appindexing.Action viewAction = com.google.android.gms.appindexing.Action.newAction(\n" + |
| " com.google.android.gms.appindexing.Action.TYPE_VIEW, // TODO: choose an action type.\n" + |
| " \"Main Page\", // TODO: Define a title for the content shown.\n" + |
| " // TODO: If you have web page content that matches this app activity's content,\n" + |
| " // make sure this auto-generated web page URL is correct.\n" + |
| " // Otherwise, set the URL to null.\n" + |
| " android.net.Uri.parse(\"http://www.example.com/path\"), \n" + |
| " // TODO: Make sure this auto-generated app deep link URI is correct.\n" + |
| " android.net.Uri.parse(\"android-app://com.example.creation/http/www.example.com/path\")\n" + |
| ");"; |
| |
| public ApiCreatorTest() { |
| super(false); |
| } |
| |
| @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 { |
| super.tearDown(); |
| CodeStyleSettingsManager.getInstance().dropTemporarySettings(); |
| } |
| |
| public void testNoAppIndexingDependencyInGradle() throws Exception { |
| doGradleFileTest(BUILD_GRADLE, BUILD_GRADLE_AFTER, null, true); |
| } |
| |
| public void testHasAppIndexingDependencyInGradle() throws Exception { |
| doGradleFileTest(BUILD_GRADLE_HAS_APP_INDEXING_DEPENDENCY, BUILD_GRADLE_AFTER, "8.1.0", false); |
| } |
| |
| public void testLowVersionAppIndexingDependencyInGradle() throws Exception { |
| doGradleFileTest(BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY, BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY_AFTER, "6.5.0", |
| true); |
| } |
| |
| public void testLowVersionPlayServicesDependencyInGradle() throws Exception { |
| doGradleFileTest(BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY, BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY_AFTER, "6.5.0", |
| true); |
| } |
| |
| |
| public void testNoGmsTagInAndroidManifest() throws Exception { |
| doManifestFileTest(ANDROID_MANIFEST, ANDROID_MANIFEST_AFTER); |
| } |
| |
| public void testHasGmsTagInAndroidManifest() throws Exception { |
| doManifestFileTest(ANDROID_MANIFEST_HAS_GMS_TAG, ANDROID_MANIFEST_AFTER); |
| } |
| |
| public void testCaretInAndroidManifest() throws Exception { |
| doEnableTest(CARET_NOT_IN_JAVA_FILE, false); |
| } |
| |
| public void testCaretOutsideActivity() throws Exception { |
| doEnableTest(JAVA_FILE_CARET_OUTSIDE_ACTIVITY, false); |
| } |
| |
| public void testClassNotInheritActivity() throws Exception { |
| doEnableTest(JAVA_FILE_NOT_ACTIVITY, false); |
| } |
| |
| public void testHasAppIndexingApiStartAndEnd() throws Exception { |
| doEnableTest(JAVA_FILE_HAS_APP_INDEXING_START_AND_END, false); |
| } |
| |
| public void testHasAppIndexingApiViewAndViewEnd() throws Exception { |
| doEnableTest(JAVA_FILE_HAS_APP_INDEXING_VIEW_AND_VIEW_END, false); |
| } |
| |
| /** |
| * 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, false); |
| } |
| |
| |
| 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 testOnlyAppIndexingStartAndAdjustGoogleApiClientFieldInitializer() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER, |
| JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER_AFTER); |
| } |
| |
| public void testOnlyAppIndexingEndAndAdjustClientInitializingStatement() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT, |
| JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT_AFTER); |
| } |
| |
| public void testOnlyAppIndexingView() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_VIEW_CALL, JAVA_FILE_ONLY_VIEW_CALL_AFTER); |
| } |
| |
| public void testOnlyAppIndexingViewEnd() throws Exception { |
| doJavaFileTest(JAVA_FILE_ONLY_VIEW_END_CALL, JAVA_FILE_ONLY_VIEW_END_CALL_AFTER); |
| } |
| |
| public void testGoogleApiActionNotInitialized() throws Exception { |
| doJavaFileTest(JAVA_FILE_ACTION_NOT_INITIALIZED, JAVA_FILE_ACTION_NOT_INITIALIZED_AFTER); |
| } |
| |
| public void testWithGoogleApiClientField() throws Exception { |
| doJavaFileTest(JAVA_FILE_CLIENT_FIELD, JAVA_FILE_CLIENT_FIELD_AFTER); |
| } |
| |
| public void testWithGoogleApiClientLocalVariable() throws Exception { |
| doJavaFileTest(JAVA_FILE_CLIENT_LOCAL_VARIABLE, JAVA_FILE_CLIENT_LOCAL_VARIABLE_FIELD_AFTER); |
| } |
| |
| public void testGoogleApiClientNotInitialized() throws Exception { |
| doJavaFileTest(JAVA_FILE_CLIENT_NOT_INITIALIZED, JAVA_FILE_CLIENT_NOT_INITIALIZED_AFTER); |
| } |
| |
| public void testGoogleApiClientInitializedWithoutBuilder() throws Exception { |
| doJavaFileTest(JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER, JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER_AFTER); |
| } |
| |
| public void testGetGoogleApiClientFromDeclarationStatement() throws Exception { |
| doJavaFileTest(JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT, JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT_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); |
| } |
| |
| public void testGenerateActionInitializerWithIncompleteDeepLink() throws Exception { |
| myFixture.configureByFiles(JAVA_FILE_EMPTY_ACTIVITY); |
| ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); |
| assertEquals(creator.getActionStatement(null, "viewAction"), ACTION_WITH_NULL_DEEP_LINK); |
| assertEquals(creator.getActionStatement("http://", "viewAction"), ACTION_WITH_SCHEME_ONLY); |
| assertEquals(creator.getActionStatement("http://www.example.com", "viewAction"), ACTION_WITHOUT_PATH); |
| } |
| |
| /** |
| * 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, boolean enable) { |
| myFixture.configureByFiles(filePath); |
| assertEquals(ApiCreator.eligibleForInsertingAppIndexingApiCode(myFixture.getEditor(), myFixture.getFile()), enable); |
| } |
| |
| /** |
| * Test for creation in Java source file. |
| */ |
| private void doJavaFileTest(@NotNull String testFilePath, @NotNull String expectedFilePath) { |
| doEnableTest(testFilePath, true); |
| myFixture.copyFileToProject(ANDROID_MANIFEST); |
| final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); |
| CommandProcessor.getInstance().executeCommand(null, new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| creator.insertAppIndexingApiCodeInJavaFile(creator.getDeepLinkOfActivity(), false); |
| } |
| }); |
| } |
| }, 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(normalizedTestFile, getNormalizedContent(expectedPsiFile)); |
| } |
| |
| /** |
| * Test for creation in build.gradle file. |
| */ |
| private void doGradleFileTest(@NotNull String testFilePath, |
| @NotNull String expectedFilePath, |
| @Nullable final String version, |
| final boolean needSync) { |
| VirtualFile testVirtual = myFixture.copyFileToProject(testFilePath, getGradleRelativePath()); |
| myFixture.configureByFile(JAVA_FILE_EMPTY_ACTIVITY); // This file is not used. It's for opening editor. |
| |
| final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); |
| creator.setGmsLibVersion(version); |
| CommandProcessor.getInstance().executeCommand(null, new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| assertEquals(creator.insertGmsCompileDependencyInGradleIfNeeded(), needSync); |
| } |
| catch (ApiCreator.ApiCreatorException e) { |
| e.printStackTrace(); |
| fail(); |
| } |
| } |
| }); |
| } |
| }, null, null); |
| |
| PsiFile testPsi = PsiManager.getInstance(myFixture.getProject()).findFile(testVirtual); |
| PsiFile expectedPsi = myFixture.configureByFile(expectedFilePath); |
| assertNotNull(testPsi); |
| assertEquals(getNormalizedContent(testPsi), getNormalizedContent(expectedPsi)); |
| } |
| |
| /** |
| * Test for creation in AndroidManifest.xml file. |
| */ |
| private void doManifestFileTest(@NotNull String testFilePath, @NotNull String expectedFilePath) { |
| PsiFile[] files = myFixture.configureByFiles(testFilePath, expectedFilePath); |
| final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); |
| CommandProcessor.getInstance().executeCommand(null, new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| assertTrue(myFixture.getFile() instanceof XmlFile); |
| creator.insertGmsVersionTagInManifestIfNeeded((XmlFile)myFixture.getFile()); |
| } |
| }); |
| } |
| }, null, null); |
| assertEquals(getNormalizedContent(myFixture.getFile()), getNormalizedContent(files[1])); |
| } |
| |
| /** |
| * 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(); |
| } |
| } |