diff --git a/platform/external-system-impl/intellij.platform.externalSystem.tests.iml b/platform/external-system-impl/intellij.platform.externalSystem.tests.iml
index d81cbf7e5565..3553348b2048 100644
--- a/platform/external-system-impl/intellij.platform.externalSystem.tests.iml
+++ b/platform/external-system-impl/intellij.platform.externalSystem.tests.iml
@@ -22,5 +22,6 @@
+
\ No newline at end of file
diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemJdkUtil.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemJdkUtil.java
index 4936632eb96c..95de8a0f0f29 100644
--- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemJdkUtil.java
+++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemJdkUtil.java
@@ -7,6 +7,7 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.projectRoots.impl.DependentSdkType;
+import com.intellij.openapi.projectRoots.impl.MockSdk;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtil.kt b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtil.kt
index ed574cbcb004..c79e20ce653e 100644
--- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtil.kt
+++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtil.kt
@@ -5,36 +5,38 @@ package com.intellij.openapi.externalSystem.service.ui
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkException
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil
+import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil.USE_PROJECT_JDK
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.roots.ui.configuration.SdkComboBox
import com.intellij.openapi.roots.ui.configuration.SdkListItem
+import org.jetbrains.annotations.TestOnly
-fun SdkComboBox.getSelectedJdkReference(): String? {
- return when (val it = selectedItem) {
- is SdkListItem.ProjectSdkItem -> ExternalSystemJdkUtil.USE_PROJECT_JDK
- is SdkListItem.SdkItem -> it.sdk.name
- is SdkListItem.InvalidSdkItem -> it.sdkName
+fun SdkComboBox.getSelectedJdkReference() = resolveJdkReference(selectedItem)
+
+private fun resolveJdkReference(item: SdkListItem?): String? {
+ return when (item) {
+ is SdkListItem.ProjectSdkItem -> USE_PROJECT_JDK
+ is SdkListItem.SdkItem -> item.sdk.name
+ is SdkListItem.InvalidSdkItem -> item.sdkName
else -> null
}
}
fun SdkComboBox.setSelectedJdkReference(jdkReference: String?) {
- selectedItem = when (jdkReference) {
- null -> showNoneSdkItem()
- ExternalSystemJdkUtil.USE_PROJECT_JDK -> showProjectSdkItem()
- else -> resolveSdkItem(jdkReference)
- }
+ selectedItem = resolveSdkItem(jdkReference)
}
-private fun SdkComboBox.resolveSdkItem(selectedJdkReference: String): SdkListItem {
+private fun SdkComboBox.resolveSdkItem(jdkReference: String?): SdkListItem {
+ if (jdkReference == null) return showNoneSdkItem()
+ if (jdkReference == USE_PROJECT_JDK) return showProjectSdkItem()
try {
- val selectedJdk = ExternalSystemJdkUtil.resolveJdkName(null, selectedJdkReference)
- if (selectedJdk == null) return showInvalidSdkItem(selectedJdkReference)
+ val selectedJdk = ExternalSystemJdkUtil.resolveJdkName(null, jdkReference)
+ if (selectedJdk == null) return showInvalidSdkItem(jdkReference)
return findSdkItem(selectedJdk) ?: addAndGetSdkItem(selectedJdk)
}
catch (ex: ExternalSystemJdkException) {
- return showInvalidSdkItem(selectedJdkReference)
+ return showInvalidSdkItem(jdkReference)
}
}
@@ -47,4 +49,7 @@ private fun SdkComboBox.addAndGetSdkItem(sdk: Sdk): SdkListItem {
private fun SdkComboBox.findSdkItem(sdk: Sdk): SdkListItem? {
return model.listModel.findSdkItem(sdk)
-}
\ No newline at end of file
+}
+
+@TestOnly
+fun resolveJdkReferenceInTests(item: SdkListItem?) = resolveJdkReference(item)
diff --git a/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTest.kt b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTest.kt
new file mode 100644
index 000000000000..5ff076cda429
--- /dev/null
+++ b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTest.kt
@@ -0,0 +1,50 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.externalSystem.service.ui
+
+import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil.USE_INTERNAL_JAVA
+import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil.USE_PROJECT_JDK
+import com.intellij.openapi.roots.ui.configuration.SdkListItem.*
+
+class ExternalSystemJdkComboBoxUtilTest : ExternalSystemJdkComboBoxUtilTestCase() {
+ fun `test reference usage`() {
+ val sdk1 = createAndRegisterSdk()
+ val sdk2 = createAndRegisterSdk(isProjectSdk = true)
+ val sdk3 = createAndRegisterSdk()
+ val invalidSdk = TestSdkGenerator.createNextSdk()
+ val internalJdk = getInternalJdk()
+
+ val comboBox = createJdkComboBox()
+
+ assertComboBoxContent(comboBox)
+ .reference(USE_PROJECT_JDK)
+ .reference(sdk1)
+ .reference(sdk2)
+ .reference(sdk3)
+ .nothing()
+
+ comboBox.setSelectedJdkReference(USE_PROJECT_JDK)
+ assertComboBoxSelection(comboBox, sdk2, USE_PROJECT_JDK)
+ comboBox.setSelectedJdkReference(sdk1.name)
+ assertComboBoxSelection(comboBox, sdk1, sdk1.name)
+ comboBox.setSelectedJdkReference(sdk2.name)
+ assertComboBoxSelection(comboBox, sdk2, sdk2.name)
+ comboBox.setSelectedJdkReference(sdk3.name)
+ assertComboBoxSelection(comboBox, sdk3, sdk3.name)
+ comboBox.setSelectedJdkReference(invalidSdk.name)
+ assertComboBoxSelection(comboBox, null, invalidSdk.name)
+ comboBox.setSelectedJdkReference(USE_INTERNAL_JAVA)
+ assertComboBoxSelection(comboBox, internalJdk, internalJdk.name)
+ comboBox.setSelectedJdkReference(null)
+ assertComboBoxSelection(comboBox, null, null)
+
+ assertComboBoxContent(comboBox)
+ .reference(null, isSelected = true)
+ .reference(USE_PROJECT_JDK)
+ .reference(invalidSdk.name)
+ .reference(internalJdk)
+ .reference(sdk1)
+ .reference(sdk2)
+ .reference(sdk3)
+ .nothing()
+ }
+}
\ No newline at end of file
diff --git a/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTestCase.kt b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTestCase.kt
new file mode 100644
index 000000000000..0a7ba2a54405
--- /dev/null
+++ b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTestCase.kt
@@ -0,0 +1,40 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.externalSystem.service.ui
+
+import com.intellij.openapi.application.invokeAndWaitIfNeeded
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.projectRoots.JavaSdkType
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl
+import com.intellij.openapi.roots.ui.configuration.SdkComboBox
+import com.intellij.openapi.roots.ui.configuration.SdkComboBoxTestCase
+import com.intellij.openapi.roots.ui.configuration.SdkListItem
+
+abstract class ExternalSystemJdkComboBoxUtilTestCase : SdkComboBoxTestCase() {
+
+ override fun tearDown() {
+ invokeAndWaitIfNeeded {
+ runWriteAction {
+ JavaAwareProjectJdkTableImpl.removeInternalJdkInTests()
+ }
+ }
+ super.tearDown()
+ }
+
+ fun createJdkComboBox() = createComboBox { it is JavaSdkType }
+
+ fun ComboBoxChecker.reference(sdk: Sdk, isSelected: Boolean = false) =
+ reference(sdk.name, isSelected)
+
+ fun ComboBoxChecker.reference(reference: String?, isSelected: Boolean = false) =
+ item(isSelected) {
+ assertEquals(reference, resolveJdkReferenceInTests(it))
+ }
+
+ fun getInternalJdk() = JavaAwareProjectJdkTableImpl.getInstanceEx().internalJdk
+
+ inline fun assertComboBoxSelection(comboBox: SdkComboBox, expectedSdk: Sdk?, expectedReference: String?) {
+ assertComboBoxSelection(comboBox, expectedSdk)
+ assertEquals(expectedReference, comboBox.getSelectedJdkReference())
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkComboBox.kt b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkComboBox.kt
index b9995da28be5..54ac3d7c4eb7 100644
--- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkComboBox.kt
+++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkComboBox.kt
@@ -15,12 +15,21 @@ class SdkComboBox(model: SdkComboBoxModel) : SdkComboBoxBase(model.
setModel(model.copyAndSetListModel(listModel))
}
+ override fun getSelectedItem(): SdkListItem? {
+ return super.getSelectedItem() as SdkListItem?
+ }
+
override fun setSelectedItem(anObject: Any?) {
if (anObject is SdkListItem) {
if (myModel.executeAction(this, anObject, ::setSelectedItem)) {
return
}
}
+ when (anObject) {
+ is SdkListItem.ProjectSdkItem -> showProjectSdkItem()
+ is SdkListItem.InvalidSdkItem -> showInvalidSdkItem(anObject.sdkName)
+ is SdkListItem.NoneSdkItem -> showNoneSdkItem()
+ }
reloadModel()
super.setSelectedItem(anObject)
}
@@ -37,12 +46,14 @@ class SdkComboBox(model: SdkComboBoxModel) : SdkComboBoxBase(model.
fun getSelectedSdk(): Sdk? {
return when (val it = selectedItem) {
- is SdkListItem.ProjectSdkItem -> model.sdksModel.projectSdk
- is SdkListItem.SdkItem -> it.sdk
+ is SdkListItem.ProjectSdkItem -> findSdk(model.sdksModel.projectSdk)
+ is SdkListItem.SdkItem -> findSdk(it.sdk)
else -> null
}
}
+ private fun findSdk(sdk: Sdk?) = model.sdksModel.findSdk(sdk)
+
init {
setModel(model)
setRenderer(SdkListPresenter { this@SdkComboBox.model.listModel })
diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java
index 63fceeca8bdc..fd3ae74d71c1 100644
--- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java
+++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java
@@ -2,6 +2,7 @@
package com.intellij.openapi.roots.ui.configuration;
import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
@@ -58,6 +59,17 @@ public class SdkDetector {
return;
}
+ /*
+ TODO[jo] fix: deadlock in combobox tests on {@link SdkDetector#myPublicationLock}
+ detection must be called from edt {@link SdkDetector#getDetectedSdksWithUpdate}
+ detection is synchronous for unit tests {@link com.intellij.openapi.progress.impl.CoreProgressManager#run}
+ */
+ Application application = ApplicationManager.getApplication();
+ if (application.isUnitTestMode() || application.isHeadlessEnvironment()) {
+ LOG.warn("Sdks detection is skipped, because deadlock is coming for synchronous detection");
+ return;
+ }
+
EdtDetectedSdkListener actualListener = new EdtDetectedSdkListener(callbackModality, listener);
synchronized (myPublicationLock) {
//skip multiple registrations
diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkListItem.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkListItem.java
index 0abe217aea6a..46487eef4bf1 100644
--- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkListItem.java
+++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkListItem.java
@@ -121,14 +121,14 @@ public abstract class SdkListItem {
}
}
- enum ActionRole {
+ public enum ActionRole {
DOWNLOAD, ADD
}
public static final class ActionItem extends SdkListItem {
@Nullable final GroupItem myGroup;
@NotNull final ActionRole myRole;
- @NotNull final NewSdkAction myAction;
+ @NotNull final NewSdkAction myAction;
ActionItem(@NotNull ActionRole role, @NotNull NewSdkAction action, @Nullable GroupItem group) {
myRole = role;
@@ -136,6 +136,16 @@ public abstract class SdkListItem {
myGroup = group;
}
+ @NotNull
+ public ActionRole getRole() {
+ return myRole;
+ }
+
+ @NotNull
+ public NewSdkAction getAction() {
+ return myAction;
+ }
+
@NotNull
ActionItem withGroup(@NotNull GroupItem group) {
return new ActionItem(myRole, myAction, group);
diff --git a/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTest.kt b/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTest.kt
new file mode 100644
index 000000000000..e1981fec490c
--- /dev/null
+++ b/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTest.kt
@@ -0,0 +1,116 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.roots.ui.configuration
+
+import com.intellij.openapi.projectRoots.ProjectJdkTable
+import com.intellij.openapi.projectRoots.Sdk
+
+class SdkComboBoxTest : SdkComboBoxTestCase() {
+
+ fun `test simple usage`() {
+ val sdk1 = createAndRegisterSdk()
+ val sdk2 = createAndRegisterSdk(isProjectSdk = true)
+ val sdk3 = createAndRegisterSdk()
+ val sdk4 = TestSdkGenerator.createNextSdk()
+ val comboBox = createComboBox()
+
+ assertComboBoxContent(comboBox)
+ .item { assertEquals(sdk2, comboBox.getProjectSdk()) }
+ .item { assertSdkItem(sdk1, it) }
+ .item { assertSdkItem(sdk2, it) }
+ .item { assertSdkItem(sdk3, it) }
+ .nothing()
+
+ assertComboBoxSelection(comboBox, null)
+ comboBox.setSelectedSdk(sdk1)
+ assertComboBoxSelection(comboBox, sdk1)
+ comboBox.setSelectedSdk(sdk2)
+ assertComboBoxSelection(comboBox, sdk2)
+ comboBox.setSelectedSdk(sdk3)
+ assertComboBoxSelection(comboBox, sdk3)
+ comboBox.setSelectedSdk(sdk4)
+ assertComboBoxSelection(comboBox, null)
+ comboBox.setSelectedSdk(null)
+ assertComboBoxSelection(comboBox, null)
+
+ assertComboBoxContent(comboBox)
+ .item(isSelected = true)
+ .item { assertEquals(sdk2, comboBox.getProjectSdk()) }
+ .item { assertEquals(sdk4.name, it.sdkName) }
+ .item { assertSdkItem(sdk1, it) }
+ .item { assertSdkItem(sdk2, it) }
+ .item { assertSdkItem(sdk3, it) }
+ .nothing()
+
+ comboBox.setSelectedItem(SdkListItem.ProjectSdkItem())
+ assertComboBoxSelection(comboBox, sdk2)
+ comboBox.setSelectedItem(SdkListItem.NoneSdkItem())
+ assertComboBoxSelection(comboBox, null)
+ comboBox.setSelectedItem(SdkListItem.InvalidSdkItem("invalid sdk"))
+ assertComboBoxSelection(comboBox, null)
+
+ assertComboBoxContent(comboBox)
+ .item()
+ .item { assertEquals(sdk2, comboBox.getProjectSdk()) }
+ .item(isSelected = true) { assertEquals("invalid sdk", it.sdkName) }
+ .item { assertSdkItem(sdk1, it) }
+ .item { assertSdkItem(sdk2, it) }
+ .item { assertSdkItem(sdk3, it) }
+ .nothing()
+ }
+
+ fun `test combobox actions`() {
+ val comboBox = createComboBox()
+ .withOpenDropdownPopup()
+
+ assertComboBoxContent(comboBox)
+ .item { assertActionItem(SdkListItem.ActionRole.DOWNLOAD, it) }
+ .item { assertActionItem(SdkListItem.ActionRole.ADD, it) }
+ .nothing()
+
+ val download1 = comboBox.touchDownloadAction()
+ val download2 = comboBox.touchDownloadAction()
+ val download3 = comboBox.touchDownloadAction()
+ val download4 = comboBox.touchDownloadAction()
+
+ assertComboBoxContent(comboBox)
+ .item { assertSdkItem(download1, it) }
+ .item { assertSdkItem(download2, it) }
+ .item { assertSdkItem(download3, it) }
+ .item(isSelected = true) { assertSdkItem(download4, it) }
+ .item { assertActionItem(SdkListItem.ActionRole.DOWNLOAD, it) }
+ .item { assertActionItem(SdkListItem.ActionRole.ADD, it) }
+ .nothing()
+
+ val add1 = comboBox.touchAddAction()
+ val add2 = comboBox.touchAddAction()
+ val add3 = comboBox.touchAddAction()
+ val add4 = comboBox.touchAddAction()
+
+ assertComboBoxContent(comboBox)
+ .item { assertSdkItem(download1, it) }
+ .item { assertSdkItem(download2, it) }
+ .item { assertSdkItem(download3, it) }
+ .item { assertSdkItem(download4, it) }
+ .item { assertSdkItem(add1, it) }
+ .item { assertSdkItem(add2, it) }
+ .item { assertSdkItem(add3, it) }
+ .item(isSelected = true) { assertSdkItem(add4, it) }
+ .item { assertActionItem(SdkListItem.ActionRole.DOWNLOAD, it) }
+ .item { assertActionItem(SdkListItem.ActionRole.ADD, it) }
+ .nothing()
+
+ assertCollectionContent(comboBox.model.sdksModel.projectSdks.values.sortedBy { it.name })
+ .element { assertSdk(download1, it) }
+ .element { assertSdk(download2, it) }
+ .element { assertSdk(download3, it) }
+ .element { assertSdk(download4, it) }
+ .element { assertSdk(add1, it) }
+ .element { assertSdk(add2, it) }
+ .element { assertSdk(add3, it) }
+ .element { assertSdk(add4, it) }
+ .nothing()
+
+ assertCollectionContent(ProjectJdkTable.getInstance().getSdksOfType(TestSdkType))
+ .nothing()
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTestCase.kt b/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTestCase.kt
new file mode 100644
index 000000000000..5f3c7a276c71
--- /dev/null
+++ b/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTestCase.kt
@@ -0,0 +1,208 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.roots.ui.configuration
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.invokeAndWaitIfNeeded
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.projectRoots.ProjectJdkTable
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.SdkType
+import com.intellij.openapi.projectRoots.SdkTypeId
+import com.intellij.openapi.roots.ui.configuration.SdkComboBoxModel.Companion.createSdkComboBoxModel
+import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
+import com.intellij.openapi.util.Disposer
+import java.util.function.Predicate
+
+abstract class SdkComboBoxTestCase : SdkTestCase() {
+
+ fun createComboBox(filter: (SdkTypeId) -> Boolean = { true }): SdkComboBox {
+ val sdksModel = TestProjectSdksModel()
+ sdksModel.reset(project)
+ Disposer.register(project, sdksModel)
+ val model = createSdkComboBoxModel(project, sdksModel, Predicate { TestSdkType == it || filter(it) })
+ return SdkComboBox(model)
+ }
+
+ class ComboBoxChecker(val comboBox: SdkComboBox) :
+ CollectionChecker(comboBox.itemSequence.iterator(), comboBox.dumpToString()) {
+
+ inline fun item(
+ isSelected: Boolean = false,
+ noinline assertItem: SdkComboBox.(I) -> Unit = {}
+ ): ComboBoxChecker {
+ element {
+ assertSelection(comboBox, it, isSelected)
+ comboBox.assertItem(it)
+ }
+ return this
+ }
+
+ fun assertSelection(comboBox: SdkComboBox, elementToCheck: SdkListItem, mustBeSelected: Boolean) {
+ if (mustBeSelected) {
+ val message = "'${comboBox.dumpToString(elementToCheck)}' must be selected"
+ assertTrue(message, comboBox.selectedItem == elementToCheck)
+ }
+ else {
+ val message = "'${comboBox.dumpToString(elementToCheck)}' must not be selected"
+ assertFalse(message, comboBox.selectedItem == elementToCheck)
+ }
+ }
+ }
+
+ open class CollectionChecker(val iterator: Iterator, val dump: String) {
+
+ inline fun element(noinline assertItem: (I) -> Unit = {}): CollectionChecker {
+ assertTrue(dump, iterator.hasNext())
+ val element = iterator.next()
+ val item = assertIsInstance(dump, element)
+ withMessageIfException { assertItem(item) }
+ return this
+ }
+
+ fun nothing() {
+ assertFalse(dump, iterator.hasNext())
+ }
+
+ fun withMessageIfException(action: () -> R): R {
+ try {
+ return action()
+ }
+ catch (ex: Throwable) {
+ System.err.println("${ex::class.java.name}: $dump")
+ throw ex
+ }
+ }
+ }
+
+ /**
+ * Works in team with:
+ * [SdkConfigurationUtil.selectSdkHome(SdkType, Component, Consumer)],
+ * [SdkComboBoxTestCase.TestProjectSdksModel]
+ */
+ object CanarySdk : TestSdk("canary", "canary-home", "canary-version") {
+ fun replaceByTestSdk(action: () -> R): R {
+ return invokeAndWaitIfNeeded {
+ runWriteAction {
+ val projectSdkTable = ProjectJdkTable.getInstance()
+ projectSdkTable.addJdk(CanarySdk)
+ try {
+ action()
+ }
+ finally {
+ projectSdkTable.removeJdk(CanarySdk)
+ }
+ }
+ }
+ }
+ }
+
+ class TestProjectSdksModel : ProjectSdksModel(), Disposable {
+ override fun addSdk(type: SdkType, home: String, callback: com.intellij.util.Consumer?) {
+ if (home == CanarySdk.homePath) {
+ val sdk = TestSdkGenerator.createNextSdk()
+ setupSdk(sdk, callback)
+ }
+ else {
+ super.addSdk(type, home, callback)
+ }
+ }
+
+ private fun setupSdk(newJdk: TestSdk, callback: com.intellij.util.Consumer?) {
+ val sdkType = newJdk.sdkType as SdkType
+ if (!sdkType.setupSdkPaths(newJdk, this)) return
+ doAdd(newJdk, callback)
+ }
+
+ override fun reset(project: Project?) {
+ disposeEditableSdks()
+ super.reset(project)
+ }
+
+ override fun dispose() = disposeEditableSdks()
+
+ private fun disposeEditableSdks() {
+ for (editable in projectSdks.values) {
+ if (editable is Disposable) {
+ Disposer.dispose(editable)
+ }
+ }
+ }
+ }
+
+ companion object {
+ fun assertSdkItem(expected: Sdk, item: SdkListItem.SdkItem) {
+ assertSdk(expected, item.sdk)
+ }
+
+ fun assertActionItem(role: SdkListItem.ActionRole, item: SdkListItem.ActionItem) {
+ assertEquals(role, item.role)
+ assertEquals(TestSdkType, item.action.sdkType)
+ }
+
+ inline fun assertComboBoxSelection(comboBox: SdkComboBox, expectedSdk: Sdk?) {
+ assertEquals(comboBox.dumpToString(), expectedSdk, comboBox.getSelectedSdk())
+ assertIsInstance(comboBox.dumpToString(), comboBox.getSelectedItem())
+ }
+
+ fun assertComboBoxContent(comboBox: SdkComboBox) = ComboBoxChecker(comboBox)
+
+ fun assertCollectionContent(collection: Collection<*>) = CollectionChecker(collection.iterator(), collection.toString())
+
+ inline fun assertIsInstance(message: String, o: Any?): T {
+ requireNotNull(o) { message }
+ assertTrue("Expected instance of: ${T::class.java.name} actual: ${o::class.java.name}, $message", o is T)
+ return o as T
+ }
+
+ fun SdkComboBox.getProjectSdk() = model.sdksModel.projectSdk?.let { TestSdkGenerator.findTestSdk(it) }
+
+ val SdkComboBox.itemSequence
+ get() = sequence {
+ val model = getModel()
+ for (i in 0 until model.getSize()) {
+ yield(model.getElementAt(i))
+ }
+ }
+
+ fun SdkComboBox.touchDownloadAction(): TestSdk {
+ selectedItem = itemSequence
+ .filterIsInstance()
+ .first { it.role == SdkListItem.ActionRole.DOWNLOAD }
+ return TestSdkGenerator.getCurrentSdk()
+ }
+
+ fun SdkComboBox.touchAddAction(): TestSdk {
+ CanarySdk.replaceByTestSdk {
+ selectedItem = itemSequence
+ .filterIsInstance()
+ .first { it.role == SdkListItem.ActionRole.ADD }
+ }
+ return TestSdkGenerator.getCurrentSdk()
+ }
+
+ fun SdkComboBox.withOpenDropdownPopup(): SdkComboBox {
+ firePopupMenuWillBecomeVisible()
+ return this
+ }
+
+ fun SdkComboBox.dumpToString(): String {
+ val elements = itemSequence.joinToString { (if (selectedItem == it) "* " else "") + dumpToString(it) }
+ return "ComboBox { $elements }"
+ }
+
+ fun SdkComboBox.dumpToString(element: SdkListItem?): String {
+ return when (element) {
+ is SdkListItem.NoneSdkItem -> "[none]"
+ is SdkListItem.ProjectSdkItem -> "[project] ${getProjectSdk()?.name}"
+ is SdkListItem.InvalidSdkItem -> "[invalid] ${element.sdkName}"
+ is SdkListItem.SdkItem -> "[sdk] ${element.sdk.name}"
+ is SdkListItem.SuggestedItem -> "[suggested] ${element.homePath}"
+ is SdkListItem.ActionItem -> "[action] ${element.myRole} ${element.myAction.sdkType.name}"
+ is SdkListItem.GroupItem -> "[group] {${element.mySubItems.joinToString { dumpToString(it) }}}"
+ null -> "null"
+ else -> element::class.java.name
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt b/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt
new file mode 100644
index 000000000000..65c87dca3a95
--- /dev/null
+++ b/platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt
@@ -0,0 +1,154 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.roots.ui.configuration
+
+import com.intellij.openapi.application.invokeAndWaitIfNeeded
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.projectRoots.*
+import com.intellij.openapi.projectRoots.impl.MockSdk
+import com.intellij.openapi.roots.ProjectRootManager
+import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownload
+import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownloadTask
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.testFramework.LightPlatformTestCase
+import com.intellij.util.containers.MultiMap
+import org.jdom.Element
+import java.io.File
+import java.util.*
+import java.util.function.Consumer
+import javax.swing.JComponent
+import kotlin.collections.LinkedHashMap
+
+abstract class SdkTestCase : LightPlatformTestCase() {
+
+ override fun setUp() {
+ super.setUp()
+
+ TestSdkGenerator.reset()
+ SdkType.EP_NAME.getPoint(null)
+ .registerExtension(TestSdkType, project)
+ SdkDownload.EP_NAME.getPoint(null)
+ .registerExtension(TestSdkDownloader, project)
+ }
+
+ override fun tearDown() {
+ Disposer.disposeChildren(project)
+ closeAndDeleteProject()
+
+ super.tearDown()
+ }
+
+ fun createAndRegisterSdk(isProjectSdk: Boolean = false): Sdk {
+ val sdk = TestSdkGenerator.createNextSdk()
+ registerSdk(sdk)
+ if (isProjectSdk) {
+ setProjectSdk(sdk)
+ }
+ return sdk
+ }
+
+ private fun registerSdk(sdk: Sdk) {
+ invokeAndWaitIfNeeded {
+ runWriteAction {
+ val jdkTable = ProjectJdkTable.getInstance()
+ jdkTable.addJdk(sdk, project)
+ }
+ }
+ }
+
+ private fun setProjectSdk(sdk: Sdk) {
+ invokeAndWaitIfNeeded {
+ runWriteAction {
+ val rootManager = ProjectRootManager.getInstance(project)
+ rootManager.projectSdk = sdk
+ }
+ }
+ }
+
+ object TestSdkType : SdkType("test-type"), JavaSdkType {
+ override fun getPresentableName(): String = name
+ override fun isValidSdkHome(path: String?): Boolean = true
+ override fun suggestSdkName(currentSdkName: String?, sdkHome: String?): String = "sdk-name"
+ override fun suggestHomePath(): String? = null
+ override fun createAdditionalDataConfigurable(sdkModel: SdkModel, sdkModificator: SdkModificator): AdditionalDataConfigurable? = null
+ override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {}
+ override fun getBinPath(sdk: Sdk): String = File(sdk.homePath, "bin").path
+ override fun getToolsPath(sdk: Sdk): String = File(sdk.homePath, "lib/tools.jar").path
+ override fun getVMExecutablePath(sdk: Sdk): String = File(sdk.homePath, "bin/java").path
+ }
+
+ open class TestSdk(name: String, homePath: String, versionString: String)
+ : MockSdk(name, homePath, versionString, MultiMap(), TestSdkType) {
+ override fun getHomePath(): String = super.getHomePath()!!
+ }
+
+ object TestSdkDownloader : SdkDownload {
+ override fun supportsDownload(sdkTypeId: SdkTypeId) = sdkTypeId == TestSdkType
+
+ override fun showDownloadUI(
+ sdkTypeId: SdkTypeId,
+ sdkModel: SdkModel,
+ parentComponent: JComponent,
+ selectedSdk: Sdk?,
+ sdkCreatedCallback: Consumer
+ ) {
+ val sdk = TestSdkGenerator.createNextSdk()
+ sdkCreatedCallback.accept(object : SdkDownloadTask {
+ override fun doDownload(indicator: ProgressIndicator) {}
+ override fun getPlannedVersion() = sdk.versionString
+ override fun getSuggestedSdkName() = sdk.name
+ override fun getPlannedHomeDir() = sdk.homePath
+ })
+ }
+ }
+
+ object TestSdkGenerator {
+ private var createdSdkCounter = 0
+ private lateinit var createdSdks: MutableMap
+
+ fun findTestSdk(sdk: Sdk): TestSdk? = createdSdks[sdk.name]
+
+ fun getCurrentSdk() = createdSdks.values.last()
+
+ fun createNextSdk(): TestSdk {
+ val name = "test-name (${createdSdkCounter++})"
+ val versionString = "11"
+ val homePath = FileUtil.getTempDirectory() + "/jdk-$name"
+ generateJdkStructure(homePath, versionString)
+ createdSdks[name] = TestSdk(name, homePath, versionString)
+ return getCurrentSdk()
+ }
+
+ private fun generateJdkStructure(homePath: String, versionString: String) {
+ createFile("$homePath/release")
+ createFile("$homePath/jre/lib/rt.jar")
+ createFile("$homePath/bin/javac")
+ createFile("$homePath/bin/java")
+ val properties = Properties()
+ properties.setProperty("JAVA_FULL_VERSION", versionString)
+ File("$homePath/release").outputStream().use {
+ properties.store(it, null)
+ }
+ }
+
+ private fun createFile(path: String) {
+ val file = File(path)
+ file.parentFile.mkdirs()
+ file.createNewFile()
+ }
+
+ fun reset() {
+ createdSdkCounter = 0
+ createdSdks = LinkedHashMap()
+ }
+ }
+
+ companion object {
+ fun assertSdk(expected: Sdk, actual: Sdk) {
+ assertEquals(expected.name, actual.name)
+ assertEquals(expected.sdkType, actual.sdkType)
+ assertEquals(expected, TestSdkGenerator.findTestSdk(actual))
+ }
+ }
+}
\ No newline at end of file