Skip to content

Commit 2d22922

Browse files
zishkazrudolf101
andauthored
Fixed test generation for paths with spaces in JavaScript (#1596)
* Fixed spaces in paths JS * Rollbacked npm packages auto installation changes * Implemented npm package postfix to support both win and linux * Moved search for Node.js interpreter before npm cmd call * Fixed exception if path to Node.js in settings is corrupted * Removed .cmd hardcode * Optimize imports * Refactoring packages auto-installation --------- Co-authored-by: Vladislav Kasimov <vladislavkasimov@gmail.com>
1 parent 20698af commit 2d22922

File tree

12 files changed

+127
-112
lines changed

12 files changed

+127
-112
lines changed

utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ class JsCoverageCommand : CliktCommand(name = "coverage_js", help = "Get tests c
5252
shouldWait = true,
5353
timeout = 20,
5454
cmd = arrayOf(
55-
pathToNYC,
56-
"--report-dir=$coverageDataPath",
55+
"\"$pathToNYC\"",
56+
"--report-dir=\"$coverageDataPath\"",
5757
"--reporter=\"clover\"",
58-
"--temp-dir=${workingDir}/cache",
58+
"--temp-dir=\"${workingDir}/cache\"",
5959
"mocha",
60-
testFileAbsolutePath
60+
"\"$testFileAbsolutePath\""
6161
)
6262
)
6363
val coveredList = mutableListOf<Int>()

utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class JsRunTestsCommand : CliktCommand(name = "run_js", help = "Runs tests for t
4242
val (inputText, errorText) = JsCmdExec.runCommand(
4343
dir = dir,
4444
shouldWait = true,
45-
cmd = arrayOf("mocha", fileWithTestsAbsolutePath)
45+
cmd = arrayOf("mocha", "\"$fileWithTestsAbsolutePath\"")
4646
)
4747
if (errorText.isNotEmpty()) {
4848
logger.error { "An error has occurred while running tests for $fileWithTests: $errorText" }

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import com.intellij.psi.PsiDocumentManager
1818
import com.intellij.psi.PsiFileFactory
1919
import com.intellij.psi.impl.file.PsiDirectoryFactory
2020
import com.intellij.util.concurrency.AppExecutorUtil
21-
import framework.codegen.Mocha
2221
import mu.KotlinLogging
2322
import org.jetbrains.kotlin.idea.util.application.invokeLater
2423
import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -29,8 +28,12 @@ import org.utbot.intellij.plugin.ui.utils.testModules
2928
import settings.JsDynamicSettings
3029
import settings.JsExportsSettings.endComment
3130
import settings.JsExportsSettings.startComment
31+
import settings.JsPackagesSettings.mochaData
3232
import settings.JsTestGenerationSettings.dummyClassName
33+
import settings.PackageData
3334
import utils.JsCmdExec
35+
import utils.OsProvider
36+
import java.io.IOException
3437

3538
private val logger = KotlinLogging.logger {}
3639

@@ -45,14 +48,17 @@ object JsDialogProcessor {
4548
editor: Editor,
4649
file: JSFile
4750
) {
48-
val model = createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file)
51+
val model =
52+
createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file) ?: return
4953
(object : Task.Backgroundable(
5054
project,
5155
"Check the requirements"
5256
) {
5357
override fun run(indicator: ProgressIndicator) {
5458
invokeLater {
55-
getFrameworkLibraryPath(Mocha.displayName.lowercase(), model)
59+
if (!mochaData.findPackageByNpm(model.project.basePath!!, model.pathToNPM)) {
60+
installMissingRequirement(model.project, model.pathToNPM, mochaData)
61+
}
5662
createDialog(model)?.let { dialogProcessor ->
5763
if (!dialogProcessor.showAndGet()) return@invokeLater
5864
// Since Tern.js accesses containing file, sync with file system required before test generation.
@@ -68,6 +74,37 @@ object JsDialogProcessor {
6874
}).queue()
6975
}
7076

77+
private fun findNodeAndNPM(): Pair<String, String>? =
78+
try {
79+
val pathToNode = NodeJsLocalInterpreterManager.getInstance()
80+
.interpreters.first().interpreterSystemIndependentPath
81+
val (_, errorText) = JsCmdExec.runCommand(
82+
shouldWait = true,
83+
cmd = arrayOf("\"${pathToNode}\"", "-v")
84+
)
85+
if (errorText.isNotEmpty()) throw NoSuchElementException()
86+
val pathToNPM =
87+
pathToNode.substringBeforeLast("/") + "/" + "npm" + OsProvider.getProviderByOs().npmPackagePostfix
88+
pathToNode to pathToNPM
89+
} catch (e: NoSuchElementException) {
90+
Messages.showErrorDialog(
91+
"Node.js interpreter is not found in IDEA settings.\n" +
92+
"Please set it in Settings > Languages & Frameworks > Node.js",
93+
"Requirement Error"
94+
)
95+
logger.error { "Node.js interpreter was not found in IDEA settings." }
96+
null
97+
} catch (e: IOException) {
98+
Messages.showErrorDialog(
99+
"Node.js interpreter path is corrupted in IDEA settings.\n" +
100+
"Please check Settings > Languages & Frameworks > Node.js",
101+
"Requirement Error"
102+
)
103+
logger.error { "Node.js interpreter path is corrupted in IDEA settings." }
104+
null
105+
}
106+
107+
71108
private fun createJsTestModel(
72109
project: Project,
73110
srcModule: Module,
@@ -86,43 +123,22 @@ object JsDialogProcessor {
86123
showErrorDialogLater(project, errorMessage, "Test source roots not found")
87124
return null
88125
}
126+
val (pathToNode, pathToNPM) = findNodeAndNPM() ?: return null
89127
return JsTestsModel(
90128
project = project,
91129
srcModule = srcModule,
92130
potentialTestModules = testModules,
93131
fileMethods = fileMethods,
94132
selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(),
95-
file = file
133+
file = file,
96134
).apply {
97135
containingFilePath = filePath
136+
this.pathToNode = pathToNode
137+
this.pathToNPM = pathToNPM
98138
}
99-
100139
}
101140

102-
private fun createDialog(
103-
jsTestsModel: JsTestsModel?
104-
): JsDialogWindow? {
105-
try {
106-
jsTestsModel?.pathToNode = NodeJsLocalInterpreterManager.getInstance()
107-
.interpreters.first().interpreterSystemIndependentPath
108-
val (_, errorText) = JsCmdExec.runCommand(
109-
shouldWait = true,
110-
cmd = arrayOf("node", "-v")
111-
)
112-
if (errorText.isNotEmpty()) throw NoSuchElementException()
113-
} catch (e: NoSuchElementException) {
114-
Messages.showErrorDialog(
115-
"Node.js interpreter is not found in IDEA settings.\n" +
116-
"Please set it in Settings > Languages & Frameworks > Node.js",
117-
"Requirement Error"
118-
)
119-
logger.error { "Node.js interpreter was not found in IDEA settings." }
120-
return null
121-
}
122-
return jsTestsModel?.let {
123-
JsDialogWindow(it)
124-
}
125-
}
141+
private fun createDialog(jsTestsModel: JsTestsModel?) = jsTestsModel?.let { JsDialogWindow(it) }
126142

127143
private fun unblockDocument(project: Project, document: Document) {
128144
PsiDocumentManager.getInstance(project).apply {
@@ -248,10 +264,10 @@ object JsDialogProcessor {
248264
}
249265

250266
// TODO(MINOR): Add indicator.text for each installation
251-
fun installMissingRequirement(project: Project, pathToNPM: String, requirement: String) {
267+
fun installMissingRequirement(project: Project, pathToNPM: String, requirement: PackageData) {
252268
val message = """
253269
Requirement is not installed:
254-
$requirement
270+
${requirement.packageName}
255271
Install it?
256272
""".trimIndent()
257273
val result = Messages.showOkCancelDialog(
@@ -266,7 +282,7 @@ fun installMissingRequirement(project: Project, pathToNPM: String, requirement:
266282
if (result == Messages.CANCEL)
267283
return
268284

269-
val (_, errorText) = installRequirement(pathToNPM, requirement, project.basePath)
285+
val (_, errorText) = requirement.installPackage(project.basePath!!, pathToNPM)
270286

271287
if (errorText.isNotEmpty()) {
272288
showErrorDialogLater(

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogWindow.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) {
5252
)
5353

5454
init {
55-
model.pathToNPM = model.pathToNode.substringBeforeLast("/") + "/" + "npm"
5655
title = "Generate Tests with UtBot"
5756
isResizable = false
5857
init()

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/NycSourceFileChooser.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import com.intellij.openapi.ui.TextBrowseFolderListener
55
import com.intellij.openapi.ui.TextFieldWithBrowseButton
66
import com.intellij.openapi.ui.ValidationInfo
77
import org.utbot.common.PathUtil.replaceSeparator
8-
import settings.JsDynamicSettings
8+
import settings.JsPackagesSettings.nycData
9+
import utils.OsProvider
910

1011

1112
class NycSourceFileChooser(val model: JsTestsModel) : TextFieldWithBrowseButton() {
@@ -23,11 +24,13 @@ class NycSourceFileChooser(val model: JsTestsModel) : TextFieldWithBrowseButton(
2324
addBrowseFolderListener(
2425
TextBrowseFolderListener(descriptor, model.project)
2526
)
26-
text = replaceSeparator(getFrameworkLibraryPath(JsDynamicSettings().pathToNYC, model) ?: "Nyc was not found")
27+
nycData.findPackagePath() ?: installMissingRequirement(model.project, model.pathToNPM, nycData)
28+
text = (replaceSeparator(nycData.findPackagePath() ?: "Nyc was not found")
29+
+ OsProvider.getProviderByOs().npmPackagePostfix)
2730
}
2831

2932
fun validateNyc(): ValidationInfo? {
30-
return if (replaceSeparator(text).endsWith("nyc"))
33+
return if (replaceSeparator(text).endsWith("nyc" + OsProvider.getProviderByOs().npmPackagePostfix))
3134
null
3235
else
3336
ValidationInfo("Nyc executable file was not found in the specified directory", this)

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt

Lines changed: 0 additions & 57 deletions
This file was deleted.

utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class JsFuzzerAstVisitor : IAstVisitor {
3838

3939
}
4040

41-
private fun validateNode(value: Any) {
41+
private fun validateNode(value: Any?) {
4242
when (value) {
4343
is String -> {
4444
fuzzedConcreteValues.add(

utbot-js/src/main/kotlin/parser/JsParserUtils.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package parser
22

33
import com.google.javascript.rhino.Node
4-
import java.lang.IllegalStateException
54
import org.utbot.fuzzer.FuzzedContext
6-
import parser.JsParserUtils.getMethodName
75

86
// Used for .children() calls.
97
@Suppress("DEPRECATION")
@@ -57,12 +55,12 @@ object JsParserUtils {
5755
/**
5856
* Called upon node with any kind of literal value token.
5957
*/
60-
fun Node.getAnyValue(): Any = when {
58+
fun Node.getAnyValue(): Any? = when {
6159
this.isNumber -> this.double
6260
this.isString || this.isName -> this.string
6361
this.isTrue -> true
6462
this.isFalse -> false
65-
else -> throw UnsupportedOperationException("Not yet implemented!")
63+
else -> null
6664
}
6765

6866
// For some reason Closure Compiler doesn't contain a built-in method
@@ -139,4 +137,4 @@ object JsParserUtils {
139137
* Called upon node with Method token.
140138
*/
141139
fun Node.isStatic(): Boolean = this.isStaticMember
142-
}
140+
}

utbot-js/src/main/kotlin/service/BasicCoverageService.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,12 @@ class BasicCoverageService(
105105
val (_, errorText) =
106106
JsCmdExec.runCommand(
107107
cmd = arrayOf(
108-
settings.pathToNYC,
109-
"--report-dir=$utbotDirPath/coverage$index",
108+
"\"${settings.pathToNYC}\"",
109+
"--report-dir=\"$utbotDir/coverage$index\"",
110110
"--reporter=json",
111-
"--temp-dir=$utbotDirPath/cache$index",
112-
"node",
113-
filePath
111+
"--temp-dir=\"$utbotDir/cache$index\"",
112+
"\"${settings.pathToNode}\"",
113+
"\"$filePath\""
114114
),
115115
shouldWait = true,
116116
dir = context.projectPath,

utbot-js/src/main/kotlin/service/TernService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ test("${context.filePathToInference}")
8181
JsCmdExec.runCommand(
8282
dir = path,
8383
shouldWait = true,
84-
cmd = arrayOf(context.settings.pathToNPM, "i", "tern", "-l")
84+
cmd = arrayOf("\"${context.settings.pathToNPM}\"", "i", "tern", "-l")
8585
)
8686
}
8787

@@ -97,7 +97,7 @@ test("${context.filePathToInference}")
9797
dir = "$projectPath/$utbotDir/",
9898
shouldWait = true,
9999
timeout = 20,
100-
cmd = arrayOf(settings.pathToNode, "${projectPath}/$utbotDir/ternScript.js"),
100+
cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""),
101101
)
102102
json = try {
103103
JSONObject(inputText.replaceAfterLast("}", ""))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package settings
2+
3+
import utils.JsCmdExec
4+
import utils.OsProvider
5+
6+
object JsPackagesSettings {
7+
val mochaData: PackageData = PackageData("mocha", "-l")
8+
val nycData: PackageData = PackageData("nyc", "-g")
9+
10+
// TODO(MINOR): Add tern auto installation
11+
val ternData: PackageData? = null
12+
}
13+
14+
data class PackageData(
15+
val packageName: String,
16+
val npmListFlag: String
17+
) {
18+
fun findPackageByNpm(projectBasePath: String, pathToNpm: String): Boolean {
19+
val (inputText, _) = JsCmdExec.runCommand(
20+
dir = projectBasePath,
21+
shouldWait = true,
22+
timeout = 10,
23+
cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag)
24+
)
25+
26+
return inputText.contains(packageName)
27+
}
28+
29+
fun findPackagePath(): String? {
30+
val (inputText, _) = JsCmdExec.runCommand(
31+
dir = null,
32+
shouldWait = true,
33+
timeout = 10,
34+
cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), packageName)
35+
)
36+
37+
return inputText.split(System.lineSeparator()).first().takeIf { it.contains(packageName) }
38+
}
39+
40+
fun installPackage(projectBasePath: String, pathToNpm: String): Pair<String, String> {
41+
val (inputText, errorText) = JsCmdExec.runCommand(
42+
dir = projectBasePath,
43+
shouldWait = true,
44+
timeout = 10,
45+
cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag, packageName)
46+
)
47+
48+
return Pair(inputText, errorText)
49+
}
50+
}

0 commit comments

Comments
 (0)