Skip to content

Commit 272da22

Browse files
Vassiliy-Kudryashovdtim
authored andcommitted
Dedicated test root for auto-generated tests should be provided #159 (#304)
1 parent 2840303 commit 272da22

File tree

3 files changed

+190
-86
lines changed

3 files changed

+190
-86
lines changed

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,14 @@ import org.utbot.intellij.plugin.ui.utils.findFrameworkLibrary
2828
import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath
2929
import org.utbot.intellij.plugin.ui.utils.kotlinTargetPlatform
3030
import org.utbot.intellij.plugin.ui.utils.parseVersion
31-
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
3231
import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes
32+
import org.utbot.intellij.plugin.ui.utils.addSourceRootIfAbsent
3333
import org.utbot.intellij.plugin.ui.utils.testRootType
3434
import com.intellij.ide.impl.ProjectNewWindowDoNotAskOption
3535
import com.intellij.openapi.application.runWriteAction
3636
import com.intellij.openapi.command.WriteCommandAction
3737
import com.intellij.openapi.components.service
3838
import com.intellij.openapi.options.ShowSettingsUtil
39-
import com.intellij.openapi.projectRoots.JavaSdkVersion
4039
import com.intellij.openapi.roots.ContentEntry
4140
import com.intellij.openapi.roots.DependencyScope
4241
import com.intellij.openapi.roots.ExternalLibraryDescriptor
@@ -48,10 +47,12 @@ import com.intellij.openapi.ui.DialogPanel
4847
import com.intellij.openapi.ui.DialogWrapper
4948
import com.intellij.openapi.ui.Messages
5049
import com.intellij.openapi.ui.ValidationInfo
51-
import com.intellij.openapi.ui.popup.IconButton
50+
import com.intellij.openapi.util.Computable
51+
import com.intellij.openapi.vfs.StandardFileSystems
5252
import com.intellij.openapi.vfs.VfsUtil
5353
import com.intellij.openapi.vfs.VfsUtilCore.urlToPath
5454
import com.intellij.openapi.vfs.VirtualFile
55+
import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile
5556
import com.intellij.psi.PsiClass
5657
import com.intellij.psi.PsiManager
5758
import com.intellij.psi.PsiMethod
@@ -74,14 +75,9 @@ import com.intellij.ui.layout.Row
7475
import com.intellij.ui.layout.panel
7576
import com.intellij.util.IncorrectOperationException
7677
import com.intellij.util.io.exists
77-
import com.intellij.util.lang.JavaVersion
78-
import com.intellij.util.ui.JBUI
79-
import com.intellij.util.ui.JBUI.Borders.empty
80-
import com.intellij.util.ui.JBUI.Borders.merge
81-
import com.intellij.util.ui.JBUI.scale
8278
import com.intellij.util.ui.JBUI.size
79+
import com.intellij.util.ui.JBUI
8380
import com.intellij.util.ui.UIUtil
84-
import com.intellij.util.ui.components.BorderLayoutPanel
8581
import java.awt.BorderLayout
8682
import java.nio.file.Files
8783
import java.nio.file.Path
@@ -274,16 +270,16 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
274270

275271
private fun getTestRoot() : VirtualFile? {
276272
model.testSourceRoot?.let {
277-
if (it.isDirectory) return it
273+
if (it.isDirectory || it is FakeVirtualFile) return it
278274
}
279275
return null
280276
}
281277

282278
override fun doValidate(): ValidationInfo? {
283-
if (getTestRoot() == null) {
284-
return ValidationInfo("Test source root is not configured", testSourceFolderField.childComponent)
285-
}
286-
if (getRootDirectoryAndContentEntry() == null) {
279+
val testRoot = getTestRoot()
280+
?: return ValidationInfo("Test source root is not configured", testSourceFolderField.childComponent)
281+
282+
if (findReadOnlyContentEntry(testRoot) == null) {
287283
return ValidationInfo("Test source root is located out of content entry", testSourceFolderField.childComponent)
288284
}
289285

@@ -373,18 +369,33 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
373369
* Creates test source root if absent and target packages for tests.
374370
*/
375371
private fun createTestRootAndPackages(): Boolean {
376-
val (sourceRoot, contentEntry) = getRootDirectoryAndContentEntry() ?: return false
377-
val modifiableModel = ModuleRootManager.getInstance(model.testModule).modifiableModel
378-
VfsUtil.createDirectoryIfMissing(urlToPath(sourceRoot.url))
379-
contentEntry.addSourceFolder(sourceRoot.url, codegenLanguages.item.testRootType())
380-
WriteCommandAction.runWriteCommandAction(model.project) { modifiableModel.commit() }
372+
model.testSourceRoot = createDirectoryIfMissing(model.testSourceRoot)
373+
val testSourceRoot = model.testSourceRoot ?: return false
374+
if (model.testSourceRoot?.isDirectory != true) return false
375+
if (getOrCreateTestRoot(testSourceRoot)) {
376+
if (cbSpecifyTestPackage.isSelected) {
377+
createSelectedPackage(testSourceRoot)
378+
} else {
379+
createPackagesByClasses(testSourceRoot)
380+
}
381+
return true
382+
}
383+
return false
384+
}
381385

382-
if (cbSpecifyTestPackage.isSelected) {
383-
createSelectedPackage(sourceRoot)
386+
private fun createDirectoryIfMissing(dir : VirtualFile?): VirtualFile? {
387+
val file = if (dir is FakeVirtualFile) {
388+
WriteCommandAction.runWriteCommandAction(model.project, Computable<VirtualFile> {
389+
VfsUtil.createDirectoryIfMissing(dir.path)
390+
})
384391
} else {
385-
createPackagesByClasses(sourceRoot)
392+
dir
393+
}?: return null
394+
return if (VfsUtil.virtualToIoFile(file).isFile) {
395+
null
396+
} else {
397+
StandardFileSystems.local().findFileByPath(file.path)
386398
}
387-
return true
388399
}
389400

390401
private fun createPackagesByClasses(testSourceRoot: VirtualFile) {
@@ -413,12 +424,33 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
413424
"Generation error"
414425
)
415426

416-
private fun getRootDirectoryAndContentEntry() : Pair<VirtualFile, ContentEntry>? {
417-
val testSourceRoot = getTestRoot()?: return null
418-
val contentEntry = ModuleRootManager.getInstance(model.testModule).contentEntries
427+
private fun findReadOnlyContentEntry(testSourceRoot: VirtualFile?): ContentEntry? {
428+
if (testSourceRoot == null) return null
429+
if (testSourceRoot is FakeVirtualFile) {
430+
return findReadOnlyContentEntry(testSourceRoot.parent)
431+
}
432+
return ModuleRootManager.getInstance(model.testModule).contentEntries
419433
.filterNot { it.file == null }
420-
.firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, true) } ?: return null
421-
return Pair(testSourceRoot, contentEntry)
434+
.firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, false) }
435+
}
436+
437+
private fun getOrCreateTestRoot(testSourceRoot: VirtualFile): Boolean {
438+
val modifiableModel = ModuleRootManager.getInstance(model.testModule).modifiableModel
439+
try {
440+
val contentEntry = modifiableModel.contentEntries
441+
.filterNot { it.file == null }
442+
.firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, true) }
443+
?: return false
444+
445+
contentEntry.addSourceRootIfAbsent(
446+
modifiableModel,
447+
testSourceRoot.url,
448+
codegenLanguages.item.testRootType()
449+
)
450+
return true
451+
} finally {
452+
if (modifiableModel.isWritable && !modifiableModel.isDisposed) modifiableModel.dispose()
453+
}
422454
}
423455

424456
private fun createPackageWrapper(packageName: String?): PackageWrapper =
@@ -648,7 +680,14 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
648680
itemsToHelpTooltip.forEach { (box, tooltip) -> box.setHelpTooltipTextChanger(tooltip) }
649681

650682
testSourceFolderField.childComponent.addActionListener { event ->
651-
model.testSourceRoot = pathToFile((event.source as JComboBox<*>).selectedItem as String)
683+
with((event.source as JComboBox<*>).selectedItem) {
684+
if (this is VirtualFile) {
685+
model.testSourceRoot = this@with
686+
}
687+
else {
688+
model.testSourceRoot = null
689+
}
690+
}
652691
}
653692

654693
mockStrategies.addActionListener { event ->
@@ -700,14 +739,6 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
700739
}
701740
}
702741

703-
private fun pathToFile(path: String): VirtualFile? {
704-
val relativePath = path.substring(".../".length).replace('\\', '/')
705-
return model.testModule
706-
.suitableTestSourceRoots()
707-
.firstOrNull { it.path.contains(relativePath) }
708-
}
709-
710-
711742
private lateinit var currentFrameworkItem: TestFramework
712743

713744
//We would like to remove JUnit4 from framework list in parametrized mode

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,53 @@
11
package org.utbot.intellij.plugin.ui.components
22

3-
import org.utbot.common.PathUtil
4-
import org.utbot.intellij.plugin.ui.GenerateTestsModel
5-
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
6-
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
73
import com.intellij.openapi.application.ReadAction
84
import com.intellij.openapi.fileChooser.FileChooser
95
import com.intellij.openapi.fileChooser.FileChooserDescriptor
106
import com.intellij.openapi.project.guessProjectDir
117
import com.intellij.openapi.vfs.VirtualFile
8+
import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile
9+
import com.intellij.ui.ColoredListCellRenderer
1210
import com.intellij.ui.ComboboxWithBrowseButton
11+
import com.intellij.ui.SimpleTextAttributes
1312
import com.intellij.util.ArrayUtil
13+
import java.io.File
1414
import javax.swing.DefaultComboBoxModel
15+
import javax.swing.JList
16+
import org.utbot.common.PathUtil
17+
import org.utbot.framework.plugin.api.CodegenLanguage
18+
import org.utbot.intellij.plugin.ui.GenerateTestsModel
19+
import org.utbot.intellij.plugin.ui.utils.addDedicatedTestRoot
20+
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
1521

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

1824
private val SET_TEST_FOLDER = "set test folder"
1925

2026
init {
2127
childComponent.isEditable = false
28+
childComponent.renderer = object : ColoredListCellRenderer<Any?>() {
29+
override fun customizeCellRenderer(
30+
list: JList<out Any?>,
31+
value: Any?,
32+
index: Int,
33+
selected: Boolean,
34+
hasFocus: Boolean
35+
) {
36+
if (value is String) {
37+
append(value)
38+
return
39+
}
40+
if (value is VirtualFile) {
41+
append(formatUrl(value, model))
42+
}
43+
if (value is FakeVirtualFile) {
44+
append(" (will be created)", SimpleTextAttributes.ERROR_ATTRIBUTES)
45+
}
46+
}
47+
}
2248

23-
val testRoots = model.testModule.suitableTestSourceRoots()
24-
49+
val testRoots = model.testModule.suitableTestSourceRoots(CodegenLanguage.JAVA).toMutableList()
50+
model.testModule.addDedicatedTestRoot(testRoots)
2551
if (testRoots.isNotEmpty()) {
2652
configureRootsCombo(testRoots)
2753
} else {
@@ -34,11 +60,11 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
3460
model.testSourceRoot = it
3561

3662
if (childComponent.itemCount == 1 && childComponent.selectedItem == SET_TEST_FOLDER) {
37-
newItemList(setOf(formatUrl(it, model)))
63+
newItemList(setOf(it))
3864
} else {
3965
//Prepend and select newly added test root
40-
val testRootItems = linkedSetOf(formatUrl(it, model))
41-
testRootItems += (0 until childComponent.itemCount).map { i -> childComponent.getItemAt(i) as String}
66+
val testRootItems = linkedSetOf(it)
67+
testRootItems += (0 until childComponent.itemCount).map { i -> childComponent.getItemAt(i) as VirtualFile}
4268
newItemList(testRootItems)
4369
}
4470
}
@@ -59,17 +85,19 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
5985
val selectedRoot = testRoots.first()
6086

6187
model.testSourceRoot = selectedRoot
62-
newItemList(testRoots.map { root -> formatUrl(root, model) }.toSet())
88+
newItemList(testRoots.toSet())
6389
}
6490

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

7195
private fun formatUrl(virtualFile: VirtualFile, model: GenerateTestsModel): String {
72-
var directoryUrl = virtualFile.presentableUrl
96+
var directoryUrl = if (virtualFile is FakeVirtualFile) {
97+
virtualFile.parent.presentableUrl + File.separatorChar + virtualFile.name
98+
} else {
99+
virtualFile.presentableUrl
100+
}
73101
@Suppress("DEPRECATION")
74102
val projectHomeUrl = model.project.baseDir.presentableUrl
75103

0 commit comments

Comments
 (0)