diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt index bab5de98ff..788b63c559 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt @@ -52,12 +52,12 @@ class JsCoverageCommand : CliktCommand(name = "coverage_js", help = "Get tests c shouldWait = true, timeout = 20, cmd = arrayOf( - pathToNYC, - "--report-dir=$coverageDataPath", + "\"$pathToNYC\"", + "--report-dir=\"$coverageDataPath\"", "--reporter=\"clover\"", - "--temp-dir=${workingDir}/cache", + "--temp-dir=\"${workingDir}/cache\"", "mocha", - testFileAbsolutePath + "\"$testFileAbsolutePath\"" ) ) val coveredList = mutableListOf() diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt index 1c094aa058..60d96fc374 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt @@ -42,7 +42,7 @@ class JsRunTestsCommand : CliktCommand(name = "run_js", help = "Runs tests for t val (inputText, errorText) = JsCmdExec.runCommand( dir = dir, shouldWait = true, - cmd = arrayOf("mocha", fileWithTestsAbsolutePath) + cmd = arrayOf("mocha", "\"$fileWithTestsAbsolutePath\"") ) if (errorText.isNotEmpty()) { logger.error { "An error has occurred while running tests for $fileWithTests: $errorText" } 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 cbbf47bd65..a9563afc37 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 @@ -18,7 +18,6 @@ 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 @@ -29,8 +28,12 @@ import org.utbot.intellij.plugin.ui.utils.testModules import settings.JsDynamicSettings import settings.JsExportsSettings.endComment import settings.JsExportsSettings.startComment +import settings.JsPackagesSettings.mochaData import settings.JsTestGenerationSettings.dummyClassName +import settings.PackageData import utils.JsCmdExec +import utils.OsProvider +import java.io.IOException private val logger = KotlinLogging.logger {} @@ -45,14 +48,17 @@ object JsDialogProcessor { editor: Editor, file: JSFile ) { - val model = createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file) + val model = + createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file) ?: return (object : Task.Backgroundable( project, "Check the requirements" ) { override fun run(indicator: ProgressIndicator) { invokeLater { - getFrameworkLibraryPath(Mocha.displayName.lowercase(), model) + if (!mochaData.findPackageByNpm(model.project.basePath!!, model.pathToNPM)) { + installMissingRequirement(model.project, model.pathToNPM, mochaData) + } createDialog(model)?.let { dialogProcessor -> if (!dialogProcessor.showAndGet()) return@invokeLater // Since Tern.js accesses containing file, sync with file system required before test generation. @@ -68,6 +74,37 @@ object JsDialogProcessor { }).queue() } + private fun findNodeAndNPM(): Pair? = + try { + val pathToNode = NodeJsLocalInterpreterManager.getInstance() + .interpreters.first().interpreterSystemIndependentPath + val (_, errorText) = JsCmdExec.runCommand( + shouldWait = true, + cmd = arrayOf("\"${pathToNode}\"", "-v") + ) + if (errorText.isNotEmpty()) throw NoSuchElementException() + val pathToNPM = + pathToNode.substringBeforeLast("/") + "/" + "npm" + OsProvider.getProviderByOs().npmPackagePostfix + pathToNode to pathToNPM + } 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." } + null + } catch (e: IOException) { + Messages.showErrorDialog( + "Node.js interpreter path is corrupted in IDEA settings.\n" + + "Please check Settings > Languages & Frameworks > Node.js", + "Requirement Error" + ) + logger.error { "Node.js interpreter path is corrupted in IDEA settings." } + null + } + + private fun createJsTestModel( project: Project, srcModule: Module, @@ -86,43 +123,22 @@ object JsDialogProcessor { showErrorDialogLater(project, errorMessage, "Test source roots not found") return null } + val (pathToNode, pathToNPM) = findNodeAndNPM() ?: return null return JsTestsModel( project = project, srcModule = srcModule, potentialTestModules = testModules, fileMethods = fileMethods, selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(), - file = file + file = file, ).apply { containingFilePath = filePath + this.pathToNode = pathToNode + this.pathToNPM = pathToNPM } - } - private fun createDialog( - jsTestsModel: JsTestsModel? - ): JsDialogWindow? { - try { - jsTestsModel?.pathToNode = NodeJsLocalInterpreterManager.getInstance() - .interpreters.first().interpreterSystemIndependentPath - val (_, errorText) = JsCmdExec.runCommand( - shouldWait = true, - cmd = arrayOf("node", "-v") - ) - if (errorText.isNotEmpty()) 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 createDialog(jsTestsModel: JsTestsModel?) = jsTestsModel?.let { JsDialogWindow(it) } private fun unblockDocument(project: Project, document: Document) { PsiDocumentManager.getInstance(project).apply { @@ -248,10 +264,10 @@ object JsDialogProcessor { } // TODO(MINOR): Add indicator.text for each installation -fun installMissingRequirement(project: Project, pathToNPM: String, requirement: String) { +fun installMissingRequirement(project: Project, pathToNPM: String, requirement: PackageData) { val message = """ Requirement is not installed: - $requirement + ${requirement.packageName} Install it? """.trimIndent() val result = Messages.showOkCancelDialog( @@ -266,7 +282,7 @@ fun installMissingRequirement(project: Project, pathToNPM: String, requirement: if (result == Messages.CANCEL) return - val (_, errorText) = installRequirement(pathToNPM, requirement, project.basePath) + val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM) if (errorText.isNotEmpty()) { showErrorDialogLater( 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 c2a8a79671..fcf6eb3700 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 @@ -52,7 +52,6 @@ class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) { ) init { - model.pathToNPM = model.pathToNode.substringBeforeLast("/") + "/" + "npm" title = "Generate Tests with UtBot" isResizable = false init() diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/NycSourceFileChooser.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/NycSourceFileChooser.kt index 11fde43182..cbc51d63f5 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/NycSourceFileChooser.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/NycSourceFileChooser.kt @@ -5,7 +5,8 @@ import com.intellij.openapi.ui.TextBrowseFolderListener import com.intellij.openapi.ui.TextFieldWithBrowseButton import com.intellij.openapi.ui.ValidationInfo import org.utbot.common.PathUtil.replaceSeparator -import settings.JsDynamicSettings +import settings.JsPackagesSettings.nycData +import utils.OsProvider class NycSourceFileChooser(val model: JsTestsModel) : TextFieldWithBrowseButton() { @@ -23,11 +24,13 @@ class NycSourceFileChooser(val model: JsTestsModel) : TextFieldWithBrowseButton( addBrowseFolderListener( TextBrowseFolderListener(descriptor, model.project) ) - text = replaceSeparator(getFrameworkLibraryPath(JsDynamicSettings().pathToNYC, model) ?: "Nyc was not found") + nycData.findPackagePath() ?: installMissingRequirement(model.project, model.pathToNPM, nycData) + text = (replaceSeparator(nycData.findPackagePath() ?: "Nyc was not found") + + OsProvider.getProviderByOs().npmPackagePostfix) } fun validateNyc(): ValidationInfo? { - return if (replaceSeparator(text).endsWith("nyc")) + return if (replaceSeparator(text).endsWith("nyc" + OsProvider.getProviderByOs().npmPackagePostfix)) null else ValidationInfo("Nyc executable file was not found in the specified directory", this) 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 deleted file mode 100644 index aab6625d7c..0000000000 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.utbot.intellij.plugin.language.js - -import com.intellij.openapi.ui.Messages -import utils.JsCmdExec -import utils.OsProvider - -fun getFrameworkLibraryPath(npmPackageName: String, model: JsTestsModel?): String? { - val (inputText, _) = JsCmdExec.runCommand( - dir = model?.project?.basePath!!, - shouldWait = true, - timeout = 10, - cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), npmPackageName) - ) - - if (!inputText.contains(npmPackageName) && !findFrameworkLibrary(npmPackageName, model)) { - installMissingRequirement(model.project, model.pathToNPM, npmPackageName) - return null - } - return inputText.substringBefore(npmPackageName) + npmPackageName -} - -private fun npmListByFlag(model: JsTestsModel, flag: String): String { - val (inputText, _) = JsCmdExec.runCommand( - dir = model.project.basePath!!, - shouldWait = true, - timeout = 10, - cmd = arrayOf(model.pathToNPM, "list", flag) - ) - return inputText -} - -fun findFrameworkLibrary(npmPackageName: String, model: JsTestsModel): Boolean { - val packageText = npmListByFlag(model, "-g") + npmListByFlag(model, "-l") - - if (packageText.isEmpty()) { - Messages.showErrorDialog( - model.project, - "Node.js is not installed", - "Generation Failed", - ) - return false - } - return packageText.contains(npmPackageName) -} - -fun installRequirement(pathToNPM: String, requirement: String, installingDir: String?): Pair { - val installationType = if (requirement == "mocha") "-l" else "-g" - - val (inputText, errorText) = JsCmdExec.runCommand( - dir = installingDir, - shouldWait = true, - timeout = 10, - cmd = arrayOf(pathToNPM, "install", installationType) + requirement - ) - - return Pair(inputText, errorText) -} diff --git a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt index 87eb146920..68e26551b2 100644 --- a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt +++ b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt @@ -38,7 +38,7 @@ class JsFuzzerAstVisitor : IAstVisitor { } - private fun validateNode(value: Any) { + private fun validateNode(value: Any?) { when (value) { is String -> { fuzzedConcreteValues.add( diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index b23f2d3d87..d74ee12f8d 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -1,9 +1,7 @@ package parser import com.google.javascript.rhino.Node -import java.lang.IllegalStateException import org.utbot.fuzzer.FuzzedContext -import parser.JsParserUtils.getMethodName // Used for .children() calls. @Suppress("DEPRECATION") @@ -57,12 +55,12 @@ object JsParserUtils { /** * Called upon node with any kind of literal value token. */ - fun Node.getAnyValue(): Any = when { + fun Node.getAnyValue(): Any? = when { this.isNumber -> this.double this.isString || this.isName -> this.string this.isTrue -> true this.isFalse -> false - else -> throw UnsupportedOperationException("Not yet implemented!") + else -> null } // For some reason Closure Compiler doesn't contain a built-in method @@ -139,4 +137,4 @@ object JsParserUtils { * Called upon node with Method token. */ fun Node.isStatic(): Boolean = this.isStaticMember -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/BasicCoverageService.kt index 1a743c7e3c..5353f97d80 100644 --- a/utbot-js/src/main/kotlin/service/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/BasicCoverageService.kt @@ -105,12 +105,12 @@ class BasicCoverageService( val (_, errorText) = JsCmdExec.runCommand( cmd = arrayOf( - settings.pathToNYC, - "--report-dir=$utbotDirPath/coverage$index", + "\"${settings.pathToNYC}\"", + "--report-dir=\"$utbotDir/coverage$index\"", "--reporter=json", - "--temp-dir=$utbotDirPath/cache$index", - "node", - filePath + "--temp-dir=\"$utbotDir/cache$index\"", + "\"${settings.pathToNode}\"", + "\"$filePath\"" ), shouldWait = true, dir = context.projectPath, diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index c7dce28e5b..5d6034c66f 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -81,7 +81,7 @@ test("${context.filePathToInference}") JsCmdExec.runCommand( dir = path, shouldWait = true, - cmd = arrayOf(context.settings.pathToNPM, "i", "tern", "-l") + cmd = arrayOf("\"${context.settings.pathToNPM}\"", "i", "tern", "-l") ) } @@ -97,7 +97,7 @@ test("${context.filePathToInference}") dir = "$projectPath/$utbotDir/", shouldWait = true, timeout = 20, - cmd = arrayOf(settings.pathToNode, "${projectPath}/$utbotDir/ternScript.js"), + cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""), ) json = try { JSONObject(inputText.replaceAfterLast("}", "")) diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt new file mode 100644 index 0000000000..b4cae35604 --- /dev/null +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -0,0 +1,50 @@ +package settings + +import utils.JsCmdExec +import utils.OsProvider + +object JsPackagesSettings { + val mochaData: PackageData = PackageData("mocha", "-l") + val nycData: PackageData = PackageData("nyc", "-g") + + // TODO(MINOR): Add tern auto installation + val ternData: PackageData? = null +} + +data class PackageData( + val packageName: String, + val npmListFlag: String +) { + fun findPackageByNpm(projectBasePath: String, pathToNpm: String): Boolean { + val (inputText, _) = JsCmdExec.runCommand( + dir = projectBasePath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag) + ) + + return inputText.contains(packageName) + } + + fun findPackagePath(): String? { + val (inputText, _) = JsCmdExec.runCommand( + dir = null, + shouldWait = true, + timeout = 10, + cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), packageName) + ) + + return inputText.split(System.lineSeparator()).first().takeIf { it.contains(packageName) } + } + + fun installPackage(projectBasePath: String, pathToNpm: String): Pair { + val (inputText, errorText) = JsCmdExec.runCommand( + dir = projectBasePath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag, packageName) + ) + + return Pair(inputText, errorText) + } +} diff --git a/utbot-js/src/main/kotlin/utils/JsOsUtils.kt b/utbot-js/src/main/kotlin/utils/JsOsUtils.kt index a21be883ae..0b8c295f6d 100644 --- a/utbot-js/src/main/kotlin/utils/JsOsUtils.kt +++ b/utbot-js/src/main/kotlin/utils/JsOsUtils.kt @@ -7,6 +7,8 @@ abstract class OsProvider { abstract fun getCmdPrefix(): Array abstract fun getAbstractivePathTool(): String + abstract val npmPackagePostfix: String + companion object { fun getProviderByOs(): OsProvider { @@ -20,11 +22,15 @@ abstract class OsProvider { } class WindowsProvider : OsProvider() { - override fun getCmdPrefix() = arrayOf("cmd.exe", "/c") + override fun getCmdPrefix() = emptyArray() override fun getAbstractivePathTool() = "where" + + override val npmPackagePostfix = ".cmd" } class LinuxProvider : OsProvider() { override fun getCmdPrefix() = emptyArray() override fun getAbstractivePathTool() = "which" + + override val npmPackagePostfix = "" }