Skip to content

Fixed test generation for paths with spaces in JavaScript #1596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {}

Expand All @@ -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.
Expand All @@ -68,6 +74,37 @@ object JsDialogProcessor {
}).queue()
}

private fun findNodeAndNPM(): Pair<String, String>? =
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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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)
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class JsFuzzerAstVisitor : IAstVisitor {

}

private fun validateNode(value: Any) {
private fun validateNode(value: Any?) {
when (value) {
is String -> {
fuzzedConcreteValues.add(
Expand Down
8 changes: 3 additions & 5 deletions utbot-js/src/main/kotlin/parser/JsParserUtils.kt
Original file line number Diff line number Diff line change
@@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -139,4 +137,4 @@ object JsParserUtils {
* Called upon node with Method token.
*/
fun Node.isStatic(): Boolean = this.isStaticMember
}
}
10 changes: 5 additions & 5 deletions utbot-js/src/main/kotlin/service/BasicCoverageService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions utbot-js/src/main/kotlin/service/TernService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
}

Expand All @@ -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("}", ""))
Expand Down
50 changes: 50 additions & 0 deletions utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt
Original file line number Diff line number Diff line change
@@ -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<String, String> {
val (inputText, errorText) = JsCmdExec.runCommand(
dir = projectBasePath,
shouldWait = true,
timeout = 10,
cmd = arrayOf("\"$pathToNpm\"", "install", npmListFlag, packageName)
)

return Pair(inputText, errorText)
}
}
Loading