From ebf835da7c56ed98a0d64daa72a20b6b8e3cfe23 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 14:17:07 +0300 Subject: [PATCH 1/8] Refactoring of npm installation process --- .../plugin/language/js/JsDialogProcessor.kt | 48 ++++++++++++++----- .../kotlin/settings/JsPackagesSettings.kt | 7 +-- .../settings/JsTestGenerationSettings.kt | 5 +- 3 files changed, 44 insertions(+), 16 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 6da92464e0..bb20f2ed5b 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 @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.idea.util.application.invokeLater import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.konan.file.File +import org.utbot.framework.plugin.api.TimeoutException import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.testModules import settings.JsDynamicSettings @@ -58,9 +59,21 @@ object JsDialogProcessor { ) { override fun run(indicator: ProgressIndicator) { invokeLater { - checkAndInstallRequirement(model.project, model.pathToNPM, mochaData) - checkAndInstallRequirement(model.project, model.pathToNPM, nycData) - checkAndInstallRequirement(model.project, model.pathToNPM, ternData) + if (!(checkAndInstallRequirement( + model.project, + model.pathToNPM, + mochaData + ) && checkAndInstallRequirement( + model.project, + model.pathToNPM, + nycData + ) && checkAndInstallRequirement( + model.project, + model.pathToNPM, + ternData + )) + ) return@invokeLater + createDialog(model)?.let { dialogWindow -> if (!dialogWindow.showAndGet()) return@invokeLater // Since Tern.js accesses containing file, sync with file system required before test generation. @@ -271,17 +284,18 @@ fun checkAndInstallRequirement( project: Project, pathToNPM: String, requirement: PackageData, -) { +): Boolean { if (!requirement.findPackageByNpm(project.basePath!!, pathToNPM)) { - installMissingRequirement(project, pathToNPM, requirement) + return installMissingRequirement(project, pathToNPM, requirement) } + return true } private fun installMissingRequirement( project: Project, pathToNPM: String, requirement: PackageData, -) { +): Boolean { val message = """ Requirement is not installed: ${requirement.packageName} @@ -297,15 +311,25 @@ private fun installMissingRequirement( ) if (result == Messages.CANCEL) - return - - val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM) + return false - if (errorText.isNotEmpty()) { + try { + val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM) + if (errorText.isNotEmpty()) { + showErrorDialogLater( + project, + "Requirements installing failed with some reason:\n${errorText}", + "Requirement installation error" + ) + } + return true + } catch (_: TimeoutException) { showErrorDialogLater( project, - "Requirements installing failed with some reason:\n${errorText}", - "Requirements error" + "Requirements installing failed due to the exceeded waiting time for the installation of the " + + "npm package, check your internet connection.", + "Requirement installation error" ) + return false } } diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt index 59b1399052..3d9437368e 100644 --- a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -1,5 +1,6 @@ package settings +import settings.JsTestGenerationSettings.npmPackageExecutorTimeout import utils.JsCmdExec import utils.OsProvider @@ -17,7 +18,7 @@ data class PackageData( val (inputText, _) = JsCmdExec.runCommand( dir = projectBasePath, shouldWait = true, - timeout = 10, + timeout = npmPackageExecutorTimeout, cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag) ) @@ -28,7 +29,7 @@ data class PackageData( val (inputText, _) = JsCmdExec.runCommand( dir = null, shouldWait = true, - timeout = 10, + timeout = npmPackageExecutorTimeout, cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), packageName) ) @@ -39,7 +40,7 @@ data class PackageData( val (inputText, errorText) = JsCmdExec.runCommand( dir = projectBasePath, shouldWait = true, - timeout = 10, + timeout = npmPackageExecutorTimeout, cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag, packageName) ) diff --git a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt index 2031b32c5e..7af47646fa 100644 --- a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt @@ -10,6 +10,9 @@ object JsTestGenerationSettings { const val fuzzingTimeout = 30_000L + // Default timeout for any operations with npm packages + const val npmPackageExecutorTimeout = 15L + // Name of file under test when importing it. const val fileUnderTestAliases = "fileUnderTest" @@ -18,4 +21,4 @@ object JsTestGenerationSettings { // Number of test cases that can fit in one temporary file for Fast coverage mode const val fuzzingThreshold = 300 -} \ No newline at end of file +} From e8c8cea7e9f68659e85eca5e905cd6ea58312621 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 16:02:13 +0300 Subject: [PATCH 2/8] Revert "Refactoring of npm installation process" This reverts commit ebf835da7c56ed98a0d64daa72a20b6b8e3cfe23. --- .../plugin/language/js/JsDialogProcessor.kt | 48 +++++-------------- .../kotlin/settings/JsPackagesSettings.kt | 7 ++- .../settings/JsTestGenerationSettings.kt | 5 +- 3 files changed, 16 insertions(+), 44 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 bb20f2ed5b..6da92464e0 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 @@ -23,7 +23,6 @@ import org.jetbrains.kotlin.idea.util.application.invokeLater import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.konan.file.File -import org.utbot.framework.plugin.api.TimeoutException import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.testModules import settings.JsDynamicSettings @@ -59,21 +58,9 @@ object JsDialogProcessor { ) { override fun run(indicator: ProgressIndicator) { invokeLater { - if (!(checkAndInstallRequirement( - model.project, - model.pathToNPM, - mochaData - ) && checkAndInstallRequirement( - model.project, - model.pathToNPM, - nycData - ) && checkAndInstallRequirement( - model.project, - model.pathToNPM, - ternData - )) - ) return@invokeLater - + checkAndInstallRequirement(model.project, model.pathToNPM, mochaData) + checkAndInstallRequirement(model.project, model.pathToNPM, nycData) + checkAndInstallRequirement(model.project, model.pathToNPM, ternData) createDialog(model)?.let { dialogWindow -> if (!dialogWindow.showAndGet()) return@invokeLater // Since Tern.js accesses containing file, sync with file system required before test generation. @@ -284,18 +271,17 @@ fun checkAndInstallRequirement( project: Project, pathToNPM: String, requirement: PackageData, -): Boolean { +) { if (!requirement.findPackageByNpm(project.basePath!!, pathToNPM)) { - return installMissingRequirement(project, pathToNPM, requirement) + installMissingRequirement(project, pathToNPM, requirement) } - return true } private fun installMissingRequirement( project: Project, pathToNPM: String, requirement: PackageData, -): Boolean { +) { val message = """ Requirement is not installed: ${requirement.packageName} @@ -311,25 +297,15 @@ private fun installMissingRequirement( ) if (result == Messages.CANCEL) - return false + return - try { - val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM) - if (errorText.isNotEmpty()) { - showErrorDialogLater( - project, - "Requirements installing failed with some reason:\n${errorText}", - "Requirement installation error" - ) - } - return true - } catch (_: TimeoutException) { + val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM) + + if (errorText.isNotEmpty()) { showErrorDialogLater( project, - "Requirements installing failed due to the exceeded waiting time for the installation of the " + - "npm package, check your internet connection.", - "Requirement installation error" + "Requirements installing failed with some reason:\n${errorText}", + "Requirements error" ) - return false } } diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt index 3d9437368e..59b1399052 100644 --- a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -1,6 +1,5 @@ package settings -import settings.JsTestGenerationSettings.npmPackageExecutorTimeout import utils.JsCmdExec import utils.OsProvider @@ -18,7 +17,7 @@ data class PackageData( val (inputText, _) = JsCmdExec.runCommand( dir = projectBasePath, shouldWait = true, - timeout = npmPackageExecutorTimeout, + timeout = 10, cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag) ) @@ -29,7 +28,7 @@ data class PackageData( val (inputText, _) = JsCmdExec.runCommand( dir = null, shouldWait = true, - timeout = npmPackageExecutorTimeout, + timeout = 10, cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), packageName) ) @@ -40,7 +39,7 @@ data class PackageData( val (inputText, errorText) = JsCmdExec.runCommand( dir = projectBasePath, shouldWait = true, - timeout = npmPackageExecutorTimeout, + timeout = 10, cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag, packageName) ) diff --git a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt index 7af47646fa..2031b32c5e 100644 --- a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt @@ -10,9 +10,6 @@ object JsTestGenerationSettings { const val fuzzingTimeout = 30_000L - // Default timeout for any operations with npm packages - const val npmPackageExecutorTimeout = 15L - // Name of file under test when importing it. const val fileUnderTestAliases = "fileUnderTest" @@ -21,4 +18,4 @@ object JsTestGenerationSettings { // Number of test cases that can fit in one temporary file for Fast coverage mode const val fuzzingThreshold = 300 -} +} \ No newline at end of file From 752570e4840f5dea3115ba2d594e34212102e9d1 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov <70969943+rudolf101@users.noreply.github.com> Date: Tue, 21 Feb 2023 20:21:22 +0300 Subject: [PATCH 3/8] Refactor npm packages checking & installing --- .../plugin/language/js/JsDialogProcessor.kt | 5 +- .../main/kotlin/service/PackageJsonService.kt | 42 ++++++++++++++ .../kotlin/settings/JsPackagesSettings.kt | 55 ++++++++++++++----- 3 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 utbot-js/src/main/kotlin/service/PackageJsonService.kt 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 6da92464e0..3a9b72a46a 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 @@ -36,6 +36,7 @@ import settings.PackageData import utils.JsCmdExec import utils.OsProvider import java.io.IOException +import settings.PackageDataService private val logger = KotlinLogging.logger {} @@ -267,12 +268,12 @@ object JsDialogProcessor { } } -fun checkAndInstallRequirement( +fun PackageDataService.checkAndInstallRequirement( project: Project, pathToNPM: String, requirement: PackageData, ) { - if (!requirement.findPackageByNpm(project.basePath!!, pathToNPM)) { + if (!this.findPackageByNpm(requirement, project.basePath!!, pathToNPM)) { installMissingRequirement(project, pathToNPM, requirement) } } diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt new file mode 100644 index 0000000000..a838af0ec3 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -0,0 +1,42 @@ +package service + +import java.io.File +import java.io.FilenameFilter +import org.json.JSONObject + +data class PackageJson( + val isModule: Boolean, + val deps: Set +) { + companion object { + val defaultConfig = PackageJson(false, emptySet()) + } +} + +class PackageJsonService( + private val filePathToInference: String, + private val projectPath: String +) { + + fun findClosestConfig(): PackageJson { + var currDir = File(filePathToInference.substringBeforeLast("/")) + do { + val matchingFiles: Array = currDir.listFiles( + FilenameFilter { _, name -> + return@FilenameFilter name == "package.json" + } + ) ?: throw IllegalStateException("Error occurred while scanning file system") + if (matchingFiles.isNotEmpty()) return parseConfig(matchingFiles.first()) + currDir = currDir.parentFile + } while (currDir.path != projectPath) + return PackageJson.defaultConfig + } + + private fun parseConfig(configFile: File): PackageJson { + val configAsJson = JSONObject(configFile.readText()) + return PackageJson( + isModule = configAsJson.optString("type") == "module", + deps = configAsJson.optJSONObject("dependencies").keySet() ?: emptySet() + ) + } +} diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt index 59b1399052..b10d05bfc3 100644 --- a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -1,28 +1,28 @@ package settings +import service.PackageJsonService import utils.JsCmdExec import utils.OsProvider object JsPackagesSettings { - val mochaData: PackageData = PackageData("mocha", "-l") - val nycData: PackageData = PackageData("nyc", "-g") - val ternData: PackageData = PackageData("tern", "-l") + val mochaData: PackageData = PackageData("mocha", NpmListFlag.L) + val nycData: PackageData = PackageData("nyc", NpmListFlag.G) + val ternData: PackageData = PackageData("tern", NpmListFlag.L) +} + +enum class NpmListFlag { + L { + override fun toString(): String = "-l" + }, + G { + override fun toString(): String = "-g" + } } data class PackageData( val packageName: String, - val npmListFlag: String + val npmListFlag: NpmListFlag ) { - 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( @@ -40,9 +40,34 @@ data class PackageData( dir = projectBasePath, shouldWait = true, timeout = 10, - cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag, packageName) + cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag.toString(), packageName) ) return Pair(inputText, errorText) } } + +class PackageDataService( + filePathToInference: String, + projectPath: String, +) { + private val packageJson = PackageJsonService(filePathToInference, projectPath).findClosestConfig() + + fun findPackageByNpm(packageData: PackageData, projectBasePath: String, pathToNpm: String): Boolean = with(packageData) { + when (npmListFlag) { + NpmListFlag.G -> { + val (inputText, _) = JsCmdExec.runCommand( + dir = projectBasePath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag.toString()) + ) + + inputText.contains(packageName) + } + NpmListFlag.L -> { + packageJson.deps.contains(packageName) + } + } + } +} From f66d4d77aecba45491d922894de0b6181b263c4a Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov <70969943+rudolf101@users.noreply.github.com> Date: Mon, 27 Feb 2023 13:13:16 +0300 Subject: [PATCH 4/8] Enhanced package installation for JavaScript --- .../plugin/language/js/JsDialogProcessor.kt | 49 +++++-------- .../language/js/NycSourceFileChooser.kt | 5 +- .../src/main/kotlin/api/JsTestGenerator.kt | 4 ++ .../kotlin/settings/JsPackagesSettings.kt | 70 ++++++++++++++----- 4 files changed, 77 insertions(+), 51 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 3a9b72a46a..c7c762cb3d 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,6 +18,7 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFileFactory import com.intellij.psi.impl.file.PsiDirectoryFactory import com.intellij.util.concurrency.AppExecutorUtil +import java.io.IOException import mu.KotlinLogging import org.jetbrains.kotlin.idea.util.application.invokeLater import org.jetbrains.kotlin.idea.util.application.runReadAction @@ -28,15 +29,11 @@ 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.JsPackagesSettings.nycData -import settings.JsPackagesSettings.ternData import settings.JsTestGenerationSettings.dummyClassName -import settings.PackageData +import settings.PackageDataService +import settings.jsPackagesList import utils.JsCmdExec import utils.OsProvider -import java.io.IOException -import settings.PackageDataService private val logger = KotlinLogging.logger {} @@ -59,9 +56,11 @@ object JsDialogProcessor { ) { override fun run(indicator: ProgressIndicator) { invokeLater { - checkAndInstallRequirement(model.project, model.pathToNPM, mochaData) - checkAndInstallRequirement(model.project, model.pathToNPM, nycData) - checkAndInstallRequirement(model.project, model.pathToNPM, ternData) + PackageDataService( + model.containingFilePath, + model.project.basePath!!, + model.pathToNPM + ).checkAndInstallRequirements(project) createDialog(model)?.let { dialogWindow -> if (!dialogWindow.showAndGet()) return@invokeLater // Since Tern.js accesses containing file, sync with file system required before test generation. @@ -107,7 +106,6 @@ object JsDialogProcessor { null } - private fun createJsTestModel( project: Project, srcModule: Module, @@ -139,7 +137,6 @@ object JsDialogProcessor { this.pathToNode = pathToNode this.pathToNPM = pathToNPM } - } private fun createDialog(jsTestsModel: JsTestsModel?) = jsTestsModel?.let { JsDialogWindow(it) } @@ -268,30 +265,19 @@ object JsDialogProcessor { } } -fun PackageDataService.checkAndInstallRequirement( - project: Project, - pathToNPM: String, - requirement: PackageData, -) { - if (!this.findPackageByNpm(requirement, project.basePath!!, pathToNPM)) { - installMissingRequirement(project, pathToNPM, requirement) - } -} - -private fun installMissingRequirement( - project: Project, - pathToNPM: String, - requirement: PackageData, -) { +private fun PackageDataService.checkAndInstallRequirements(project: Project) { + val absentPackages = jsPackagesList + .filterNot { this.findPackage(it) } + if (absentPackages.isEmpty()) return val message = """ - Requirement is not installed: - ${requirement.packageName} - Install it? + Requirements are not installed: + ${absentPackages.joinToString { it.packageName }} + Install them? """.trimIndent() val result = Messages.showOkCancelDialog( project, message, - "Requirement Missmatch Error", + "Requirements Missmatch Error", "Install", "Cancel", null @@ -300,8 +286,7 @@ private fun installMissingRequirement( if (result == Messages.CANCEL) return - val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM) - + val (_, errorText) = this.installAbsentPackages(absentPackages) if (errorText.isNotEmpty()) { showErrorDialogLater( project, 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 9575d75284..ebe84a48b3 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,7 @@ 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.JsPackagesSettings.nycData +import settings.PackageDataService import utils.OsProvider @@ -24,8 +24,7 @@ class NycSourceFileChooser(val model: JsTestsModel) : TextFieldWithBrowseButton( addBrowseFolderListener( TextBrowseFolderListener(descriptor, model.project) ) - text = (replaceSeparator(nycData.findPackagePath() ?: "Nyc was not found") - + OsProvider.getProviderByOs().npmPackagePostfix) + text = PackageDataService.nycPath } fun validateNyc(): ValidationInfo? { diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index a35344f7b9..0d3685e7df 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -100,6 +100,10 @@ class JsTestGenerator( parsedFile = parsedFile, settings = settings, ) + context.packageJson = PackageJsonService( + sourceFilePath, + projectPath, + ).findClosestConfig() val ternService = TernService(context) val paramNames = mutableMapOf>() val testSets = mutableListOf() diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt index b10d05bfc3..9e858b1fb8 100644 --- a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -1,6 +1,10 @@ package settings +import org.utbot.common.PathUtil.replaceSeparator import service.PackageJsonService +import settings.JsPackagesSettings.mochaData +import settings.JsPackagesSettings.nycData +import settings.JsPackagesSettings.ternData import utils.JsCmdExec import utils.OsProvider @@ -10,6 +14,12 @@ object JsPackagesSettings { val ternData: PackageData = PackageData("tern", NpmListFlag.L) } +val jsPackagesList = listOf( + mochaData, + nycData, + ternData +) + enum class NpmListFlag { L { override fun toString(): String = "-l" @@ -34,40 +44,68 @@ data class PackageData( 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.toString(), packageName) - ) - - return Pair(inputText, errorText) - } } class PackageDataService( filePathToInference: String, - projectPath: String, + private val projectPath: String, + private val pathToNpm: String, ) { private val packageJson = PackageJsonService(filePathToInference, projectPath).findClosestConfig() - fun findPackageByNpm(packageData: PackageData, projectBasePath: String, pathToNpm: String): Boolean = with(packageData) { + companion object { + var nycPath: String = "" + private set + } + + fun findPackage(packageData: PackageData): Boolean = with(packageData) { when (npmListFlag) { NpmListFlag.G -> { val (inputText, _) = JsCmdExec.runCommand( - dir = projectBasePath, + dir = projectPath, shouldWait = true, timeout = 10, cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag.toString()) ) - - inputText.contains(packageName) + var result = inputText.contains(packageName) + if (!result || this == nycData) { + val packagePath = this.findPackagePath() + nycPath = packagePath?.let { + replaceSeparator(it) + OsProvider.getProviderByOs().npmPackagePostfix + } ?: "Nyc was not found" + if (!result) { + result = this.findPackagePath()?.isNotBlank() ?: false + } + } + return result } + NpmListFlag.L -> { packageJson.deps.contains(packageName) } } } + + fun installAbsentPackages(packages: List): Pair { + if (packages.isEmpty()) return "" to "" + val localPackageNames = packages.filter { it.npmListFlag == NpmListFlag.L } + .map { it.packageName }.toTypedArray() + val globalPackageNames = packages.filter { it.npmListFlag == NpmListFlag.G } + .map { it.packageName }.toTypedArray() + // Local packages installation + val (inputTextL, errorTextL) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.L.toString(), *localPackageNames) + ) + // Global packages installation + val (inputTextG, errorTextG) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.G.toString(), *globalPackageNames) + ) + return Pair(inputTextL + inputTextG, errorTextL + errorTextG) + } } From 88a96774bc93eee85bcd22940153e4df8ca817d4 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 15:58:23 +0300 Subject: [PATCH 5/8] Add JSON file consistency check --- utbot-js/src/main/kotlin/service/PackageJsonService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index a838af0ec3..1d5d04a2b2 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -1,8 +1,8 @@ package service +import org.json.JSONObject import java.io.File import java.io.FilenameFilter -import org.json.JSONObject data class PackageJson( val isModule: Boolean, @@ -36,7 +36,7 @@ class PackageJsonService( val configAsJson = JSONObject(configFile.readText()) return PackageJson( isModule = configAsJson.optString("type") == "module", - deps = configAsJson.optJSONObject("dependencies").keySet() ?: emptySet() + deps = configAsJson.optJSONObject("dependencies")?.keySet() ?: emptySet() ) } } From 769bf7dbfc5a8279dd89d6b5412873a10011d343 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov <70969943+rudolf101@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:00:43 +0300 Subject: [PATCH 6/8] Add message about time expiration for npm packages installation --- .../plugin/language/js/JsDialogProcessor.kt | 118 ++++++++++-------- .../kotlin/settings/JsPackagesSettings.kt | 45 ++++--- 2 files changed, 93 insertions(+), 70 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 c7c762cb3d..035bb92660 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,12 +18,12 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFileFactory import com.intellij.psi.impl.file.PsiDirectoryFactory import com.intellij.util.concurrency.AppExecutorUtil -import java.io.IOException 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 import org.jetbrains.kotlin.konan.file.File +import org.utbot.framework.plugin.api.TimeoutException import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.testModules import settings.JsDynamicSettings @@ -34,6 +34,7 @@ import settings.PackageDataService import settings.jsPackagesList import utils.JsCmdExec import utils.OsProvider +import java.io.IOException private val logger = KotlinLogging.logger {} @@ -56,11 +57,10 @@ object JsDialogProcessor { ) { override fun run(indicator: ProgressIndicator) { invokeLater { - PackageDataService( - model.containingFilePath, - model.project.basePath!!, - model.pathToNPM - ).checkAndInstallRequirements(project) + if (!PackageDataService( + model.containingFilePath, model.project.basePath!!, model.pathToNPM + ).checkAndInstallRequirements(project) + ) return@invokeLater createDialog(model)?.let { dialogWindow -> if (!dialogWindow.showAndGet()) return@invokeLater // Since Tern.js accesses containing file, sync with file system required before test generation. @@ -76,35 +76,31 @@ 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 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, @@ -157,10 +153,8 @@ object JsDialogProcessor { val testDir = PsiDirectoryFactory.getInstance(project).createDirectory( model.testSourceRoot!! ) - val testFileName = normalizedContainingFilePath.substringAfterLast("/") - .replace(Regex(".js"), "Test.js") - val testGenerator = JsTestGenerator( - fileText = editor.document.text, + val testFileName = normalizedContainingFilePath.substringAfterLast("/").replace(Regex(".js"), "Test.js") + val testGenerator = JsTestGenerator(fileText = editor.document.text, sourceFilePath = normalizedContainingFilePath, projectPath = model.project.basePath?.replace(File.separator, "/") ?: throw IllegalStateException("Can't access project path."), @@ -265,33 +259,47 @@ object JsDialogProcessor { } } -private fun PackageDataService.checkAndInstallRequirements(project: Project) { - val absentPackages = jsPackagesList - .filterNot { this.findPackage(it) } - if (absentPackages.isEmpty()) return +private fun PackageDataService.checkAndInstallRequirements(project: Project): Boolean { + val missingPackages = jsPackagesList.filterNot { this.findPackage(it) } + if (missingPackages.isEmpty()) return true val message = """ Requirements are not installed: - ${absentPackages.joinToString { it.packageName }} + ${missingPackages.joinToString { it.packageName }} Install them? """.trimIndent() val result = Messages.showOkCancelDialog( - project, - message, - "Requirements Missmatch Error", - "Install", - "Cancel", - null + project, message, "Requirements Missmatch Error", "Install", "Cancel", null ) if (result == Messages.CANCEL) - return + return false - val (_, errorText) = this.installAbsentPackages(absentPackages) - if (errorText.isNotEmpty()) { + try { + val (_, errorText) = this.installMissingPackages(missingPackages) + if (errorText.isNotEmpty()) { + showErrorDialogLater( + project, + "Requirements installing failed with some reason:\n${errorText}", + "Requirement installation error" + ) + return false + } + return true + } catch (_: TimeoutException) { showErrorDialogLater( project, - "Requirements installing failed with some reason:\n${errorText}", - "Requirements error" + """ + Requirements installing failed due to the exceeded waiting time for the installation, check your internet connection. + + Try to install missing npm packages manually: + ${ + missingPackages.joinToString(separator = "\n") { + "> npm install ${it.npmListFlag} ${it.packageName}" + } + } + """.trimIndent(), + "Requirement installation error" ) + return false } } diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt index 9e858b1fb8..3da41c4387 100644 --- a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -86,26 +86,41 @@ class PackageDataService( } } - fun installAbsentPackages(packages: List): Pair { - if (packages.isEmpty()) return "" to "" + fun installMissingPackages(packages: List): Pair { + var inputTextAllPackages = "" + var errorTextAllPackages = "" + if (packages.isEmpty()) return inputTextAllPackages to errorTextAllPackages + val localPackageNames = packages.filter { it.npmListFlag == NpmListFlag.L } .map { it.packageName }.toTypedArray() val globalPackageNames = packages.filter { it.npmListFlag == NpmListFlag.G } .map { it.packageName }.toTypedArray() + // Local packages installation - val (inputTextL, errorTextL) = JsCmdExec.runCommand( - dir = projectPath, - shouldWait = true, - timeout = 10, - cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.L.toString(), *localPackageNames) - ) + if (localPackageNames.isNotEmpty()) { + val (inputText, errorText) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.L.toString(), *localPackageNames) + ) + inputTextAllPackages += inputText + errorTextAllPackages += errorText + } // Global packages installation - val (inputTextG, errorTextG) = JsCmdExec.runCommand( - dir = projectPath, - shouldWait = true, - timeout = 10, - cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.G.toString(), *globalPackageNames) - ) - return Pair(inputTextL + inputTextG, errorTextL + errorTextG) + if (globalPackageNames.isNotEmpty()) { + val (inputText, errorText) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.G.toString(), *globalPackageNames) + ) + inputTextAllPackages += inputText + errorTextAllPackages += errorText + } + // Find path to nyc execution file after installation + if (packages.contains(nycData)) findPackage(nycData) + + return Pair(inputTextAllPackages, errorTextAllPackages) } } From 79eaa63639e8851b24c21ab40586c1fffde0385a Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 16:34:34 +0300 Subject: [PATCH 7/8] Add packageJson in ServiceContext --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 3 ++- utbot-js/src/main/kotlin/service/ServiceContext.kt | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 0d3685e7df..eafa82384b 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -30,6 +30,7 @@ import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control +import org.utbot.fuzzing.utils.Trie import parser.JsClassAstVisitor import parser.JsFunctionAstVisitor import parser.JsFuzzerAstVisitor @@ -44,6 +45,7 @@ import parser.JsToplevelFunctionAstVisitor import service.CoverageMode import service.CoverageServiceProvider import service.InstrumentationService +import service.PackageJsonService import service.ServiceContext import service.TernService import settings.JsDynamicSettings @@ -56,7 +58,6 @@ import utils.constructClass import utils.toJsAny import java.io.File import java.util.concurrent.CancellationException -import org.utbot.fuzzing.utils.Trie private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/ServiceContext.kt b/utbot-js/src/main/kotlin/service/ServiceContext.kt index 387b567641..29315e139d 100644 --- a/utbot-js/src/main/kotlin/service/ServiceContext.kt +++ b/utbot-js/src/main/kotlin/service/ServiceContext.kt @@ -9,7 +9,8 @@ class ServiceContext( override val filePathToInference: String, override val parsedFile: Node, override val settings: JsDynamicSettings, -): ContextOwner + override var packageJson: PackageJson = PackageJson.defaultConfig +) : ContextOwner interface ContextOwner { val utbotDir: String @@ -17,4 +18,5 @@ interface ContextOwner { val filePathToInference: String val parsedFile: Node val settings: JsDynamicSettings -} \ No newline at end of file + var packageJson: PackageJson +} From 408b7a7ef0fa2ba10e0504425e0012e951a7a325 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 17:03:20 +0300 Subject: [PATCH 8/8] Text corrections --- .../utbot/intellij/plugin/language/js/JsDialogProcessor.kt | 4 ++-- utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 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 035bb92660..9b2549ee12 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 @@ -280,7 +280,7 @@ private fun PackageDataService.checkAndInstallRequirements(project: Project): Bo showErrorDialogLater( project, "Requirements installing failed with some reason:\n${errorText}", - "Requirement installation error" + "Failed to install requirements" ) return false } @@ -298,7 +298,7 @@ private fun PackageDataService.checkAndInstallRequirements(project: Project): Bo } } """.trimIndent(), - "Requirement installation error" + "Failed to install requirements" ) return false } diff --git a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt index 2031b32c5e..37922d5dfe 100644 --- a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt @@ -5,7 +5,7 @@ object JsTestGenerationSettings { // Used for toplevel functions in IDEA plugin. const val dummyClassName = "toplevelHack" - // Default timeout for Node.js to try run a single testcase. + // Default timeout for Node.js try to run a single testcase. const val defaultTimeout = 10L const val fuzzingTimeout = 30_000L @@ -18,4 +18,4 @@ object JsTestGenerationSettings { // Number of test cases that can fit in one temporary file for Fast coverage mode const val fuzzingThreshold = 300 -} \ No newline at end of file +}