Skip to content

Dedicated test root for auto-generated tests should be provided #159 #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ import org.utbot.intellij.plugin.ui.utils.kotlinTargetPlatform
import org.utbot.intellij.plugin.ui.utils.parseVersion
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes
import org.utbot.intellij.plugin.ui.utils.addSourceRootIfAbsent
import org.utbot.intellij.plugin.ui.utils.testRootType
import com.intellij.ide.impl.ProjectNewWindowDoNotAskOption
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.service
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.DependencyScope
Expand All @@ -48,10 +50,13 @@ import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.Computable
import com.intellij.openapi.ui.popup.IconButton
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VfsUtilCore.urlToPath
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiMethod
Expand Down Expand Up @@ -80,6 +85,7 @@ import com.intellij.util.ui.JBUI.Borders.empty
import com.intellij.util.ui.JBUI.Borders.merge
import com.intellij.util.ui.JBUI.scale
import com.intellij.util.ui.JBUI.size
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.components.BorderLayoutPanel
import java.awt.BorderLayout
Expand Down Expand Up @@ -274,16 +280,16 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m

private fun getTestRoot() : VirtualFile? {
model.testSourceRoot?.let {
if (it.isDirectory) return it
if (it.isDirectory || it is FakeVirtualFile) return it
}
return null
}

override fun doValidate(): ValidationInfo? {
if (getTestRoot() == null) {
return ValidationInfo("Test source root is not configured", testSourceFolderField.childComponent)
}
if (getRootDirectoryAndContentEntry() == null) {
val testRoot = getTestRoot()
?: return ValidationInfo("Test source root is not configured", testSourceFolderField.childComponent)

if (findReadOnlyContentEntry(testRoot) == null) {
return ValidationInfo("Test source root is located out of content entry", testSourceFolderField.childComponent)
}

Expand Down Expand Up @@ -373,18 +379,33 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
* Creates test source root if absent and target packages for tests.
*/
private fun createTestRootAndPackages(): Boolean {
val (sourceRoot, contentEntry) = getRootDirectoryAndContentEntry() ?: return false
val modifiableModel = ModuleRootManager.getInstance(model.testModule).modifiableModel
VfsUtil.createDirectoryIfMissing(urlToPath(sourceRoot.url))
contentEntry.addSourceFolder(sourceRoot.url, codegenLanguages.item.testRootType())
WriteCommandAction.runWriteCommandAction(model.project) { modifiableModel.commit() }
model.testSourceRoot = createDirectoryIfMissing(model.testSourceRoot)
val testSourceRoot = model.testSourceRoot ?: return false
if (model.testSourceRoot?.isDirectory != true) return false
if (getOrCreateTestRoot(testSourceRoot)) {
if (cbSpecifyTestPackage.isSelected) {
createSelectedPackage(testSourceRoot)
} else {
createPackagesByClasses(testSourceRoot)
}
return true
}
return false
}

if (cbSpecifyTestPackage.isSelected) {
createSelectedPackage(sourceRoot)
private fun createDirectoryIfMissing(dir : VirtualFile?): VirtualFile? {
val file = if (dir is FakeVirtualFile) {
WriteCommandAction.runWriteCommandAction(model.project, Computable<VirtualFile> {
VfsUtil.createDirectoryIfMissing(dir.path)
})
} else {
createPackagesByClasses(sourceRoot)
dir
}?: return null
return if (VfsUtil.virtualToIoFile(file).isFile) {
null
} else {
StandardFileSystems.local().findFileByPath(file.path)
}
return true
}

private fun createPackagesByClasses(testSourceRoot: VirtualFile) {
Expand Down Expand Up @@ -413,14 +434,34 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
"Generation error"
)

private fun getRootDirectoryAndContentEntry() : Pair<VirtualFile, ContentEntry>? {
val testSourceRoot = getTestRoot()?: return null
val contentEntry = ModuleRootManager.getInstance(model.testModule).contentEntries
private fun findReadOnlyContentEntry(testSourceRoot: VirtualFile?): ContentEntry? {
if (testSourceRoot == null) return null
if (testSourceRoot is FakeVirtualFile) {
return findReadOnlyContentEntry(testSourceRoot.parent)
}
return ModuleRootManager.getInstance(model.testModule).contentEntries
.filterNot { it.file == null }
.firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, true) } ?: return null
return Pair(testSourceRoot, contentEntry)
.firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, false) }
}

private fun getOrCreateTestRoot(testSourceRoot: VirtualFile): Boolean {
val modifiableModel = ModuleRootManager.getInstance(model.testModule).modifiableModel
try {
val contentEntry = modifiableModel.contentEntries
.filterNot { it.file == null }
.firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, true) }
?: return false

contentEntry.addSourceRootIfAbsent(
modifiableModel,
testSourceRoot.url,
codegenLanguages.item.testRootType()
)
return true
} finally {
if (modifiableModel.isWritable && !modifiableModel.isDisposed) modifiableModel.dispose()
}

private fun createPackageWrapper(packageName: String?): PackageWrapper =
PackageWrapper(PsiManager.getInstance(model.project), trimPackageName(packageName))

Expand Down Expand Up @@ -648,7 +689,14 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
itemsToHelpTooltip.forEach { (box, tooltip) -> box.setHelpTooltipTextChanger(tooltip) }

testSourceFolderField.childComponent.addActionListener { event ->
model.testSourceRoot = pathToFile((event.source as JComboBox<*>).selectedItem as String)
with((event.source as JComboBox<*>).selectedItem) {
if (this is VirtualFile) {
model.testSourceRoot = this@with
}
else {
model.testSourceRoot = null
}
}
}

mockStrategies.addActionListener { event ->
Expand Down Expand Up @@ -700,14 +748,6 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
}
}

private fun pathToFile(path: String): VirtualFile? {
val relativePath = path.substring(".../".length).replace('\\', '/')
return model.testModule
.suitableTestSourceRoots()
.firstOrNull { it.path.contains(relativePath) }
}


private lateinit var currentFrameworkItem: TestFramework

//We would like to remove JUnit4 from framework list in parametrized mode
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
package org.utbot.intellij.plugin.ui.components

import org.utbot.common.PathUtil
import org.utbot.intellij.plugin.ui.GenerateTestsModel
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.fileChooser.FileChooser
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.ComboboxWithBrowseButton
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ArrayUtil
import java.io.File
import javax.swing.DefaultComboBoxModel
import javax.swing.JList
import org.utbot.common.PathUtil
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.intellij.plugin.ui.GenerateTestsModel
import org.utbot.intellij.plugin.ui.utils.addDedicatedTestRoot
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots

class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : ComboboxWithBrowseButton() {

private val SET_TEST_FOLDER = "set test folder"

init {
childComponent.isEditable = false
childComponent.renderer = object : ColoredListCellRenderer<Any?>() {
override fun customizeCellRenderer(
list: JList<out Any?>,
value: Any?,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
if (value is String) {
append(value)
return
}
if (value is VirtualFile) {
append(formatUrl(value, model))
}
if (value is FakeVirtualFile) {
append(" (will be created)", SimpleTextAttributes.ERROR_ATTRIBUTES)
}
}
}

val testRoots = model.testModule.suitableTestSourceRoots()

val testRoots = model.testModule.suitableTestSourceRoots(CodegenLanguage.JAVA).toMutableList()
model.testModule.addDedicatedTestRoot(testRoots)
if (testRoots.isNotEmpty()) {
configureRootsCombo(testRoots)
} else {
Expand All @@ -34,11 +60,11 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
model.testSourceRoot = it

if (childComponent.itemCount == 1 && childComponent.selectedItem == SET_TEST_FOLDER) {
newItemList(setOf(formatUrl(it, model)))
newItemList(setOf(it))
} else {
//Prepend and select newly added test root
val testRootItems = linkedSetOf(formatUrl(it, model))
testRootItems += (0 until childComponent.itemCount).map { i -> childComponent.getItemAt(i) as String}
val testRootItems = linkedSetOf(it)
testRootItems += (0 until childComponent.itemCount).map { i -> childComponent.getItemAt(i) as VirtualFile}
newItemList(testRootItems)
}
}
Expand All @@ -59,17 +85,19 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
val selectedRoot = testRoots.first()

model.testSourceRoot = selectedRoot
newItemList(testRoots.map { root -> formatUrl(root, model) }.toSet())
newItemList(testRoots.toSet())
}

private fun newItemList(comboItems: Set<String>) {
private fun newItemList(comboItems: Set<Any>) {
childComponent.model = DefaultComboBoxModel(ArrayUtil.toObjectArray(comboItems))
childComponent.putClientProperty("JComponent.outline",
if (comboItems.isNotEmpty() && !comboItems.contains(SET_TEST_FOLDER)) null else DarculaUIUtil.Outline.error)
}

private fun formatUrl(virtualFile: VirtualFile, model: GenerateTestsModel): String {
var directoryUrl = virtualFile.presentableUrl
var directoryUrl = if (virtualFile is FakeVirtualFile) {
virtualFile.parent.presentableUrl + File.separatorChar + virtualFile.name
} else {
virtualFile.presentableUrl
}
@Suppress("DEPRECATION")
val projectHomeUrl = model.project.baseDir.presentableUrl

Expand Down
Loading