diff --git a/.idea/libraries/android_tests.xml b/.idea/libraries/android_tests.xml
new file mode 100644
index 00000000000..df8f15241aa
--- /dev/null
+++ b/.idea/libraries/android_tests.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Kotlin.iml b/Kotlin.iml
index 1f79322d25e..4278f5e1940 100644
--- a/Kotlin.iml
+++ b/Kotlin.iml
@@ -8,6 +8,7 @@
+
@@ -45,4 +46,4 @@
-
\ No newline at end of file
+
diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt
index 95b5e4f1648..61c304a9311 100644
--- a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt
+++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt
@@ -18,6 +18,10 @@ package org.jetbrains.jet.lang.resolve.android
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiElement
+import com.intellij.openapi.project.Project
+import org.jetbrains.jet.lang.psi.JetFile
+import com.intellij.openapi.components.ServiceManager
+import java.util.ArrayList
trait AndroidResource
@@ -44,4 +48,11 @@ fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
public fun isAndroidSyntheticElement(element: PsiElement?): Boolean {
return isAndroidSyntheticFile(element?.getContainingFile())
-}
\ No newline at end of file
+}
+public fun searchAndAddAndroidDeclarations(project: Project, originalFiles: Collection): Collection {
+ val parser = ServiceManager.getService(project, javaClass())
+ val file = parser?.parseToPsi(project)
+ val files = ArrayList(originalFiles)
+ if (file != null) files.add(file)
+ return files
+}
diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt
index d0f956ddbfb..0424ea48842 100644
--- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt
+++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt
@@ -34,6 +34,11 @@ import org.jetbrains.kotlin.analyzer.ModuleContent
import org.jetbrains.kotlin.di.InjectorForLazyResolveWithJava
import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.jet.lang.psi.JetFile
+import com.intellij.openapi.components.ServiceManager
+import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor
+import java.util.ArrayList
+import org.jetbrains.jet.lang.resolve.android.searchAndAddAndroidDeclarations
public class JvmResolverForModule(
override val lazyResolveSession: ResolveSession,
@@ -56,8 +61,10 @@ public object JvmAnalyzerFacade : AnalyzerFacade
): JvmResolverForModule {
val (syntheticFiles, moduleContentScope) = moduleContent
+ val filesToAnalyze = searchAndAddAndroidDeclarations(project, syntheticFiles)
val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory(
- project, globalContext.storageManager, syntheticFiles, if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope
+ project, globalContext.storageManager, filesToAnalyze,
+ if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope
)
val moduleClassResolver = ModuleClassResolverImpl { javaClass ->
diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java
index d8156aa0f3e..cbb36cab018 100644
--- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java
+++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java
@@ -17,13 +17,11 @@
package org.jetbrains.kotlin.resolve.jvm;
import com.google.common.base.Predicate;
-import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.context.GlobalContext;
@@ -174,11 +172,4 @@ public enum TopDownAnalyzerFacadeForJVM {
module.seal();
return module;
}
-
- private static Collection searchAndAddAndroidDeclarations(Project project, Collection files) {
- AndroidUIXmlProcessor parser = ServiceManager.getService(project, AndroidUIXmlProcessor.class);
- JetFile file = parser.parseToPsi(project);
- if (file != null) files.add(file);
- return files;
- }
}
diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
index e603c8739df..20e01bc7672 100644
--- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
+++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
@@ -145,6 +145,7 @@ import org.jetbrains.kotlin.idea.highlighter.*
import org.jetbrains.jet.lang.resolve.android.AbstractAndroidXml2KConversionTest
import org.jetbrains.jet.android.AbstractCrossParserTest
import org.jetbrains.jet.lang.resolve.android.AbstractAndroidBoxTest
+import org.jetbrains.jet.android.AbstractAndroidCompletionTest
fun main(args: Array) {
System.setProperty("java.awt.headless", "true")
@@ -710,6 +711,10 @@ fun main(args: Array) {
testClass("org.jetbrains.kotlin.idea.kdoc.KDocCompletionTestGenerated") {
model("kdoc/completion")
}
+
+ testClass(javaClass()) {
+ model("android/completion", recursive = false, extension = null)
+ }
}
testGroup("idea/tests", "compiler/testData") {
diff --git a/idea/idea.iml b/idea/idea.iml
index 750a0614971..4b1492d2429 100644
--- a/idea/idea.iml
+++ b/idea/idea.iml
@@ -39,11 +39,12 @@
-
+
+
diff --git a/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlProcessor.kt b/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlProcessor.kt
index c5f7b973ce5..885f3b939a1 100644
--- a/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlProcessor.kt
+++ b/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlProcessor.kt
@@ -27,14 +27,11 @@ import org.jetbrains.jet.lang.resolve.android.CliAndroidResourceManager
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManager
class IDEAndroidUIXmlProcessor(project: Project) : AndroidUIXmlProcessor(project) {
- override val searchPath: String? = if (ApplicationManager.getApplication()!!.isUnitTestMode()) project.getUserData(TestConst.TESTDATA_PATH) + "layout/"
- else project.getBasePath() + "/res/layout/"
+ override val searchPath: String? = project.getBasePath() + "/res/layout/"
override var androidAppPackage: String = ""
get() = resourceManager.readManifest()._package
- override val resourceManager: AndroidResourceManager = if (ApplicationManager.getApplication()!!.isUnitTestMode())
- CliAndroidResourceManager(project, searchPath, project.getUserData(TestConst.TESTDATA_PATH) + "AndroidManifest.xml")
- else IDEAndroidResourceManager(project, searchPath)
+ override val resourceManager: AndroidResourceManager = IDEAndroidResourceManager(project, searchPath)
override fun parseSingleFileImpl(file: PsiFile): String {
val ids: MutableCollection = ArrayList()
diff --git a/idea/testData/android/AndroidManifest.xml b/idea/testData/android/AndroidManifest.xml
new file mode 100644
index 00000000000..c82007dcc87
--- /dev/null
+++ b/idea/testData/android/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/idea/testData/android/completion/PropertiesSimple/res/layout/AndroidManifest.xml b/idea/testData/android/completion/PropertiesSimple/res/layout/AndroidManifest.xml
deleted file mode 100644
index 580c474f5de..00000000000
--- a/idea/testData/android/completion/PropertiesSimple/res/layout/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/idea/testData/android/completion/fqNameInAttr/fqNameInAttr.kt b/idea/testData/android/completion/fqNameInAttr/fqNameInAttr.kt
new file mode 100644
index 00000000000..e70f6033309
--- /dev/null
+++ b/idea/testData/android/completion/fqNameInAttr/fqNameInAttr.kt
@@ -0,0 +1,10 @@
+package com.myapp
+
+import android.app.Activity
+
+
+class MyActivity: Activity() {
+ val button = this.MyBu
+}
+
+// EXIST: MyButton
diff --git a/idea/testData/android/completion/fqNameInAttr/res/layout/layout.xml b/idea/testData/android/completion/fqNameInAttr/res/layout/layout.xml
new file mode 100644
index 00000000000..a0ef9f3a1ed
--- /dev/null
+++ b/idea/testData/android/completion/fqNameInAttr/res/layout/layout.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/idea/testData/android/completion/fqNameInTag/fqNameInTag.kt b/idea/testData/android/completion/fqNameInTag/fqNameInTag.kt
new file mode 100644
index 00000000000..e70f6033309
--- /dev/null
+++ b/idea/testData/android/completion/fqNameInTag/fqNameInTag.kt
@@ -0,0 +1,10 @@
+package com.myapp
+
+import android.app.Activity
+
+
+class MyActivity: Activity() {
+ val button = this.MyBu
+}
+
+// EXIST: MyButton
diff --git a/idea/testData/android/completion/fqNameInTag/res/layout/layout.xml b/idea/testData/android/completion/fqNameInTag/res/layout/layout.xml
new file mode 100644
index 00000000000..2ec85364e74
--- /dev/null
+++ b/idea/testData/android/completion/fqNameInTag/res/layout/layout.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/idea/testData/android/completion/multiFile/multiFile.kt b/idea/testData/android/completion/multiFile/multiFile.kt
new file mode 100644
index 00000000000..1a81c61f283
--- /dev/null
+++ b/idea/testData/android/completion/multiFile/multiFile.kt
@@ -0,0 +1,10 @@
+package com.myapp
+
+import android.app.Activity
+
+
+class MyActivity: Activity() {
+ val button = this.log
+}
+
+// EXIST: login, loginButton
diff --git a/idea/testData/android/completion/multiFile/res/layout/layout.xml b/idea/testData/android/completion/multiFile/res/layout/layout.xml
new file mode 100644
index 00000000000..f4736f6937b
--- /dev/null
+++ b/idea/testData/android/completion/multiFile/res/layout/layout.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/idea/testData/android/completion/multiFile/res/layout/layout1.xml b/idea/testData/android/completion/multiFile/res/layout/layout1.xml
new file mode 100644
index 00000000000..7c48f2b615c
--- /dev/null
+++ b/idea/testData/android/completion/multiFile/res/layout/layout1.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/idea/testData/android/completion/PropertiesSimple/PropertiesSimple.kt b/idea/testData/android/completion/propertiesSimple/propertiesSimple.kt
similarity index 55%
rename from idea/testData/android/completion/PropertiesSimple/PropertiesSimple.kt
rename to idea/testData/android/completion/propertiesSimple/propertiesSimple.kt
index c9dbc632e72..d25fbcbc094 100644
--- a/idea/testData/android/completion/PropertiesSimple/PropertiesSimple.kt
+++ b/idea/testData/android/completion/propertiesSimple/propertiesSimple.kt
@@ -1,10 +1,9 @@
package com.myapp
-trait Activity
-trait Button
+import android.app.Activity
-class MyActivity: Activity {
+class MyActivity: Activity() {
val button = this.log
}
diff --git a/idea/testData/android/completion/PropertiesSimple/res/layout/layout.xml b/idea/testData/android/completion/propertiesSimple/res/layout/layout.xml
similarity index 100%
rename from idea/testData/android/completion/PropertiesSimple/res/layout/layout.xml
rename to idea/testData/android/completion/propertiesSimple/res/layout/layout.xml
diff --git a/idea/tests/org/jetbrains/jet/android/AbstractAndroidCompletionTest.kt b/idea/tests/org/jetbrains/jet/android/AbstractAndroidCompletionTest.kt
new file mode 100644
index 00000000000..db741e80426
--- /dev/null
+++ b/idea/tests/org/jetbrains/jet/android/AbstractAndroidCompletionTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2014 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.jet.android
+
+import org.jetbrains.jet.plugin.PluginTestCaseBase
+import com.intellij.openapi.application.PathManager
+import com.intellij.codeInsight.completion.CompletionType
+import com.intellij.openapi.util.io.FileUtil
+import java.io.File
+import org.jetbrains.jet.completion.util.testCompletion
+import org.jetbrains.jet.plugin.project.TargetPlatform
+import com.intellij.ide.startup.impl.StartupManagerImpl
+import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
+import com.intellij.openapi.startup.StartupManager
+import org.jetbrains.jet.JetTestCaseBuilder
+import org.jetbrains.jet.plugin.actions.internal.KotlinInternalMode
+import com.intellij.psi.impl.PsiManagerEx
+import com.intellij.psi.PsiManager
+import org.jetbrains.jet.plugin.references.BuiltInsReferenceResolver
+import com.android.SdkConstants
+import com.intellij.codeInsight.CodeInsightSettings
+
+public abstract class AbstractAndroidCompletionTest : KotlinAndroidTestCase() {
+ private var kotlinInternalModeOriginalValue: Boolean = false
+
+ override fun setUp() {
+
+ System.setProperty(KotlinAndroidTestCaseBase.SDK_PATH_PROPERTY, PathManager.getHomePath() + "/androidSDK/")
+ System.setProperty(KotlinAndroidTestCaseBase.PLATFORM_DIR_PROPERTY, "android-17")
+
+ super.setUp()
+ myFixture!!.setTestDataPath(getTestDataPath())
+ (StartupManager.getInstance(getProject()) as StartupManagerImpl).runPostStartupActivities()
+ VfsRootAccess.allowRootAccess(JetTestCaseBuilder.getHomeDirectory())
+
+ kotlinInternalModeOriginalValue = KotlinInternalMode.enabled
+ KotlinInternalMode.enabled = true
+ setAutoCompleteSetting(false)
+ }
+
+ override fun createManifest() {
+ myFixture!!.copyFileToProject("idea/testData/android/AndroidManifest.xml", SdkConstants.FN_ANDROID_MANIFEST_XML)
+ }
+
+ override fun tearDown() {
+ KotlinInternalMode.enabled = kotlinInternalModeOriginalValue
+ VfsRootAccess.disallowRootAccess(JetTestCaseBuilder.getHomeDirectory())
+
+ val builtInsSources = getProject()!!.getComponent(javaClass())!!.getBuiltInsSources()!!
+ val fileManager = (PsiManager.getInstance(getProject()!!) as PsiManagerEx).getFileManager()
+
+ super.tearDown()
+ // Restore mapping between PsiFiles and VirtualFiles dropped in FileManager.cleanupForNextTest(),
+ // otherwise built-ins psi elements will become invalid in next test.
+ for (source in builtInsSources) {
+ val provider = source.getViewProvider()
+ fileManager.setViewProvider(provider.getVirtualFile(), provider)
+ }
+ }
+ private fun setAutoCompleteSetting(value: Boolean): Boolean {
+ val settings = CodeInsightSettings.getInstance()
+ val oldValue: Boolean
+ if (completionType() == CompletionType.SMART) {
+ oldValue = settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION
+ settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = value
+ }
+ else {
+ oldValue = settings.AUTOCOMPLETE_COMMON_PREFIX
+ settings.AUTOCOMPLETE_ON_CODE_COMPLETION = value
+ }
+ return oldValue
+ }
+
+ private fun completionType() = CompletionType.BASIC
+
+ fun doTest(testPath: String?) {
+ myFixture!!.copyDirectoryToProject("res/", "res")
+ myFixture!!.configureByFile(testPath!! + getTestName(true) + ".kt");
+ val fileText = FileUtil.loadFile(File(testPath + getTestName(true) + ".kt"), true)
+ testCompletion(fileText, TargetPlatform.JVM, {
+ count -> myFixture!!.complete(completionType())
+ })
+ }
+
+
+ override fun getTestDataPath(): String {
+ return PluginTestCaseBase.getTestDataPathBase() + "/android/completion/" + getTestName(true) + "/"
+ }
+
+
+ override fun requireRecentSdk() = true
+
+}
diff --git a/idea/tests/org/jetbrains/jet/android/AndroidCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/android/AndroidCompletionTestGenerated.java
new file mode 100644
index 00000000000..90aa49cdf53
--- /dev/null
+++ b/idea/tests/org/jetbrains/jet/android/AndroidCompletionTestGenerated.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2014 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.jet.android;
+
+import org.jetbrains.jet.JetTestUtils;
+import org.jetbrains.jet.test.TestMetadata;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("idea/testData/android/completion")
+public class AndroidCompletionTestGenerated extends AbstractAndroidCompletionTest {
+ public void SKIPtestAllFilesPresentInCompletion() throws Exception {
+ JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/android/completion"), Pattern.compile("^([^\\.]+)$"), false);
+ }
+
+ @TestMetadata("fqNameInAttr")
+ public void testFqNameInAttr() throws Exception {
+ doTest("idea/testData/android/completion/fqNameInAttr/");
+ }
+
+ @TestMetadata("fqNameInTag")
+ public void testFqNameInTag() throws Exception {
+ doTest("idea/testData/android/completion/fqNameInTag/");
+ }
+
+ @TestMetadata("multiFile")
+ public void testMultiFile() throws Exception {
+ doTest("idea/testData/android/completion/multiFile/");
+ }
+
+ @TestMetadata("propertiesSimple")
+ public void testPropertiesSimple() throws Exception {
+ doTest("idea/testData/android/completion/propertiesSimple/");
+ }
+
+}
diff --git a/idea/tests/org/jetbrains/jet/android/KotlinAndroidTestCase.java b/idea/tests/org/jetbrains/jet/android/KotlinAndroidTestCase.java
new file mode 100644
index 00000000000..c743a4c82c8
--- /dev/null
+++ b/idea/tests/org/jetbrains/jet/android/KotlinAndroidTestCase.java
@@ -0,0 +1,270 @@
+/*
+ * 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.jet.android;
+
+import com.android.SdkConstants;
+import com.android.ide.common.rendering.RenderSecurityManager;
+import com.intellij.analysis.AnalysisScope;
+import com.intellij.codeInspection.GlobalInspectionTool;
+import com.intellij.codeInspection.InspectionManager;
+import com.intellij.codeInspection.ex.GlobalInspectionContextImpl;
+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.application.ApplicationManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.ModuleRootModificationUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.InspectionTestUtil;
+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 org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.android.facet.AndroidRootUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"})
+public abstract class KotlinAndroidTestCase extends KotlinAndroidTestCaseBase {
+ protected Module myModule;
+ protected List myAdditionalModules;
+
+ private boolean myCreateManifest;
+ protected AndroidFacet myFacet;
+
+ public KotlinAndroidTestCase(boolean createManifest) {
+ this.myCreateManifest = createManifest;
+ }
+
+ public KotlinAndroidTestCase() {
+ this(true);
+ }
+ public static void SHIT() {}
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // this will throw an exception if we don't have a full Android SDK, so we need to do this first thing before any other setup
+ String sdkPath = getTestSdkPath();
+
+ final TestFixtureBuilder projectBuilder =
+ IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName());
+ myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture());
+ final JavaModuleFixtureBuilder moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class);
+ final String dirPath = myFixture.getTempDirPath() + getContentRootPath();
+ final File dir = new File(dirPath);
+
+ if (!dir.exists()) {
+ assertTrue(dir.mkdirs());
+ }
+ tuneModule(moduleFixtureBuilder, dirPath);
+
+ final ArrayList 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 !myCreateManifest.
+ // We will delete it at the end of setUp; this is needed when unit tests want to rewrite
+ // the manifest on their own.
+ createManifest();
+
+ myFacet = addAndroidFacet(myModule, sdkPath, getPlatformDir(), isToAddSdk());
+ myFixture.copyDirectoryToProject(getResDir(), "res");
+
+ myAdditionalModules = new ArrayList();
+
+ for (MyAdditionalModuleData data : modules) {
+ final Module additionalModule = data.myModuleFixtureBuilder.getFixture().getModule();
+ myAdditionalModules.add(additionalModule);
+ final AndroidFacet facet = addAndroidFacet(additionalModule, sdkPath, getPlatformDir());
+ facet.setLibraryProject(data.myLibrary);
+ final String rootPath = getContentRootPath(data.myDirName);
+ myFixture.copyDirectoryToProject("res", rootPath + "/res");
+ myFixture.copyFileToProject(SdkConstants.FN_ANDROID_MANIFEST_XML,
+ rootPath + '/' + SdkConstants.FN_ANDROID_MANIFEST_XML);
+ ModuleRootModificationUtil.addDependency(myModule, additionalModule);
+ }
+
+ if (!myCreateManifest) {
+ deleteManifest();
+ }
+
+ if (RenderSecurityManager.RESTRICT_READS) {
+ // Unit test class loader includes disk directories which security manager does not allow access to
+ RenderSecurityManager.sEnabled = false;
+ }
+ }
+
+ protected boolean isToAddSdk() {
+ return true;
+ }
+
+ protected String getContentRootPath() {
+ return "";
+ }
+
+ protected void configureAdditionalModules(@NotNull TestFixtureBuilder projectBuilder,
+ @NotNull List modules) {
+ }
+
+ protected void addModuleWithAndroidFacet(@NotNull TestFixtureBuilder projectBuilder,
+ @NotNull List modules,
+ @NotNull String dirName,
+ boolean library) {
+ final JavaModuleFixtureBuilder moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class);
+ final String moduleDirPath = myFixture.getTempDirPath() + getContentRootPath(dirName);
+ //noinspection ResultOfMethodCallIgnored
+ new File(moduleDirPath).mkdirs();
+ tuneModule(moduleFixtureBuilder, moduleDirPath);
+ modules.add(new MyAdditionalModuleData(moduleFixtureBuilder, dirName, library));
+ }
+
+ protected static String getContentRootPath(@NotNull String moduleName) {
+ return "/additionalModules/" + moduleName;
+ }
+
+ protected String getResDir() {
+ return "res";
+ }
+
+ public static void tuneModule(JavaModuleFixtureBuilder moduleBuilder, String moduleDirPath) {
+ moduleBuilder.addContentRoot(moduleDirPath);
+
+ //noinspection ResultOfMethodCallIgnored
+ new File(moduleDirPath + "/src/").mkdir();
+ moduleBuilder.addSourceRoot("src");
+
+ //noinspection ResultOfMethodCallIgnored
+ new File(moduleDirPath + "/gen/").mkdir();
+ moduleBuilder.addSourceRoot("gen");
+ }
+
+ protected void createManifest() throws IOException {
+ myFixture.copyFileToProject(SdkConstants.FN_ANDROID_MANIFEST_XML, SdkConstants.FN_ANDROID_MANIFEST_XML);
+ }
+
+ protected void createProjectProperties() throws IOException {
+ myFixture.copyFileToProject(SdkConstants.FN_PROJECT_PROPERTIES, SdkConstants.FN_PROJECT_PROPERTIES);
+ }
+
+ protected void deleteManifest() throws IOException {
+ deleteManifest(myModule);
+ }
+
+ protected void deleteManifest(final Module module) throws IOException {
+ final 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");
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ myModule = null;
+ myAdditionalModules = null;
+ myFixture.tearDown();
+ myFixture = null;
+ myFacet = null;
+ if (RenderSecurityManager.RESTRICT_READS) {
+ RenderSecurityManager.sEnabled = true;
+ }
+ super.tearDown();
+ }
+
+ public AndroidFacet addAndroidFacet(Module module, String sdkPath, String platformDir) {
+ return addAndroidFacet(module, sdkPath, platformDir, true);
+ }
+
+ public AndroidFacet addAndroidFacet(Module module, String sdkPath, String platformDir, boolean addSdk) {
+ FacetManager facetManager = FacetManager.getInstance(module);
+ AndroidFacet facet = facetManager.createFacet(AndroidFacet.getFacetType(), "Android", null);
+
+ if (addSdk) {
+ addAndroidSdk(module, sdkPath, platformDir);
+ }
+ final ModifiableFacetModel facetModel = facetManager.createModifiableModel();
+ facetModel.addFacet(facet);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ facetModel.commit();
+ }
+ });
+ return facet;
+ }
+
+ protected void doGlobalInspectionTest(@NotNull GlobalInspectionTool inspection,
+ @NotNull String globalTestDir,
+ @NotNull AnalysisScope scope) {
+ doGlobalInspectionTest(new GlobalInspectionToolWrapper(inspection), globalTestDir, scope);
+ }
+
+ protected void doGlobalInspectionTest(@NotNull GlobalInspectionToolWrapper wrapper,
+ @NotNull String globalTestDir,
+ @NotNull AnalysisScope scope) {
+ myFixture.enableInspections(wrapper.getTool());
+
+ scope.invalidate();
+
+ final InspectionManagerEx inspectionManager = (InspectionManagerEx)InspectionManager.getInstance(getProject());
+ final GlobalInspectionContextImpl globalContext =
+ CodeInsightTestFixtureImpl.createGlobalContextForTool(scope, getProject(), inspectionManager, wrapper);
+
+ InspectionTestUtil.runTool(wrapper, scope, globalContext, inspectionManager);
+ InspectionTestUtil.compareToolResults(globalContext, wrapper, false, getTestDataPath() + globalTestDir);
+ }
+
+ protected static class MyAdditionalModuleData {
+ final JavaModuleFixtureBuilder myModuleFixtureBuilder;
+ final String myDirName;
+ final boolean myLibrary;
+
+ private MyAdditionalModuleData(@NotNull JavaModuleFixtureBuilder moduleFixtureBuilder,
+ @NotNull String dirName,
+ boolean library) {
+ myModuleFixtureBuilder = moduleFixtureBuilder;
+ myDirName = dirName;
+ myLibrary = library;
+ }
+ }
+}
diff --git a/idea/tests/org/jetbrains/jet/android/KotlinAndroidTestCaseBase.java b/idea/tests/org/jetbrains/jet/android/KotlinAndroidTestCaseBase.java
new file mode 100644
index 00000000000..2b398de46f7
--- /dev/null
+++ b/idea/tests/org/jetbrains/jet/android/KotlinAndroidTestCaseBase.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2013 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 org.jetbrains.jet.android;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.tools.idea.rendering.ResourceHelper;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkModificator;
+import com.intellij.openapi.roots.JavadocOrderRootType;
+import com.intellij.openapi.roots.ModuleRootModificationUtil;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.util.Segment;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.xml.XmlAttributeValue;
+import com.intellij.testFramework.IdeaTestCase;
+import com.intellij.testFramework.UsefulTestCase;
+import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
+import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper;
+import org.jetbrains.android.sdk.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jet.plugin.PluginTestCaseBase;
+
+import java.io.File;
+
+@SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"})
+public abstract class KotlinAndroidTestCaseBase extends UsefulTestCase {
+ /** Environment variable or system property containing the full path to an SDK install */
+ public static final String SDK_PATH_PROPERTY = "ADT_TEST_SDK_PATH";
+
+ /** Environment variable or system property pointing to the directory name of the platform inside $sdk/platforms, e.g. "android-17" */
+ public static final String PLATFORM_DIR_PROPERTY = "ADT_TEST_PLATFORM";
+
+ protected JavaCodeInsightTestFixture myFixture;
+
+ protected KotlinAndroidTestCaseBase() {
+ IdeaTestCase.initPlatformPrefix();
+ }
+
+ public String getAbsoluteTestDataPath() {
+ // The following code doesn't work right now that the Android
+ // plugin lives in a separate place:
+ //String androidHomePath = System.getProperty("android.home.path");
+ //if (androidHomePath == null) {
+ // androidHomePath = new File(PathManager.getHomePath(), "android/android").getPath();
+ //}
+ //return PathUtil.getCanonicalPath(androidHomePath + "/testData");
+
+ // getTestDataPath already gives the absolute path anyway:
+ String path = getTestDataPath();
+ assertTrue(new File(path).isAbsolute());
+ return path;
+ }
+
+ public String getTestDataPath() {
+ return PluginTestCaseBase.getTestDataPathBase();
+ }
+
+ public static String getAndroidPluginHome() {
+ // Now that the Android plugin is kept in a separate place, we need to look in
+ // a relative position instead
+ String adtPath = PathManager.getHomePath() + "/../adt/idea/android";
+ if (new File(adtPath).exists()) {
+ return adtPath;
+ }
+ return new File(PathManager.getHomePath(), "android/android").getPath();
+ }
+
+ public String getDefaultTestSdkPath() {
+ return getTestDataPath() + "/sdk1.5";
+ }
+
+ public String getDefaultPlatformDir() {
+ return "android-1.5";
+ }
+
+ protected String getTestSdkPath() {
+ if (requireRecentSdk()) {
+ String override = System.getProperty(SDK_PATH_PROPERTY);
+ if (override != null) {
+ assertTrue("Must also define " + PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY) != null);
+ assertTrue(override, new File(override).exists());
+ return override;
+ }
+ override = System.getenv(SDK_PATH_PROPERTY);
+ if (override != null) {
+ assertTrue("Must also define " + PLATFORM_DIR_PROPERTY, System.getenv(PLATFORM_DIR_PROPERTY) != null);
+ return override;
+ }
+ fail("This unit test requires " + SDK_PATH_PROPERTY + " and " + PLATFORM_DIR_PROPERTY + " to be defined.");
+ }
+
+ return getDefaultTestSdkPath();
+ }
+
+ protected String getPlatformDir() {
+ if (requireRecentSdk()) {
+ String override = System.getProperty(PLATFORM_DIR_PROPERTY);
+ if (override != null) {
+ return override;
+ }
+ override = System.getenv(PLATFORM_DIR_PROPERTY);
+ if (override != null) {
+ return override;
+ }
+ fail("This unit test requires " + SDK_PATH_PROPERTY + " and " + PLATFORM_DIR_PROPERTY + " to be defined.");
+ }
+ return getDefaultPlatformDir();
+ }
+
+ /** Is the bundled (incomplete) SDK install adequate or do we need to find a valid install? */
+ protected boolean requireRecentSdk() {
+ return false;
+ }
+
+ protected void addAndroidSdk(Module module, String sdkPath, String platformDir) {
+ Sdk androidSdk = createAndroidSdk(sdkPath, platformDir);
+ ModuleRootModificationUtil.setModuleSdk(module, androidSdk);
+ }
+
+ public Sdk createAndroidSdk(String sdkPath, String platformDir) {
+ Sdk sdk = ProjectJdkTable.getInstance().createSdk("android_test_sdk", AndroidSdkType.getInstance());
+ SdkModificator sdkModificator = sdk.getSdkModificator();
+ sdkModificator.setHomePath(sdkPath);
+
+ VirtualFile androidJar;
+ if (platformDir.equals(getDefaultPlatformDir())) {
+ // Compatibility: the unit tests were using android.jar outside the sdk1.5 install;
+ // we need to use that one, rather than the real one in sdk1.5, in order for the
+ // tests to pass. Longer term, we should switch the unit tests over to all using
+ // a valid SDK.
+ String androidJarPath = sdkPath + "/../android.jar!/";
+ androidJar = JarFileSystem.getInstance().findFileByPath(androidJarPath);
+ } else {
+ androidJar = LocalFileSystem.getInstance().findFileByPath(sdkPath + "/platforms/" + platformDir + "/android.jar");
+ }
+ sdkModificator.addRoot(androidJar, OrderRootType.CLASSES);
+
+ VirtualFile resFolder = LocalFileSystem.getInstance().findFileByPath(sdkPath + "/platforms/" + platformDir + "/data/res");
+ sdkModificator.addRoot(resFolder, OrderRootType.CLASSES);
+
+ VirtualFile docsFolder = LocalFileSystem.getInstance().findFileByPath(sdkPath + "/docs/reference");
+ if (docsFolder != null) {
+ sdkModificator.addRoot(docsFolder, JavadocOrderRootType.getInstance());
+ }
+
+ AndroidSdkAdditionalData data = new AndroidSdkAdditionalData(sdk);
+ AndroidSdkData sdkData = AndroidSdkData.getSdkData(sdkPath);
+ assertNotNull(sdkData);
+ IAndroidTarget target = sdkData.findTargetByName("Android 4.2"); // TODO: Get rid of this hardcoded version number
+ if (target == null) {
+ IAndroidTarget[] targets = sdkData.getTargets();
+ for (IAndroidTarget t : targets) {
+ if (t.getLocation().contains(platformDir)) {
+ target = t;
+ break;
+ }
+ }
+ if (target == null && targets.length > 0) {
+ target = targets[targets.length - 1];
+ }
+ }
+ assertNotNull(target);
+ data.setBuildTarget(target);
+ sdkModificator.setSdkAdditionalData(data);
+ sdkModificator.commitChanges();
+ return sdk;
+ }
+
+ protected Project getProject() {
+ return myFixture.getProject();
+ }
+
+ protected void ensureSdkManagerAvailable() {
+ AndroidSdkData sdkData = AndroidSdkUtils.tryToChooseAndroidSdk();
+ if (sdkData == null) {
+ sdkData = createTestSdkManager();
+ if (sdkData != null) {
+ AndroidSdkUtils.setSdkData(sdkData);
+ }
+ }
+ assertNotNull(sdkData);
+ }
+
+ @Nullable
+ protected AndroidSdkData createTestSdkManager() {
+ Sdk androidSdk = createAndroidSdk(getTestSdkPath(), getPlatformDir());
+ AndroidSdkAdditionalData data = (AndroidSdkAdditionalData)androidSdk.getSdkAdditionalData();
+ if (data != null) {
+ AndroidPlatform androidPlatform = data.getAndroidPlatform();
+ if (androidPlatform != null) {
+ // Put default platforms in the list before non-default ones so they'll be looked at first.
+ return androidPlatform.getSdkData();
+ } else {
+ fail("No getAndroidPlatform() associated with the AndroidSdkAdditionalData: " + data);
+ }
+ } else {
+ fail("Could not find data associated with the SDK: " + androidSdk.getName());
+ }
+ return null;
+ }
+
+ /** Returns a description of the given elements, suitable as unit test golden file output */
+ public static String describeElements(@Nullable PsiElement[] elements) {
+ if (elements == null) {
+ return "Empty";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (PsiElement target : elements) {
+ appendElementDescription(sb, target);
+ }
+ return sb.toString();
+ }
+
+ /** Appends a description of the given element, suitable as unit test golden file output */
+ public static void appendElementDescription(@NotNull StringBuilder sb, @NotNull PsiElement element) {
+ if (element instanceof LazyValueResourceElementWrapper) {
+ LazyValueResourceElementWrapper wrapper = (LazyValueResourceElementWrapper)element;
+ XmlAttributeValue value = wrapper.computeElement();
+ if (value != null) {
+ element = value;
+ }
+ }
+ PsiFile file = element.getContainingFile();
+ int offset = element.getTextOffset();
+ TextRange segment = element.getTextRange();
+ appendSourceDescription(sb, file, offset, segment);
+ }
+
+ /** Appends a description of the given elements, suitable as unit test golden file output */
+ public static void appendSourceDescription(@NotNull StringBuilder sb, @Nullable PsiFile file, int offset, @Nullable Segment segment) {
+ if (file != null && segment != null) {
+ if (ResourceHelper.getFolderType(file) != null) {
+ assertNotNull(file.getParent());
+ sb.append(file.getParent().getName());
+ sb.append("/");
+ }
+ sb.append(file.getName());
+ sb.append(':');
+ String text = file.getText();
+ int lineNumber = 1;
+ for (int i = 0; i < offset; i++) {
+ if (text.charAt(i) == '\n') {
+ lineNumber++;
+ }
+ }
+ sb.append(lineNumber);
+ sb.append(":");
+ sb.append('\n');
+ int startOffset = segment.getStartOffset();
+ int endOffset = segment.getEndOffset();
+ assertTrue(offset == -1 || offset >= startOffset);
+ assertTrue(offset == -1 || offset <= endOffset);
+
+ int lineStart = startOffset;
+ while (lineStart > 0 && text.charAt(lineStart - 1) != '\n') {
+ lineStart--;
+ }
+
+ // Skip over leading whitespace
+ while (lineStart < startOffset && Character.isWhitespace(text.charAt(lineStart))) {
+ lineStart++;
+ }
+
+ int lineEnd = startOffset;
+ while (lineEnd < text.length() && text.charAt(lineEnd) != '\n') {
+ lineEnd++;
+ }
+ String indent = " ";
+ sb.append(indent);
+ sb.append(text.substring(lineStart, lineEnd));
+ sb.append('\n');
+ sb.append(indent);
+ for (int i = lineStart; i < lineEnd; i++) {
+ if (i == offset) {
+ sb.append('|');
+ } else if (i >= startOffset && i <= endOffset) {
+ sb.append('~');
+ } else {
+ sb.append(' ');
+ }
+ }
+ } else {
+ sb.append(offset);
+ sb.append(":?");
+ }
+ sb.append('\n');
+ }
+}
+