From 7fe1f1f0e76cf1a1ebea28ab95b7c1e9b7f738c7 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:26:26 +0300 Subject: [PATCH 1/3] Added Node.js getter from IDEA settings --- utbot-intellij-js/build.gradle.kts | 1 + .../plugin/language/js/JsDialogProcessor.kt | 118 ++++++++++++++---- .../plugin/language/js/JsDialogWindow.kt | 16 --- .../intellij/plugin/language/js/Utils.kt | 33 +++-- utbot-js/src/main/kotlin/utils/JsCmdExec.kt | 4 +- 5 files changed, 113 insertions(+), 59 deletions(-) diff --git a/utbot-intellij-js/build.gradle.kts b/utbot-intellij-js/build.gradle.kts index bafc50275c..9af87e9c69 100644 --- a/utbot-intellij-js/build.gradle.kts +++ b/utbot-intellij-js/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") implementation(project(":utbot-ui-commons")) + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) //Family implementation(project(":utbot-js")) diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt index cb78711a66..67151d225a 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt @@ -2,6 +2,7 @@ package org.utbot.intellij.plugin.language.js import api.JsTestGenerator import com.intellij.codeInsight.CodeInsightUtil +import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreterManager import com.intellij.lang.ecmascript6.psi.ES6Class import com.intellij.lang.javascript.psi.JSFile import com.intellij.lang.javascript.refactoring.util.JSMemberInfo @@ -12,10 +13,13 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFileFactory import com.intellij.psi.impl.file.PsiDirectoryFactory import com.intellij.util.concurrency.AppExecutorUtil +import framework.codegen.Mocha +import mu.KotlinLogging import org.jetbrains.kotlin.idea.util.application.invokeLater import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction @@ -26,6 +30,9 @@ import settings.JsDynamicSettings import settings.JsExportsSettings.endComment import settings.JsExportsSettings.startComment import settings.JsTestGenerationSettings.dummyClassName +import utils.JsCmdExec + +private val logger = KotlinLogging.logger {} object JsDialogProcessor { @@ -38,28 +45,37 @@ object JsDialogProcessor { editor: Editor, file: JSFile ) { - createDialog(project, srcModule, fileMethods, focusedMethod, containingFilePath, file)?.let { dialogProcessor -> - if (!dialogProcessor.showAndGet()) return - /* - Since Tern.js accesses containing file, sync with file system required before test generation. - */ - runWriteAction { - with(FileDocumentManager.getInstance()) { - saveDocument(editor.document) + val model = createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file) + (object : Task.Backgroundable( + project, + "Check the requirements" + ) { + override fun run(indicator: ProgressIndicator) { + invokeLater { + getFrameworkLibraryPath(Mocha.displayName.lowercase(), model) + createDialog(model)?.let { dialogProcessor -> + if (!dialogProcessor.showAndGet()) return@invokeLater + // Since Tern.js accesses containing file, sync with file system required before test generation. + runWriteAction { + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } + } + createTests(dialogProcessor.model, containingFilePath, editor) + } } } - createTests(dialogProcessor.model, containingFilePath, editor) - } + }).queue() } - private fun createDialog( + private fun createJsTestModel( project: Project, srcModule: Module, fileMethods: Set, focusedMethod: JSMemberInfo?, filePath: String, file: JSFile - ): JsDialogWindow? { + ): JsTestsModel? { val testModules = srcModule.testModules(project) if (testModules.isEmpty()) { @@ -70,19 +86,42 @@ object JsDialogProcessor { showErrorDialogLater(project, errorMessage, "Test source roots not found") return null } + return JsTestsModel( + project = project, + srcModule = srcModule, + potentialTestModules = testModules, + fileMethods = fileMethods, + selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(), + file = file + ).apply { + containingFilePath = filePath + } - return JsDialogWindow( - JsTestsModel( - project = project, - srcModule = srcModule, - potentialTestModules = testModules, - fileMethods = fileMethods, - selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(), - file = file - ).apply { - containingFilePath = filePath - } - ) + } + + private fun createDialog( + jsTestsModel: JsTestsModel? + ): JsDialogWindow? { + try { + jsTestsModel?.pathToNode = NodeJsLocalInterpreterManager.getInstance() + .interpreters.first().interpreterSystemIndependentPath + val (_, _, code) = JsCmdExec.runCommand( + shouldWait = true, + cmd = arrayOf("node", "-v") + ) + if (code != 0) throw NoSuchElementException() + } catch (e: NoSuchElementException) { + Messages.showErrorDialog( + "Node.js interpreter is not found in IDEA settings.\n" + + "Please set it in Settings > Languages & Frameworks > Node.js", + "Requirement Error" + ) + logger.error { "Node.js interpreter was not found in IDEA settings." } + return null + } + return jsTestsModel?.let { + JsDialogWindow(it) + } } private fun unblockDocument(project: Project, document: Document) { @@ -207,3 +246,34 @@ object JsDialogProcessor { } } } + +// TODO(MINOR): Add indicator.text for each installation +fun installRequirement(project: Project, pathToNPM: String, requirement: String) { + val message = """ + Requirement is not installed: + $requirement + Install it? + """.trimIndent() + val result = Messages.showOkCancelDialog( + project, + message, + "Requirement Missmatch Error", + "Install", + "Cancel", + null + ) + + if (result == Messages.CANCEL) + return + + val (_, errorStream) = installRequirement(pathToNPM, requirement) + + val errorText = errorStream.readText() + if (errorText.isNotEmpty()) { + showErrorDialogLater( + project, + "Requirements installing failed with some reason:\n${errorText}", + "Requirements error" + ) + } +} diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogWindow.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogWindow.kt index fcaef2630f..c2a8a79671 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogWindow.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogWindow.kt @@ -1,6 +1,5 @@ package org.utbot.intellij.plugin.language.js -import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreterManager import com.intellij.lang.javascript.refactoring.ui.JSMemberSelectionTable import com.intellij.lang.javascript.refactoring.util.JSMemberInfo import com.intellij.openapi.ui.ComboBox @@ -37,18 +36,11 @@ class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) { this.preferredScrollableViewportSize = JBUI.size(-1, height) } - private val nodeInterp = try { - NodeJsLocalInterpreterManager.getInstance().interpreters.first() - } catch (e: NoSuchElementException) { - throw IllegalStateException("Node.js interpreter is not set in the IDEA settings!") - } - private val testSourceFolderField = TestSourceDirectoryChooser(model, model.file.virtualFile) private val testFrameworks = ComboBox(DefaultComboBoxModel(arrayOf(Mocha))) private val nycSourceFileChooserField = NycSourceFileChooser(model) private val coverageMode = CoverageModeButtons -// private var initTestFrameworkPresenceThread: Thread private lateinit var panel: DialogPanel private val timeoutSpinner = @@ -60,16 +52,8 @@ class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) { ) init { - model.pathToNode = nodeInterp.interpreterSystemDependentPath.replace("\\", "/") model.pathToNPM = model.pathToNode.substringBeforeLast("/") + "/" + "npm" - //TODO: Find out how to find pathToNode from IDEA settings without extra actions from the user - model.pathToNode = "node" title = "Generate Tests with UtBot" -// initTestFrameworkPresenceThread = thread(start = true) { -// JsCgLanguageAssistant.getLanguageTestFrameworkManager().testFrameworks.forEach { -// it.isInstalled = findFrameworkLibrary(it.displayName.lowercase(Locale.getDefault()), model) -// } -// } isResizable = false init() } diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt index 256a040629..9f1cd004e1 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt @@ -3,31 +3,20 @@ package org.utbot.intellij.plugin.language.js import com.intellij.openapi.ui.Messages import utils.JsCmdExec import utils.OsProvider +import java.io.BufferedReader -fun getFrameworkLibraryPath(npmPackageName: String, model: JsTestsModel): String? { +fun getFrameworkLibraryPath(npmPackageName: String, model: JsTestsModel?): String? { val (bufferedReader, errorReader) = JsCmdExec.runCommand( - dir = model.project.basePath!!, + dir = model?.project?.basePath!!, shouldWait = true, timeout = 10, - cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), model.pathToNYC) + cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), npmPackageName) ) val input = bufferedReader.readText() val error = errorReader.readText() - if (error.isNotEmpty() or !input.contains(npmPackageName)) { - if (findFrameworkLibrary(npmPackageName, model)) { - Messages.showErrorDialog( - model.project, - "The following packages were not found, please set it in menu manually:\n $npmPackageName", - "$npmPackageName missing!", - ) - } else { - Messages.showErrorDialog( - model.project, - "The following packages are not installed: $npmPackageName \nPlease install it via npm `> npm i -g $npmPackageName`", - "$npmPackageName missing!", - ) - } + if ((error.isNotEmpty() or !input.contains(npmPackageName)) && !findFrameworkLibrary(npmPackageName, model)) { + installRequirement(model.project, model.pathToNPM, npmPackageName) return null } return input.substringBefore(npmPackageName) + npmPackageName @@ -52,3 +41,13 @@ fun findFrameworkLibrary(npmPackageName: String, model: JsTestsModel): Boolean { } return checkForPackageText.contains(npmPackageName) } + +fun installRequirement(pathToNPM: String, requirement: String): Pair { + val (buf1, buf2, _) = JsCmdExec.runCommand( + dir = null, + shouldWait = true, + timeout = 10, + cmd = arrayOf(pathToNPM, "install", "-g") + requirement + ) + return buf1 to buf2 +} diff --git a/utbot-js/src/main/kotlin/utils/JsCmdExec.kt b/utbot-js/src/main/kotlin/utils/JsCmdExec.kt index 6641ddb987..53501bb951 100644 --- a/utbot-js/src/main/kotlin/utils/JsCmdExec.kt +++ b/utbot-js/src/main/kotlin/utils/JsCmdExec.kt @@ -13,7 +13,7 @@ object JsCmdExec { shouldWait: Boolean = false, timeout: Long = defaultTimeout, vararg cmd: String, - ): Pair { + ): Triple { val builder = ProcessBuilder(*OsProvider.getProviderByOs().getCmdPrefix(), *cmd) dir?.let { builder.directory(File(it)) @@ -28,6 +28,6 @@ object JsCmdExec { throw TimeoutException("") } } - return process.inputStream.bufferedReader() to process.errorStream.bufferedReader() + return Triple(process.inputStream.bufferedReader(), process.errorStream.bufferedReader(), process.exitValue()) } } From 1d450b89b818c06a603aa7ed6ac9b456e8ebaab6 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Wed, 21 Dec 2022 17:45:56 +0300 Subject: [PATCH 2/3] Prepatch for local mocha installation --- .../intellij/plugin/language/js/JsDialogProcessor.kt | 6 +++--- .../kotlin/org/utbot/intellij/plugin/language/js/Utils.kt | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt index 67151d225a..1adda9e697 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt @@ -248,7 +248,7 @@ object JsDialogProcessor { } // TODO(MINOR): Add indicator.text for each installation -fun installRequirement(project: Project, pathToNPM: String, requirement: String) { +fun installMissingRequirement(project: Project, pathToNPM: String, requirement: String) { val message = """ Requirement is not installed: $requirement @@ -265,8 +265,8 @@ fun installRequirement(project: Project, pathToNPM: String, requirement: String) if (result == Messages.CANCEL) return - - val (_, errorStream) = installRequirement(pathToNPM, requirement) + + val (_, errorStream) = installRequirement(pathToNPM, requirement, project.basePath) val errorText = errorStream.readText() if (errorText.isNotEmpty()) { diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt index 9f1cd004e1..441764513f 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt @@ -16,7 +16,7 @@ fun getFrameworkLibraryPath(npmPackageName: String, model: JsTestsModel?): Strin val error = errorReader.readText() if ((error.isNotEmpty() or !input.contains(npmPackageName)) && !findFrameworkLibrary(npmPackageName, model)) { - installRequirement(model.project, model.pathToNPM, npmPackageName) + installMissingRequirement(model.project, model.pathToNPM, npmPackageName) return null } return input.substringBefore(npmPackageName) + npmPackageName @@ -42,12 +42,12 @@ fun findFrameworkLibrary(npmPackageName: String, model: JsTestsModel): Boolean { return checkForPackageText.contains(npmPackageName) } -fun installRequirement(pathToNPM: String, requirement: String): Pair { +fun installRequirement(pathToNPM: String, requirement: String, installingDir: String?): Pair { val (buf1, buf2, _) = JsCmdExec.runCommand( - dir = null, + dir = installingDir, shouldWait = true, timeout = 10, - cmd = arrayOf(pathToNPM, "install", "-g") + requirement + cmd = arrayOf(pathToNPM, "install", "-l") + requirement ) return buf1 to buf2 } From 41a118bd1aead045a0987f89ff3759c7164de0f5 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Wed, 21 Dec 2022 18:28:35 +0300 Subject: [PATCH 3/3] Added mocha local installation, fixed Node.js getter --- .../intellij/plugin/language/js/JsDialogProcessor.kt | 6 +++--- .../kotlin/org/utbot/intellij/plugin/language/js/Utils.kt | 6 ++++-- utbot-js/src/main/kotlin/utils/JsCmdExec.kt | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt index 1adda9e697..277c95e1aa 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt @@ -105,11 +105,11 @@ object JsDialogProcessor { try { jsTestsModel?.pathToNode = NodeJsLocalInterpreterManager.getInstance() .interpreters.first().interpreterSystemIndependentPath - val (_, _, code) = JsCmdExec.runCommand( + val (_, error) = JsCmdExec.runCommand( shouldWait = true, cmd = arrayOf("node", "-v") ) - if (code != 0) throw NoSuchElementException() + if (error.readText().isNotEmpty()) throw NoSuchElementException() } catch (e: NoSuchElementException) { Messages.showErrorDialog( "Node.js interpreter is not found in IDEA settings.\n" + @@ -265,7 +265,7 @@ fun installMissingRequirement(project: Project, pathToNPM: String, requirement: if (result == Messages.CANCEL) return - + val (_, errorStream) = installRequirement(pathToNPM, requirement, project.basePath) val errorText = errorStream.readText() diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt index 441764513f..17cbc0852b 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt @@ -43,11 +43,13 @@ fun findFrameworkLibrary(npmPackageName: String, model: JsTestsModel): Boolean { } fun installRequirement(pathToNPM: String, requirement: String, installingDir: String?): Pair { - val (buf1, buf2, _) = JsCmdExec.runCommand( + val installationType = if (requirement == "mocha") "-l" else "-g" + + val (buf1, buf2) = JsCmdExec.runCommand( dir = installingDir, shouldWait = true, timeout = 10, - cmd = arrayOf(pathToNPM, "install", "-l") + requirement + cmd = arrayOf(pathToNPM, "install", installationType) + requirement ) return buf1 to buf2 } diff --git a/utbot-js/src/main/kotlin/utils/JsCmdExec.kt b/utbot-js/src/main/kotlin/utils/JsCmdExec.kt index 53501bb951..661641217b 100644 --- a/utbot-js/src/main/kotlin/utils/JsCmdExec.kt +++ b/utbot-js/src/main/kotlin/utils/JsCmdExec.kt @@ -1,10 +1,10 @@ package utils +import org.utbot.framework.plugin.api.TimeoutException +import settings.JsTestGenerationSettings.defaultTimeout import java.io.BufferedReader import java.io.File import java.util.concurrent.TimeUnit -import org.utbot.framework.plugin.api.TimeoutException -import settings.JsTestGenerationSettings.defaultTimeout object JsCmdExec { @@ -13,7 +13,7 @@ object JsCmdExec { shouldWait: Boolean = false, timeout: Long = defaultTimeout, vararg cmd: String, - ): Triple { + ): Pair { val builder = ProcessBuilder(*OsProvider.getProviderByOs().getCmdPrefix(), *cmd) dir?.let { builder.directory(File(it)) @@ -28,6 +28,6 @@ object JsCmdExec { throw TimeoutException("") } } - return Triple(process.inputStream.bufferedReader(), process.errorStream.bufferedReader(), process.exitValue()) + return Pair(process.inputStream.bufferedReader(), process.errorStream.bufferedReader()) } }