From f9d6ab9cd74c327e410cfcddf23dbbcf54a3b303 Mon Sep 17 00:00:00 2001 From: Sergei Vorobyov Date: Tue, 31 Dec 2019 13:15:55 +0300 Subject: [PATCH] [sdk] added tests for SdkComboBox.kt and for ES combobox util GitOrigin-RevId: 7f53b2b1a4ea117dae49853c4d9484f196b6a67a --- ...intellij.platform.externalSystem.tests.iml | 1 + .../execution/ExternalSystemJdkUtil.java | 1 + .../ui/ExternalSystemJdkComboBoxUtil.kt | 35 +-- .../ui/ExternalSystemJdkComboBoxUtilTest.kt | 50 +++++ .../ExternalSystemJdkComboBoxUtilTestCase.kt | 40 ++++ .../roots/ui/configuration/SdkComboBox.kt | 15 +- .../roots/ui/configuration/SdkDetector.java | 12 + .../roots/ui/configuration/SdkListItem.java | 14 +- .../roots/ui/configuration/SdkComboBoxTest.kt | 116 ++++++++++ .../ui/configuration/SdkComboBoxTestCase.kt | 208 ++++++++++++++++++ .../roots/ui/configuration/SdkTestCase.kt | 154 +++++++++++++ 11 files changed, 627 insertions(+), 19 deletions(-) create mode 100644 platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTest.kt create mode 100644 platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/service/ui/ExternalSystemJdkComboBoxUtilTestCase.kt create mode 100644 platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTest.kt create mode 100644 platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkComboBoxTestCase.kt create mode 100644 platform/lang-impl/testSources/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt 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