From 020adc740b0e416ea0c1e6065e6cbb47b14daa76 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Sun, 5 Feb 2023 01:23:46 +0300 Subject: [PATCH 01/74] [WIP] Better imports manager for JS + removed thisInstance builder --- .../src/main/kotlin/api/JsTestGenerator.kt | 22 +++-- .../src/main/kotlin/parser/JsAstScrapper.kt | 90 +++++++++++++++++++ .../src/main/kotlin/parser/JsParserUtils.kt | 7 ++ 3 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 utbot-js/src/main/kotlin/parser/JsAstScrapper.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index a35344f7b9..2ca5460527 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -30,8 +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 parser.JsClassAstVisitor -import parser.JsFunctionAstVisitor +import parser.JsAstScrapper import parser.JsFuzzerAstVisitor import parser.JsParserUtils import parser.JsParserUtils.getAbstractFunctionName @@ -47,7 +46,6 @@ import service.InstrumentationService import service.ServiceContext import service.TernService import settings.JsDynamicSettings -import settings.JsTestGenerationSettings.dummyClassName import settings.JsTestGenerationSettings.fuzzingThreshold import settings.JsTestGenerationSettings.fuzzingTimeout import utils.PathResolver @@ -76,6 +74,8 @@ class JsTestGenerator( private lateinit var parsedFile: Node + private lateinit var astScrapper: JsAstScrapper + private val utbotDir = "utbotJs" init { @@ -93,6 +93,7 @@ class JsTestGenerator( */ fun run(): String { parsedFile = runParser(fileText) + astScrapper = JsAstScrapper(parsedFile) val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, @@ -347,12 +348,11 @@ class JsTestGenerator( } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { - val visitor = JsFunctionAstVisitor( - focusedMethodName, - if (parentClassName != dummyClassName) parentClassName else null - ) - visitor.accept(parsedFile) - return visitor.targetFunctionNode + return parentClassName?.let { astScrapper.findMethod(focusedMethodName, parentClassName) } + ?: astScrapper.findFunction(focusedMethodName) + ?: throw IllegalStateException( + "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" + ) } private fun getMethodsToTest() = @@ -363,9 +363,7 @@ class JsTestGenerator( } private fun getClassMethods(className: String): List { - val visitor = JsClassAstVisitor(className) - visitor.accept(parsedFile) - val classNode = JsParserUtils.searchForClassDecl(className, parsedFile) + val classNode = astScrapper.findClass(className) return classNode?.getClassMethods() ?: throw IllegalStateException("Can't extract methods of class $className") } } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt new file mode 100644 index 0000000000..9691fbc109 --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -0,0 +1,90 @@ +package parser + +import com.google.javascript.jscomp.Compiler +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.jscomp.SourceFile +import com.google.javascript.rhino.Node +import java.io.File +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.isRequireImport + +class JsAstScrapper(private val parsedFile: Node) { + + // Used not to parse the same file multiple times. + private val _parsedFilesCache = mutableMapOf() + + fun findFunction(key: String): Node? { + if (importsMap[key]?.isFunction == true) return importsMap[key] + val functionVisitor = JsFunctionAstVisitor(key, null) + functionVisitor.accept(parsedFile) + return try { + functionVisitor.targetFunctionNode + } catch(e: Exception) { null } + } + + fun findClass(key: String): Node? { + if (importsMap[key]?.isClass == true) return importsMap[key] + val classVisitor = JsClassAstVisitor(key) + classVisitor.accept(parsedFile) + return try { + classVisitor.targetClassNode + } catch (e: Exception) { null } + } + + fun findMethod(classKey: String, methodKey: String): Node? { + val classNode = findClass(classKey) + return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } + } + + private val importsMap = run { + val visitor = Visitor() + visitor.accept(parsedFile) + visitor.importNodes.fold(emptyMap()) { acc, node -> + mapOf(*acc.toList().toTypedArray(), *node.importedNodes().toList().toTypedArray()) + } + } + + private fun Node.importedNodes(): Map { + return when { + this.isRequireImport() -> mapOf( + this.parent!!.string to this.firstChild!!.next!! + ) + else -> emptyMap() + } + } + + private fun makePathFromImport() { +// val relPath = node.importText + if (node.importText.endsWith(".ts")) "" else ".ts" +// // If import text doesn't contain "/", then it is NodeJS stdlib import. +// if (!relPath.contains("/")) return true +// val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)) + } + + private fun File.findEntityInFile(key: String): Node? { + val parsedFile = _parsedFilesCache.putIfAbsent( + path, + Compiler().parse(SourceFile.fromCode("jsFile", readText())) + )!! + val localScrapper = JsAstScrapper(parsedFile) + return localScrapper.findClass(key) + ?: localScrapper.findFunction(key) + + } + + + + private class Visitor: IAstVisitor { + + private val _importNodes = mutableListOf() + + val importNodes: List + get() = _importNodes.toList() + + override fun accept(rootNode: Node) { + NodeUtil.visitPreOrder(rootNode) { node -> + if (node.isImport || node.isRequireImport()) _importNodes += node + } + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 05a3676a83..4bf6aa8259 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -3,7 +3,9 @@ package parser import com.google.javascript.jscomp.Compiler import com.google.javascript.jscomp.SourceFile 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") @@ -142,4 +144,9 @@ object JsParserUtils { * Called upon node with Method token. */ fun Node.isStatic(): Boolean = this.isStaticMember + + /** + * Checks if node is "required" JavaScript import. + */ + fun Node.isRequireImport(): Boolean = this.isCall && this.firstChild?.string == "require" } From bd1d38ce49ad884e37f2aaaa1e0fda6fbc008cb9 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Sat, 11 Feb 2023 12:06:59 +0300 Subject: [PATCH 02/74] Implemented AST scrapper for better imports handling --- .../src/main/kotlin/api/JsTestGenerator.kt | 2 +- .../src/main/kotlin/parser/JsAstScrapper.kt | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 2ca5460527..d680fcd21e 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -93,7 +93,7 @@ class JsTestGenerator( */ fun run(): String { parsedFile = runParser(fileText) - astScrapper = JsAstScrapper(parsedFile) + astScrapper = JsAstScrapper(parsedFile, sourceFilePath) val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index 9691fbc109..cbb6da4c02 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -5,11 +5,16 @@ import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.io.File +import java.nio.file.Paths import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getClassMethods import parser.JsParserUtils.isRequireImport +import kotlin.io.path.pathString -class JsAstScrapper(private val parsedFile: Node) { +class JsAstScrapper( + private val parsedFile: Node, + private val basePath: String, +) { // Used not to parse the same file multiple times. private val _parsedFilesCache = mutableMapOf() @@ -48,27 +53,32 @@ class JsAstScrapper(private val parsedFile: Node) { private fun Node.importedNodes(): Map { return when { this.isRequireImport() -> mapOf( - this.parent!!.string to this.firstChild!!.next!! + this.parent!!.string to (makePathFromImport(this.firstChild!!.next!!)?.let { + File(it).findEntityInFile() + // Workaround for std imports. + } ?: this.firstChild!!.next!!) ) else -> emptyMap() } } - private fun makePathFromImport() { -// val relPath = node.importText + if (node.importText.endsWith(".ts")) "" else ".ts" -// // If import text doesn't contain "/", then it is NodeJS stdlib import. -// if (!relPath.contains("/")) return true -// val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)) + private fun makePathFromImport(importNode: Node): String? { + val relPath = importNode.string + if (importNode.string.endsWith(".js")) "" else ".js" + // If import text doesn't contain "/", then it is NodeJS stdlib import. + if (!relPath.contains("/")) return null + val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString + return finalPath } - private fun File.findEntityInFile(key: String): Node? { + private fun File.findEntityInFile(): Node { val parsedFile = _parsedFilesCache.putIfAbsent( path, Compiler().parse(SourceFile.fromCode("jsFile", readText())) )!! - val localScrapper = JsAstScrapper(parsedFile) - return localScrapper.findClass(key) - ?: localScrapper.findFunction(key) +// val localScrapper = JsAstScrapper(parsedFile, basePath) +// return localScrapper.findClass(key) +// ?: localScrapper.findFunction(key) + return parsedFile } From b89bdcd8cc7506dd5fb7d4fb96ce0bb0c7cd41af Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sun, 12 Feb 2023 19:16:54 +0300 Subject: [PATCH 03/74] Fixed wrong argument sequence in function call --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index d680fcd21e..d7f572dd58 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -348,7 +348,7 @@ class JsTestGenerator( } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { - return parentClassName?.let { astScrapper.findMethod(focusedMethodName, parentClassName) } + return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName) } ?: astScrapper.findFunction(focusedMethodName) ?: throw IllegalStateException( "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" From f6cde43660a60e73c9b0175c091968d33b911e46 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 09:43:33 +0300 Subject: [PATCH 04/74] Implemented require import manager for JavaScript --- .../src/main/kotlin/parser/JsAstScrapper.kt | 22 +++-------- .../src/main/kotlin/parser/JsParserUtils.kt | 15 +++++++- .../kotlin/service/InstrumentationService.kt | 38 ++++++++++++++++--- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index cbb6da4c02..dec6e73f1a 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -8,6 +8,7 @@ import java.io.File import java.nio.file.Paths import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getRequireImportText import parser.JsParserUtils.isRequireImport import kotlin.io.path.pathString @@ -53,7 +54,7 @@ class JsAstScrapper( private fun Node.importedNodes(): Map { return when { this.isRequireImport() -> mapOf( - this.parent!!.string to (makePathFromImport(this.firstChild!!.next!!)?.let { + this.parent!!.string to (makePathFromImport(this.getRequireImportText())?.let { File(it).findEntityInFile() // Workaround for std imports. } ?: this.firstChild!!.next!!) @@ -62,28 +63,17 @@ class JsAstScrapper( } } - private fun makePathFromImport(importNode: Node): String? { - val relPath = importNode.string + if (importNode.string.endsWith(".js")) "" else ".js" + private fun makePathFromImport(importText: String): String? { + val relPath = importText + if (importText.endsWith(".js")) "" else ".js" // If import text doesn't contain "/", then it is NodeJS stdlib import. if (!relPath.contains("/")) return null - val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString - return finalPath + return Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString } private fun File.findEntityInFile(): Node { - val parsedFile = _parsedFilesCache.putIfAbsent( - path, - Compiler().parse(SourceFile.fromCode("jsFile", readText())) - )!! -// val localScrapper = JsAstScrapper(parsedFile, basePath) -// return localScrapper.findClass(key) -// ?: localScrapper.findFunction(key) - return parsedFile - + return Compiler().parse(SourceFile.fromCode("jsFile", readText())) } - - private class Visitor: IAstVisitor { private val _importNodes = mutableListOf() diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 4bf6aa8259..495fb7303c 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -146,7 +146,18 @@ object JsParserUtils { fun Node.isStatic(): Boolean = this.isStaticMember /** - * Checks if node is "required" JavaScript import. + * Checks if node is "require" JavaScript import. */ - fun Node.isRequireImport(): Boolean = this.isCall && this.firstChild?.string == "require" + fun Node.isRequireImport(): Boolean = try { + this.isCall && this.firstChild?.string == "require" + } catch (e: ClassCastException) { + false + } + + /** + * Called upon "require" JavaScript import. + * + * Returns path to imported file as [String]. + */ + fun Node.getRequireImportText(): String = this.firstChild!!.next!!.string } diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt index dc81a71f09..f613c1f635 100644 --- a/utbot-js/src/main/kotlin/service/InstrumentationService.kt +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -1,19 +1,26 @@ package service +import com.google.javascript.jscomp.CodePrinter import com.google.javascript.jscomp.NodeUtil import com.google.javascript.rhino.Node +import java.io.File +import java.nio.file.Paths import org.apache.commons.io.FileUtils import parser.JsFunctionAstVisitor import parser.JsParserUtils.getAnyValue +import parser.JsParserUtils.getRequireImportText +import parser.JsParserUtils.isRequireImport import parser.JsParserUtils.runParser import utils.JsCmdExec -import java.io.File +import utils.PathResolver.getRelativePath +import kotlin.io.path.pathString import kotlin.math.roundToInt class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair): ContextOwner by context { private val destinationFolderPath = "${projectPath}/${utbotDir}/instr" private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.substringAfterLast("/")}" + private lateinit var parsedInstrFile: Node lateinit var covFunName: String val allStatements: Set @@ -92,10 +99,8 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset } private fun getStatementMapKeys() = buildSet { - val fileText = File(instrumentedFilePath).readText() - val rootNode = runParser(fileText) val funcVisitor = JsFunctionAstVisitor(covFunName, null) - funcVisitor.accept(rootNode) + funcVisitor.accept(parsedInstrFile) val funcNode = funcVisitor.targetFunctionNode val funcLocation = getFuncLocation(funcNode) funcNode.findAndIterateOver("statementMap") { currKey -> @@ -134,15 +139,36 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset timeout = settings.timeout, ) val instrumentedFileText = File(instrumentedFilePath).readText() + parsedInstrFile = runParser(instrumentedFileText) val covFunRegex = Regex("function (cov_.*)\\(\\).*") val funName = covFunRegex.find(instrumentedFileText.takeWhile { it != '{' })?.groups?.get(1)?.value ?: throw IllegalStateException("") - val fixedFileText = "$instrumentedFileText\nexports.$funName = $funName" - File(instrumentedFilePath).writeText(fixedFileText) + val fixedFileText = fixImportsInInstrumentedFile() + "\nexports.$funName = $funName" + File(instrumentedFilePath).writeTextAndUpdate(fixedFileText) covFunName = funName } + private fun File.writeTextAndUpdate(newText: String) { + this.writeText(newText) + parsedInstrFile = runParser(File(instrumentedFilePath).readText()) + } + + private fun fixImportsInInstrumentedFile(): String { + // nyc poorly handles imports paths in file to instrument. Manual fix required. + NodeUtil.visitPreOrder(parsedInstrFile) { node -> + if (node.isRequireImport()) { + val currString = node.getRequireImportText() + val relPath = Paths.get(getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference).parent + )).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.string = relPath + } + } + return CodePrinter.Builder(parsedInstrFile).build() + } + fun removeTempFiles() { FileUtils.deleteDirectory(File("$projectPath/$utbotDir/instr")) } From 032de9c0280f1d2171aefea0effb904e97096d6a Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 11:40:38 +0300 Subject: [PATCH 05/74] Implemented package.json parser --- .../src/main/kotlin/api/JsTestGenerator.kt | 5 ++- .../main/kotlin/service/PackageJsonService.kt | 38 +++++++++++++++++++ .../src/main/kotlin/service/ServiceContext.kt | 2 + 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 utbot-js/src/main/kotlin/service/PackageJsonService.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index d7f572dd58..ac70d7d57b 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -43,6 +43,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 @@ -101,7 +102,7 @@ class JsTestGenerator( parsedFile = parsedFile, settings = settings, ) - val ternService = TernService(context) + context.packageJson = PackageJsonService(context).findClosestConfig() val paramNames = mutableMapOf>() val testSets = mutableListOf() val classNode = @@ -111,7 +112,7 @@ class JsTestGenerator( strict = selectedMethods?.isNotEmpty() ?: false ) parentClassName = classNode?.getClassName() - val classId = makeJsClassId(classNode, ternService) + val classId = makeJsClassId(classNode, TernService(context)) val methods = makeMethodsToTest() if (methods.isEmpty()) throw IllegalArgumentException("No methods to test were found!") methods.forEach { funcNode -> 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..81acc21ab2 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -0,0 +1,38 @@ +package service + +import java.io.File +import java.io.FilenameFilter +import org.json.JSONObject + +data class PackageJson( + val isModule: Boolean +) { + companion object { + val defaultConfig = PackageJson(false) + } +} + + +class PackageJsonService(context: ServiceContext) : ContextOwner by context { + + 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.getString("type") == "module"), + ) + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/ServiceContext.kt b/utbot-js/src/main/kotlin/service/ServiceContext.kt index 387b567641..36fb0a0376 100644 --- a/utbot-js/src/main/kotlin/service/ServiceContext.kt +++ b/utbot-js/src/main/kotlin/service/ServiceContext.kt @@ -9,6 +9,7 @@ class ServiceContext( override val filePathToInference: String, override val parsedFile: Node, override val settings: JsDynamicSettings, + override var packageJson: PackageJson = PackageJson.defaultConfig ): ContextOwner interface ContextOwner { @@ -17,4 +18,5 @@ interface ContextOwner { val filePathToInference: String val parsedFile: Node val settings: JsDynamicSettings + var packageJson: PackageJson } \ No newline at end of file From 5b07b012399f76b86d0e4b603849a91a3bd95717 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 13:25:15 +0300 Subject: [PATCH 06/74] [WIP] Redesigned exports managers + better file division in utbot-js module --- .../utbot/cli/js/JsGenerateTestsCommand.kt | 22 ++++++------- .../plugin/language/js/CoverageModeButtons.kt | 2 +- .../plugin/language/js/JsDialogProcessor.kt | 20 ++++-------- .../plugin/language/js/JsTestsModel.kt | 2 +- .../src/main/kotlin/api/JsTestGenerator.kt | 31 +++++++++++++------ .../main/kotlin/service/PackageJsonService.kt | 5 ++- .../src/main/kotlin/service/TernService.kt | 2 +- .../{ => coverage}/BasicCoverageService.kt | 8 +++-- .../service/{ => coverage}/CoverageMode.kt | 2 +- .../service/{ => coverage}/CoverageService.kt | 13 +++++--- .../{ => coverage}/CoverageServiceProvider.kt | 12 ++++--- .../{ => coverage}/FastCoverageService.kt | 10 +++--- .../main/kotlin/settings/JsDynamicSettings.kt | 2 +- .../src/main/kotlin/utils/ExportsProvider.kt | 20 ++++++++++++ utbot-js/src/main/kotlin/utils/ValueUtil.kt | 5 +-- .../kotlin/utils/{ => data}/CoverageData.kt | 2 +- .../kotlin/utils/{ => data}/MethodTypes.kt | 2 +- .../kotlin/utils/{ => data}/ResultData.kt | 2 +- 18 files changed, 99 insertions(+), 63 deletions(-) rename utbot-js/src/main/kotlin/service/{ => coverage}/BasicCoverageService.kt (93%) rename utbot-js/src/main/kotlin/service/{ => coverage}/CoverageMode.kt (65%) rename utbot-js/src/main/kotlin/service/{ => coverage}/CoverageService.kt (91%) rename utbot-js/src/main/kotlin/service/{ => coverage}/CoverageServiceProvider.kt (97%) rename utbot-js/src/main/kotlin/service/{ => coverage}/FastCoverageService.kt (92%) create mode 100644 utbot-js/src/main/kotlin/utils/ExportsProvider.kt rename utbot-js/src/main/kotlin/utils/{ => data}/CoverageData.kt (77%) rename utbot-js/src/main/kotlin/utils/{ => data}/MethodTypes.kt (88%) rename utbot-js/src/main/kotlin/utils/{ => data}/ResultData.kt (97%) diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt index 110c2e121d..b93a3c15b9 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt @@ -6,7 +6,7 @@ import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.choice import mu.KotlinLogging import org.utbot.cli.js.JsUtils.makeAbsolutePath -import service.CoverageMode +import service.coverage.CoverageMode import settings.JsDynamicSettings import settings.JsExportsSettings.endComment import settings.JsExportsSettings.startComment @@ -88,7 +88,7 @@ class JsGenerateTestsCommand : sourceFilePath = sourceFileAbsolutePath, parentClassName = targetClass, outputFilePath = outputAbsolutePath, - exportsManager = ::manageExports, + exportsManager = partialApplication(::manageExports, fileText), settings = JsDynamicSettings( pathToNode = pathToNode, pathToNYC = pathToNYC, @@ -118,25 +118,17 @@ class JsGenerateTestsCommand : } } - private fun manageExports(exports: List) { + private fun manageExports(fileText: String, exports: List, swappedText: (String) -> String) { val exportSection = exports.joinToString("\n") { "exports.$it = $it" } val file = File(sourceCodeFile) - val fileText = file.readText() when { fileText.contains(exportSection) -> {} fileText.contains(startComment) && !fileText.contains(exportSection) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> - val exportRegex = Regex("exports[.](.*) =") - val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } - val existingExportsSet = existingExports.map { rawLine -> - exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() - }.toSet() - val resultSet = existingExportsSet + exports.toSet() - val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } - val swappedText = fileText.replace(existingSection, "\n$resSection\n") - file.writeText(swappedText) + val newText = swappedText(existingSection) + file.writeText(newText) } } @@ -150,4 +142,8 @@ class JsGenerateTestsCommand : } } } + + private fun partialApplication(f: (A, B, C) -> Unit, a: A): (B, C) -> Unit { + return { b: B, c: C -> f(a, b, c) } + } } \ No newline at end of file diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt index a372deb8b7..14f62e85fe 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt @@ -1,6 +1,6 @@ package org.utbot.intellij.plugin.language.js -import service.CoverageMode +import service.coverage.CoverageMode import javax.swing.JToggleButton object CoverageModeButtons { 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..7b768d4baa 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 @@ -176,7 +176,7 @@ object JsDialogProcessor { if (name == dummyClassName) null else name }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), - exportsManager = partialApplication(JsDialogProcessor::manageExports, editor, project), + exportsManager = partialApplication(JsDialogProcessor::manageExports, editor, project, editor.document.text), settings = JsDynamicSettings( pathToNode = model.pathToNode, pathToNYC = model.pathToNYC, @@ -208,33 +208,25 @@ object JsDialogProcessor { }).queue() } - private fun partialApplication(f: (A, B, C) -> Unit, a: A, b: B): (C) -> Unit { - return { c: C -> f(a, b, c) } + private fun partialApplication(f: (A, B, C, D, E) -> Unit, a: A, b: B, c: C): (D, E) -> Unit { + return { d: D, e: E -> f(a, b, c, d, e) } } - private fun manageExports(editor: Editor, project: Project, exports: List) { + private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String) -> String) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { val exportSection = exports.joinToString("\n") { "exports.$it = $it" } - val fileText = editor.document.text when { fileText.contains(exportSection) -> {} fileText.contains(startComment) && !fileText.contains(exportSection) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> - val exportRegex = Regex("exports[.](.*) =") - val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } - val existingExportsSet = existingExports.map { rawLine -> - exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() - }.toSet() - val resultSet = existingExportsSet + exports.toSet() - val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } - val swappedText = fileText.replace(existingSection, "\n$resSection\n") + val newText = swappedText(existingSection) runWriteAction { with(editor.document) { unblockDocument(project, this) - setText(swappedText) + setText(newText) unblockDocument(project, this) } with(FileDocumentManager.getInstance()) { diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt index fb869d835b..8c8508953d 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt @@ -6,7 +6,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import org.utbot.framework.codegen.domain.TestFramework import org.utbot.intellij.plugin.models.BaseTestsModel -import service.CoverageMode +import service.coverage.CoverageMode import settings.JsTestGenerationSettings.defaultTimeout class JsTestsModel( diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index ac70d7d57b..777679406f 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -14,6 +14,8 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing +import java.io.File +import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -43,19 +45,19 @@ import parser.JsToplevelFunctionAstVisitor import service.CoverageMode import service.CoverageServiceProvider import service.InstrumentationService +import service.PackageJson import service.PackageJsonService import service.ServiceContext import service.TernService +import service.coverage.CoverageMode +import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold -import settings.JsTestGenerationSettings.fuzzingTimeout +import utils.ExportsProvider.getExportsRegex import utils.PathResolver -import utils.ResultData import utils.constructClass +import utils.data.ResultData import utils.toJsAny -import java.io.File -import java.util.concurrent.CancellationException -import org.utbot.fuzzing.utils.Trie private val logger = KotlinLogging.logger {} @@ -66,7 +68,7 @@ class JsTestGenerator( private val selectedMethods: List? = null, private var parentClassName: String? = null, private var outputFilePath: String?, - private val exportsManager: (List) -> Unit, + private val exportsManager: (List, (String) -> String) -> Unit, private val settings: JsDynamicSettings, private val isCancelled: () -> Boolean = { false } ) { @@ -138,7 +140,7 @@ class JsTestGenerator( val execId = classId.allMethods.find { it.name == funcNode.getAbstractFunctionName() } ?: throw IllegalStateException() - manageExports(classNode, funcNode, execId) + manageExports(classNode, funcNode, execId, context.packageJson) val executionResults = mutableListOf() try { runBlockingWithCancellationPredicate(isCancelled) { @@ -302,12 +304,23 @@ class JsTestGenerator( private fun manageExports( classNode: Node?, funcNode: Node, - execId: JsMethodId + execId: JsMethodId, + packageJson: PackageJson ) { val obligatoryExport = (classNode?.getClassName() ?: funcNode.getAbstractFunctionName()).toString() val collectedExports = collectExports(execId) exports += (collectedExports + obligatoryExport) - exportsManager(exports.toList()) + exportsManager(exports.toList()) { existingSection -> + val exportRegex = getExportsRegex(packageJson) + val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } + val existingExportsSet = existingExports.map { rawLine -> + exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() + }.toSet() + val resultSet = existingExportsSet + exports.toSet() + val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } + val swappedText = fileText.replace(existingSection, "\n$resSection\n") + swappedText + } } private fun makeMethodsToTest(): List { diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index 81acc21ab2..61480edb4e 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -2,6 +2,7 @@ package service import java.io.File import java.io.FilenameFilter +import org.json.JSONException import org.json.JSONObject data class PackageJson( @@ -32,7 +33,9 @@ class PackageJsonService(context: ServiceContext) : ContextOwner by context { private fun parseConfig(configFile: File): PackageJson { val configAsJson = JSONObject(configFile.readText()) return PackageJson( - isModule = (configAsJson.getString("type") == "module"), + isModule = try { + (configAsJson.getString("type") == "module") + } catch (e: JSONException) { false }, ) } } \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 6e8f0271fe..6326f99f98 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -12,7 +12,7 @@ import parser.JsParserUtils.getAbstractFunctionParams import parser.JsParserUtils.getClassName import parser.JsParserUtils.getConstructor import utils.JsCmdExec -import utils.MethodTypes +import utils.data.MethodTypes import utils.constructClass import java.io.File import java.util.Locale diff --git a/utbot-js/src/main/kotlin/service/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt similarity index 93% rename from utbot-js/src/main/kotlin/service/BasicCoverageService.kt rename to utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index 2d2c36148a..ccaa389ab1 100644 --- a/utbot-js/src/main/kotlin/service/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -1,12 +1,14 @@ -package service +package service.coverage +import java.io.File import mu.KotlinLogging import org.json.JSONObject import org.utbot.framework.plugin.api.TimeoutException -import settings.JsTestGenerationSettings.tempFileName +import service.ServiceContext +import settings.JsTestGenerationSettings import utils.JsCmdExec -import utils.ResultData import java.io.File +import utils.data.ResultData private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/CoverageMode.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt similarity index 65% rename from utbot-js/src/main/kotlin/service/CoverageMode.kt rename to utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt index a1f0c0d9cc..5fee242ac0 100644 --- a/utbot-js/src/main/kotlin/service/CoverageMode.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt @@ -1,4 +1,4 @@ -package service +package service.coverage enum class CoverageMode { FAST, diff --git a/utbot-js/src/main/kotlin/service/CoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt similarity index 91% rename from utbot-js/src/main/kotlin/service/CoverageService.kt rename to utbot-js/src/main/kotlin/service/coverage/CoverageService.kt index a5bfd0425b..aca8f8ae03 100644 --- a/utbot-js/src/main/kotlin/service/CoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt @@ -1,13 +1,15 @@ -package service +package service.coverage import java.io.File import java.util.Collections import org.json.JSONException import org.json.JSONObject +import service.ContextOwner +import service.ServiceContext import settings.JsTestGenerationSettings -import utils.CoverageData import utils.JsCmdExec -import utils.ResultData +import utils.data.CoverageData +import utils.data.ResultData abstract class CoverageService( context: ServiceContext, @@ -39,7 +41,10 @@ abstract class CoverageService( scriptText = baseCoverageScriptText ) JsCmdExec.runCommand( - cmd = arrayOf("\"${settings.pathToNode}\"", "\"$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js\""), + cmd = arrayOf( + "\"${settings.pathToNode}\"", + "\"$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js\"" + ), dir = projectPath, shouldWait = true, timeout = settings.timeout, diff --git a/utbot-js/src/main/kotlin/service/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt similarity index 97% rename from utbot-js/src/main/kotlin/service/CoverageServiceProvider.kt rename to utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index aaa401025c..24d368aacc 100644 --- a/utbot-js/src/main/kotlin/service/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -1,19 +1,23 @@ -package service +package service.coverage import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription +import java.util.regex.Pattern import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic import org.utbot.fuzzer.FuzzedValue +import service.ContextOwner +import service.InstrumentationService +import service.ServiceContext import settings.JsTestGenerationSettings -import settings.JsTestGenerationSettings.tempFileName -import utils.CoverageData -import utils.ResultData +import utils.data.CoverageData +import utils.data.ResultData import java.util.regex.Pattern + // TODO: Add "error" field in result json to not collide with "result" field upon error. class CoverageServiceProvider( private val context: ServiceContext, diff --git a/utbot-js/src/main/kotlin/service/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt similarity index 92% rename from utbot-js/src/main/kotlin/service/FastCoverageService.kt rename to utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index 02a0908f59..111e3fcb63 100644 --- a/utbot-js/src/main/kotlin/service/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -1,12 +1,12 @@ -package service +package service.coverage +import java.io.File import mu.KotlinLogging import org.json.JSONObject -import settings.JsTestGenerationSettings.fuzzingThreshold -import settings.JsTestGenerationSettings.tempFileName +import service.ServiceContext +import settings.JsTestGenerationSettings import utils.JsCmdExec -import utils.ResultData -import java.io.File +import utils.data.ResultData private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt b/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt index 81d1ac02a0..c985b2454f 100644 --- a/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt @@ -1,6 +1,6 @@ package settings -import service.CoverageMode +import service.coverage.CoverageMode data class JsDynamicSettings( val pathToNode: String = "node", diff --git a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt new file mode 100644 index 0000000000..a100296eeb --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt @@ -0,0 +1,20 @@ +package utils + +import service.PackageJson + +object ExportsProvider { + + fun getExportsRegex(packageJson: PackageJson): Regex = with(packageJson) { + when (isModule) { + true -> Regex("") + false -> Regex("exports[.](.*) =") + } + } + + fun getExportsDelimiter(packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> "," + false -> "\n" + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/utils/ValueUtil.kt b/utbot-js/src/main/kotlin/utils/ValueUtil.kt index 2349a7a674..50862cd4b4 100644 --- a/utbot-js/src/main/kotlin/utils/ValueUtil.kt +++ b/utbot-js/src/main/kotlin/utils/ValueUtil.kt @@ -1,5 +1,7 @@ package utils +import org.json.JSONException +import org.json.JSONObject import framework.api.js.JsClassId import framework.api.js.util.jsBooleanClassId import framework.api.js.util.jsDoubleClassId @@ -7,8 +9,7 @@ import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsNumberClassId import framework.api.js.util.jsStringClassId import framework.api.js.util.jsUndefinedClassId -import org.json.JSONException -import org.json.JSONObject +import utils.data.ResultData fun ResultData.toJsAny(returnType: JsClassId = jsUndefinedClassId): Pair { this.buildUniqueValue()?.let { return it } diff --git a/utbot-js/src/main/kotlin/utils/CoverageData.kt b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt similarity index 77% rename from utbot-js/src/main/kotlin/utils/CoverageData.kt rename to utbot-js/src/main/kotlin/utils/data/CoverageData.kt index 6dc2538f73..2237243745 100644 --- a/utbot-js/src/main/kotlin/utils/CoverageData.kt +++ b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt @@ -1,4 +1,4 @@ -package utils +package utils.data data class CoverageData( val additionalCoverage: Set diff --git a/utbot-js/src/main/kotlin/utils/MethodTypes.kt b/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt similarity index 88% rename from utbot-js/src/main/kotlin/utils/MethodTypes.kt rename to utbot-js/src/main/kotlin/utils/data/MethodTypes.kt index 342acdf508..cea1c403ee 100644 --- a/utbot-js/src/main/kotlin/utils/MethodTypes.kt +++ b/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt @@ -1,4 +1,4 @@ -package utils +package utils.data import framework.api.js.JsClassId diff --git a/utbot-js/src/main/kotlin/utils/ResultData.kt b/utbot-js/src/main/kotlin/utils/data/ResultData.kt similarity index 97% rename from utbot-js/src/main/kotlin/utils/ResultData.kt rename to utbot-js/src/main/kotlin/utils/data/ResultData.kt index fe4a274175..ed8e6ef0e1 100644 --- a/utbot-js/src/main/kotlin/utils/ResultData.kt +++ b/utbot-js/src/main/kotlin/utils/data/ResultData.kt @@ -1,4 +1,4 @@ -package utils +package utils.data /** * Represents results after running function with arguments using Node.js From 7cc2b5df0c6907a0e35282f2edbf7e4c3171d420 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 17:38:04 +0300 Subject: [PATCH 07/74] Implemented JavaScript exports manager based on package.json --- .../utbot/cli/js/JsGenerateTestsCommand.kt | 12 ++++---- .../plugin/language/js/JsDialogProcessor.kt | 13 ++++---- .../src/main/kotlin/api/JsTestGenerator.kt | 30 +++++++++++++------ .../src/main/kotlin/utils/ExportsProvider.kt | 23 +++++++++++++- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt index b93a3c15b9..3eec14facd 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt @@ -118,13 +118,11 @@ class JsGenerateTestsCommand : } } - private fun manageExports(fileText: String, exports: List, swappedText: (String) -> String) { - val exportSection = exports.joinToString("\n") { "exports.$it = $it" } + private fun manageExports(fileText: String, exports: List, swappedText: (String?) -> String) { val file = File(sourceCodeFile) when { - fileText.contains(exportSection) -> {} - fileText.contains(startComment) && !fileText.contains(exportSection) -> { + fileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> val newText = swappedText(existingSection) @@ -134,9 +132,9 @@ class JsGenerateTestsCommand : else -> { val line = buildString { - append("\n$startComment\n") - append(exportSection) - append("\n$endComment") + append("\n$startComment") + append(swappedText(null)) + append(endComment) } file.appendText(line) } 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 7b768d4baa..82041d5ab8 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 @@ -212,14 +212,11 @@ object JsDialogProcessor { return { d: D, e: E -> f(a, b, c, d, e) } } - private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String) -> String) { + private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { - val exportSection = exports.joinToString("\n") { "exports.$it = $it" } when { - fileText.contains(exportSection) -> {} - - fileText.contains(startComment) && !fileText.contains(exportSection) -> { + fileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> val newText = swappedText(existingSection) @@ -238,9 +235,9 @@ object JsDialogProcessor { else -> { val line = buildString { - append("\n$startComment\n") - append(exportSection) - append("\n$endComment") + append("\n$startComment") + append(swappedText(null)) + append(endComment) } runWriteAction { with(editor.document) { diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 777679406f..4203c3218c 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -53,6 +53,10 @@ import service.coverage.CoverageMode import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold +import utils.ExportsProvider.getExportsDelimiter +import utils.ExportsProvider.getExportsFrame +import utils.ExportsProvider.getExportsPostfix +import utils.ExportsProvider.getExportsPrefix import utils.ExportsProvider.getExportsRegex import utils.PathResolver import utils.constructClass @@ -68,7 +72,7 @@ class JsTestGenerator( private val selectedMethods: List? = null, private var parentClassName: String? = null, private var outputFilePath: String?, - private val exportsManager: (List, (String) -> String) -> Unit, + private val exportsManager: (List, (String?) -> String) -> Unit, private val settings: JsDynamicSettings, private val isCancelled: () -> Boolean = { false } ) { @@ -311,15 +315,23 @@ class JsTestGenerator( val collectedExports = collectExports(execId) exports += (collectedExports + obligatoryExport) exportsManager(exports.toList()) { existingSection -> - val exportRegex = getExportsRegex(packageJson) - val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } - val existingExportsSet = existingExports.map { rawLine -> - exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() - }.toSet() + val existingExportsSet = existingSection?.let { section -> + val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)).substringBeforeLast(getExportsPostfix(packageJson)) + val exportRegex = getExportsRegex(packageJson) + val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)).filter { it.contains(exportRegex) && it.isNotBlank() } + existingExports.map { rawLine -> + exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() + }.toSet() + } ?: emptySet() val resultSet = existingExportsSet + exports.toSet() - val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } - val swappedText = fileText.replace(existingSection, "\n$resSection\n") - swappedText + val resSection = resultSet.joinToString( + separator = getExportsDelimiter(packageJson), + prefix = getExportsPrefix(packageJson), + postfix = getExportsPostfix(packageJson), + ) { + getExportsFrame(it, packageJson) + } + existingSection?.let { fileText.replace(existingSection, resSection) } ?: resSection } } diff --git a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt index a100296eeb..49fea8da23 100644 --- a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt +++ b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt @@ -6,7 +6,7 @@ object ExportsProvider { fun getExportsRegex(packageJson: PackageJson): Regex = with(packageJson) { when (isModule) { - true -> Regex("") + true -> Regex("(.*)") false -> Regex("exports[.](.*) =") } } @@ -17,4 +17,25 @@ object ExportsProvider { false -> "\n" } } + + fun getExportsFrame(exportString: String, packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> exportString + false -> "exports.$exportString = $exportString" + } + } + + fun getExportsPrefix(packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> "\nexport {" + false -> "\n" + } + } + + fun getExportsPostfix(packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> "}\n" + false -> "\n" + } + } } \ No newline at end of file From 1614f4cf82bb8951ba76249ae0ac522c3d40087f Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Tue, 14 Feb 2023 16:26:59 +0300 Subject: [PATCH 08/74] Fix phantom !0 in tern --- utbot-js/src/main/kotlin/service/TernService.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 6326f99f98..e403cba68c 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -12,8 +12,8 @@ import parser.JsParserUtils.getAbstractFunctionParams import parser.JsParserUtils.getClassName import parser.JsParserUtils.getConstructor import utils.JsCmdExec -import utils.data.MethodTypes import utils.constructClass +import utils.data.MethodTypes import java.io.File import java.util.Locale @@ -80,7 +80,6 @@ test("$filePathToInference") private fun installDeps(path: String) { JsCmdExec.runCommand( dir = path, - shouldWait = true, cmd = arrayOf("\"${settings.pathToNPM}\"", "i", "tern", "-l"), ) } @@ -92,14 +91,15 @@ test("$filePathToInference") } private fun runTypeInferencer() { - val (inputText, _) = JsCmdExec.runCommand( + val (reader, _) = JsCmdExec.runCommand( dir = "$projectPath/$utbotDir/", shouldWait = true, timeout = 20, cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""), ) + val text = reader.readText().replaceAfterLast("}", "") json = try { - JSONObject(inputText.replaceAfterLast("}", "")) + JSONObject(text) } catch (_: Throwable) { JSONObject() } @@ -160,7 +160,7 @@ test("$filePathToInference") } val methodJson = scope.getJSONObject(funcNode.getAbstractFunctionName()) val typesString = methodJson.getString("!type") - .filterNot { setOf(' ', '+', '!').contains(it) } + .filterNot { setOf(' ', '+').contains(it) } val parametersList = lazy { extractParameters(typesString) } val returnType = lazy { extractReturnType(typesString) } @@ -173,11 +173,9 @@ test("$filePathToInference") } } - //TODO MINOR: move to appropriate place (JsIdUtil or JsClassId constructor) private fun makeClassId(name: String): JsClassId { val classId = when { - // TODO SEVERE: I don't know why Tern sometimes says that type is "0" - name == "?" || name.toIntOrNull() != null -> jsUndefinedClassId + name == "?" || name.toIntOrNull() != null || name.contains('!') -> jsUndefinedClassId Regex("\\[(.*)]").matches(name) -> { val arrType = Regex("\\[(.*)]").find(name)?.groups?.get(1)?.value ?: throw IllegalStateException() JsClassId( From 2086b7a7da61e31929ba3dba9b397cfcb62bfabc Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 13 Jan 2023 12:20:43 +0300 Subject: [PATCH 09/74] Switch to IU for this branch --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5525b6883f..a2fa4a2905 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ kotlin.code.style=official # IU, IC, PC, PY # IC for AndroidStudio -ideType=IC +ideType=IU ideVersion=222.4167.29 pythonIde=IC,IU,PC,PY From 079af6a0b41a3bc400a8403049c4f1ff8265027a Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 17 Feb 2023 09:53:23 +0300 Subject: [PATCH 10/74] After VCS fix --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 4 ++-- utbot-js/src/main/kotlin/service/TernService.kt | 2 +- .../src/main/kotlin/service/coverage/BasicCoverageService.kt | 2 -- .../main/kotlin/service/coverage/CoverageServiceProvider.kt | 1 - .../src/main/kotlin/service/coverage/FastCoverageService.kt | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 4203c3218c..fbfd8d3af1 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -32,6 +32,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.JsAstScrapper import parser.JsFuzzerAstVisitor import parser.JsParserUtils @@ -42,8 +43,6 @@ import parser.JsParserUtils.getClassName import parser.JsParserUtils.getParamName import parser.JsParserUtils.runParser import parser.JsToplevelFunctionAstVisitor -import service.CoverageMode -import service.CoverageServiceProvider import service.InstrumentationService import service.PackageJson import service.PackageJsonService @@ -53,6 +52,7 @@ import service.coverage.CoverageMode import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold +import settings.JsTestGenerationSettings.fuzzingTimeout import utils.ExportsProvider.getExportsDelimiter import utils.ExportsProvider.getExportsFrame import utils.ExportsProvider.getExportsPostfix diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index e403cba68c..d393f67dcd 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -109,7 +109,7 @@ test("$filePathToInference") return try { val classJson = json.getJSONObject(classNode.getClassName()) val constructorFunc = classJson.getString("!type") - .filterNot { setOf(' ', '+', '!').contains(it) } + .filterNot { setOf(' ', '+').contains(it) } extractParameters(constructorFunc) } catch (e: JSONException) { classNode.getConstructor()?.getAbstractFunctionParams()?.map { jsUndefinedClassId } ?: emptyList() diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index ccaa389ab1..178f05f48d 100644 --- a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -5,9 +5,7 @@ import mu.KotlinLogging import org.json.JSONObject import org.utbot.framework.plugin.api.TimeoutException import service.ServiceContext -import settings.JsTestGenerationSettings import utils.JsCmdExec -import java.io.File import utils.data.ResultData private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 24d368aacc..20719e9e57 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -15,7 +15,6 @@ import service.ServiceContext import settings.JsTestGenerationSettings import utils.data.CoverageData import utils.data.ResultData -import java.util.regex.Pattern // TODO: Add "error" field in result json to not collide with "result" field upon error. diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index 111e3fcb63..9f807e9982 100644 --- a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -4,7 +4,6 @@ import java.io.File import mu.KotlinLogging import org.json.JSONObject import service.ServiceContext -import settings.JsTestGenerationSettings import utils.JsCmdExec import utils.data.ResultData From 32f0fed96e06c44492bbf2f53d42d49e7d80b78c Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:15:48 +0300 Subject: [PATCH 11/74] Fix after merge --- utbot-js/src/main/kotlin/service/TernService.kt | 5 ++--- .../main/kotlin/service/coverage/BasicCoverageService.kt | 3 ++- .../kotlin/service/coverage/CoverageServiceProvider.kt | 9 +++++---- .../main/kotlin/service/coverage/FastCoverageService.kt | 4 +++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index d393f67dcd..a376280d25 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -91,15 +91,14 @@ test("$filePathToInference") } private fun runTypeInferencer() { - val (reader, _) = JsCmdExec.runCommand( + val (inputText, _) = JsCmdExec.runCommand( dir = "$projectPath/$utbotDir/", shouldWait = true, timeout = 20, cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""), ) - val text = reader.readText().replaceAfterLast("}", "") json = try { - JSONObject(text) + JSONObject(inputText.replaceAfterLast("}", "")) } catch (_: Throwable) { JSONObject() } diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index 6a4c528edc..c777b799ce 100644 --- a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -1,12 +1,13 @@ package service.coverage -import java.io.File import mu.KotlinLogging import org.json.JSONObject import org.utbot.framework.plugin.api.TimeoutException import service.ServiceContext +import settings.JsTestGenerationSettings.tempFileName import utils.JsCmdExec import utils.data.ResultData +import java.io.File private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 20719e9e57..99da262768 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -4,7 +4,6 @@ import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription -import java.util.regex.Pattern import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic @@ -13,8 +12,10 @@ import service.ContextOwner import service.InstrumentationService import service.ServiceContext import settings.JsTestGenerationSettings +import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData +import java.util.regex.Pattern // TODO: Add "error" field in result json to not collide with "result" field upon error. @@ -173,11 +174,11 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) method: JsMethodId, containingClass: String? ): String { - val actualParams = description.thisInstance?.let{ fuzzedValue.drop(1) } ?: fuzzedValue + val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue val initClass = containingClass?.let { if (!method.isStatic) { - description.thisInstance?.let { fuzzedValue[0].model.toCallString() } ?: - "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" + description.thisInstance?.let { fuzzedValue[0].model.toCallString() } + ?: "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" } else "${JsTestGenerationSettings.fileUnderTestAliases}.$it" } ?: JsTestGenerationSettings.fileUnderTestAliases var callString = "$initClass.${method.name}" diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index 517676d108..94bb56b8a6 100644 --- a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -1,11 +1,13 @@ package service.coverage -import java.io.File import mu.KotlinLogging import org.json.JSONObject import service.ServiceContext +import settings.JsTestGenerationSettings.fuzzingThreshold +import settings.JsTestGenerationSettings.tempFileName import utils.JsCmdExec import utils.data.ResultData +import java.io.File private val logger = KotlinLogging.logger {} From 49034ad8a13cc0a0b29916aa52794f2c35256ca1 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:28:09 +0300 Subject: [PATCH 12/74] Fix text annotation for generated tests --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index fbfd8d3af1..7bce83391b 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -14,8 +14,6 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing -import java.io.File -import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -62,6 +60,8 @@ import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny +import java.io.File +import java.util.concurrent.CancellationException private val logger = KotlinLogging.logger {} @@ -261,7 +261,7 @@ class JsTestGenerator( getUtModelResult( execId = execId, resultData = resultData, - params + jsDescription.thisInstance?.let { params.drop(1) } ?: params ) if (result is UtTimeoutException) { emit(JsTimeoutExecution(result)) @@ -316,9 +316,11 @@ class JsTestGenerator( exports += (collectedExports + obligatoryExport) exportsManager(exports.toList()) { existingSection -> val existingExportsSet = existingSection?.let { section -> - val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)).substringBeforeLast(getExportsPostfix(packageJson)) + val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)) + .substringBeforeLast(getExportsPostfix(packageJson)) val exportRegex = getExportsRegex(packageJson) - val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)).filter { it.contains(exportRegex) && it.isNotBlank() } + val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)) + .filter { it.contains(exportRegex) && it.isNotBlank() } existingExports.map { rawLine -> exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() }.toSet() From e8c0140caaa52fc8985b7d17e340595615d912b2 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:56:16 +0300 Subject: [PATCH 13/74] Implement support for multiple files for tern --- .../src/main/kotlin/api/JsTestGenerator.kt | 2 +- .../kotlin/service/InstrumentationService.kt | 25 +++++++++++-------- .../main/kotlin/service/PackageJsonService.kt | 12 +++++---- .../src/main/kotlin/service/ServiceContext.kt | 8 +++--- .../src/main/kotlin/service/TernService.kt | 3 ++- .../coverage/CoverageServiceProvider.kt | 2 +- .../main/kotlin/utils/JsClassConstructors.kt | 4 +-- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 7bce83391b..87151165b9 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -104,7 +104,7 @@ class JsTestGenerator( val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, - filePathToInference = sourceFilePath, + filePathToInference = listOf(sourceFilePath), parsedFile = parsedFile, settings = settings, ) diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt index f613c1f635..095dec1130 100644 --- a/utbot-js/src/main/kotlin/service/InstrumentationService.kt +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -3,8 +3,6 @@ package service import com.google.javascript.jscomp.CodePrinter import com.google.javascript.jscomp.NodeUtil import com.google.javascript.rhino.Node -import java.io.File -import java.nio.file.Paths import org.apache.commons.io.FileUtils import parser.JsFunctionAstVisitor import parser.JsParserUtils.getAnyValue @@ -13,13 +11,16 @@ import parser.JsParserUtils.isRequireImport import parser.JsParserUtils.runParser import utils.JsCmdExec import utils.PathResolver.getRelativePath +import java.io.File +import java.nio.file.Paths import kotlin.io.path.pathString import kotlin.math.roundToInt -class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair): ContextOwner by context { +class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair) : + ContextOwner by context { private val destinationFolderPath = "${projectPath}/${utbotDir}/instr" - private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.substringAfterLast("/")}" + private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.first().substringAfterLast("/")}" private lateinit var parsedInstrFile: Node lateinit var covFunName: String @@ -122,7 +123,7 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset covFuncNode.findAndIterateOver("fnMap") { currKey -> val declLocation = currKey!!.getObjectLocation("decl") if (funcDeclOffset == declLocation.start) { - result = currKey.getObjectLocation("loc") + result = currKey.getObjectLocation("loc") return@findAndIterateOver } } @@ -130,11 +131,11 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset } fun instrument() { - val fileName = filePathToInference.substringAfterLast("/") + val fileName = filePathToInference.first().substringAfterLast("/") JsCmdExec.runCommand( cmd = arrayOf(settings.pathToNYC, "instrument", fileName, destinationFolderPath), - dir = filePathToInference.substringBeforeLast("/"), + dir = filePathToInference.first().substringBeforeLast("/"), shouldWait = true, timeout = settings.timeout, ) @@ -159,10 +160,12 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset NodeUtil.visitPreOrder(parsedInstrFile) { node -> if (node.isRequireImport()) { val currString = node.getRequireImportText() - val relPath = Paths.get(getRelativePath( - "${projectPath}/${utbotDir}/instr", - File(filePathToInference).parent - )).resolve(currString).pathString.replace("\\", "/") + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") node.firstChild!!.next!!.string = relPath } } diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index 61480edb4e..354aa9869a 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -1,9 +1,9 @@ package service -import java.io.File -import java.io.FilenameFilter import org.json.JSONException import org.json.JSONObject +import java.io.File +import java.io.FilenameFilter data class PackageJson( val isModule: Boolean @@ -17,7 +17,7 @@ data class PackageJson( class PackageJsonService(context: ServiceContext) : ContextOwner by context { fun findClosestConfig(): PackageJson { - var currDir = File(filePathToInference.substringBeforeLast("/")) + var currDir = File(filePathToInference.first().substringBeforeLast("/")) do { val matchingFiles: Array = currDir.listFiles( FilenameFilter { _, name -> @@ -35,7 +35,9 @@ class PackageJsonService(context: ServiceContext) : ContextOwner by context { return PackageJson( isModule = try { (configAsJson.getString("type") == "module") - } catch (e: JSONException) { false }, + } catch (e: JSONException) { + false + }, ) } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/ServiceContext.kt b/utbot-js/src/main/kotlin/service/ServiceContext.kt index 36fb0a0376..d30ccf1fea 100644 --- a/utbot-js/src/main/kotlin/service/ServiceContext.kt +++ b/utbot-js/src/main/kotlin/service/ServiceContext.kt @@ -6,17 +6,17 @@ import settings.JsDynamicSettings class ServiceContext( override val utbotDir: String, override val projectPath: String, - override val filePathToInference: String, + override val filePathToInference: List, override val parsedFile: Node, override val settings: JsDynamicSettings, override var packageJson: PackageJson = PackageJson.defaultConfig -): ContextOwner +) : ContextOwner interface ContextOwner { val utbotDir: String val projectPath: String - val filePathToInference: String + val filePathToInference: List val parsedFile: Node val settings: JsDynamicSettings var packageJson: PackageJson -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index a376280d25..a17e83e473 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -64,7 +64,7 @@ function test(options) { runTest(options); } -test("$filePathToInference") +test("${filePathToInference.joinToString(separator = " ")}") """ init { @@ -80,6 +80,7 @@ test("$filePathToInference") private fun installDeps(path: String) { JsCmdExec.runCommand( dir = path, + shouldWait = true, cmd = arrayOf("\"${settings.pathToNPM}\"", "i", "tern", "-l"), ) } diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 99da262768..df4f3b9e45 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -26,7 +26,7 @@ class CoverageServiceProvider( private val description: JsMethodDescription ) : ContextOwner by context { - private val importFileUnderTest = "instr/${filePathToInference.substringAfterLast("/")}" + private val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" private val imports = "const ${JsTestGenerationSettings.fileUnderTestAliases} = require(\"./$importFileUnderTest\")\n" + diff --git a/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt b/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt index bace7de39b..54149f9202 100644 --- a/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt +++ b/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt @@ -30,7 +30,7 @@ fun JsClassId.constructClass( methods = methods, constructor = constructor, classPackagePath = ternService.projectPath, - classFilePath = ternService.filePathToInference, + classFilePath = ternService.filePathToInference.first(), ) methods.forEach { it.classId = newClassId @@ -73,4 +73,4 @@ private fun JsClassId.constructMethods( }.asSequence() return methods } -} \ No newline at end of file +} From 02f3dac1e86bd844b9e2527799b5d5f00819f6ce Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Sat, 18 Feb 2023 11:52:35 +0300 Subject: [PATCH 14/74] [WIP] Module imports for JavaScript --- .../src/main/kotlin/api/JsTestGenerator.kt | 6 +- .../src/main/kotlin/parser/JsAstScrapper.kt | 67 ++++++++++++++++--- .../src/main/kotlin/parser/JsParserUtils.kt | 41 ++++++++++++ 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 87151165b9..7e1032a508 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -376,8 +376,8 @@ class JsTestGenerator( } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { - return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName) } - ?: astScrapper.findFunction(focusedMethodName) + return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName, parsedFile) } + ?: astScrapper.findFunction(focusedMethodName, parsedFile) ?: throw IllegalStateException( "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" ) @@ -391,7 +391,7 @@ class JsTestGenerator( } private fun getClassMethods(className: String): List { - val classNode = astScrapper.findClass(className) + val classNode = astScrapper.findClass(className, parsedFile) return classNode?.getClassMethods() ?: throw IllegalStateException("Can't extract methods of class $className") } } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index dec6e73f1a..ce3f6d80f9 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -6,12 +6,19 @@ import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.io.File import java.nio.file.Paths +import mu.KotlinLogging import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getImportSpecAliases +import parser.JsParserUtils.getImportSpecName +import parser.JsParserUtils.getModuleImportSpecsAsList +import parser.JsParserUtils.getModuleImportText import parser.JsParserUtils.getRequireImportText import parser.JsParserUtils.isRequireImport import kotlin.io.path.pathString +private val logger = KotlinLogging.logger {} + class JsAstScrapper( private val parsedFile: Node, private val basePath: String, @@ -20,26 +27,26 @@ class JsAstScrapper( // Used not to parse the same file multiple times. private val _parsedFilesCache = mutableMapOf() - fun findFunction(key: String): Node? { + fun findFunction(key: String, file: Node): Node? { if (importsMap[key]?.isFunction == true) return importsMap[key] val functionVisitor = JsFunctionAstVisitor(key, null) - functionVisitor.accept(parsedFile) + functionVisitor.accept(file) return try { functionVisitor.targetFunctionNode - } catch(e: Exception) { null } + } catch (e: Exception) { null } } - fun findClass(key: String): Node? { + fun findClass(key: String, file: Node): Node? { if (importsMap[key]?.isClass == true) return importsMap[key] val classVisitor = JsClassAstVisitor(key) - classVisitor.accept(parsedFile) + classVisitor.accept(file) return try { classVisitor.targetClassNode } catch (e: Exception) { null } } - fun findMethod(classKey: String, methodKey: String): Node? { - val classNode = findClass(classKey) + fun findMethod(classKey: String, methodKey: String, file: Node): Node? { + val classNode = findClass(classKey, file) return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } } @@ -51,18 +58,52 @@ class JsAstScrapper( } } + private fun File.parseIfNecessary(): Node = + _parsedFilesCache.getOrPut(this.path) { + Compiler().parse(SourceFile.fromCode(this.path, readText())) + } + private fun Node.importedNodes(): Map { return when { this.isRequireImport() -> mapOf( this.parent!!.string to (makePathFromImport(this.getRequireImportText())?.let { - File(it).findEntityInFile() + File(it).parseIfNecessary().findEntityInFile(null) // Workaround for std imports. } ?: this.firstChild!!.next!!) ) + this.isImport -> this.processModuleImport() else -> emptyMap() } } + private fun Node.processModuleImport(): Map { + try { + val pathToFile = makePathFromImport(this.getModuleImportText()) ?: return emptyMap() + val pFile = File(pathToFile).parseIfNecessary() + return when { + NodeUtil.findPreorder(this, { it.isImportSpecs }, { true }) != null -> { + this.getModuleImportSpecsAsList().associate { spec -> + val realName = spec.getImportSpecName() + val aliases = spec.getImportSpecAliases() + aliases to pFile.findEntityInFile(realName) + } + } + NodeUtil.findPreorder(this, { it.isImportStar }, { true }) != null -> { + val aliases = this.getImportSpecAliases() + mapOf(aliases to pFile) + } + // For example: import foo from "bar" + else -> { + val realName = this.getImportSpecName() + mapOf(realName to pFile.findEntityInFile(realName)) + } + } + } catch (e: Exception) { + logger.error { e.toString() } + return emptyMap() + } + } + private fun makePathFromImport(importText: String): String? { val relPath = importText + if (importText.endsWith(".js")) "" else ".js" // If import text doesn't contain "/", then it is NodeJS stdlib import. @@ -70,8 +111,12 @@ class JsAstScrapper( return Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString } - private fun File.findEntityInFile(): Node { - return Compiler().parse(SourceFile.fromCode("jsFile", readText())) + private fun Node.findEntityInFile(key: String?): Node { + return key?.let { k -> + findClass(k, this) + ?: findFunction(k, this) + ?: throw ClassNotFoundException("Could not locate entity $k in ${this.sourceFileName}") + } ?: this } private class Visitor: IAstVisitor { @@ -87,4 +132,4 @@ class JsAstScrapper( } } } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 495fb7303c..e3c1a97340 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -1,12 +1,14 @@ package parser import com.google.javascript.jscomp.Compiler +import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.lang.IllegalStateException import org.utbot.fuzzer.FuzzedContext import parser.JsParserUtils.getMethodName +// TODO: make methods more safe by checking the Node method is called on. // Used for .children() calls. @Suppress("DEPRECATION") object JsParserUtils { @@ -160,4 +162,43 @@ object JsParserUtils { * Returns path to imported file as [String]. */ fun Node.getRequireImportText(): String = this.firstChild!!.next!!.string + + /** + * Called upon "import" JavaScript import. + * + * Returns path to imported file as [String]. + */ + fun Node.getModuleImportText(): String = this.firstChild!!.next!!.next!!.string + + /** + * Called upon "import" JavaScript import. + * + * Returns imported objects as [List]. + */ + fun Node.getModuleImportSpecsAsList(): List { + val importSpecsNode = NodeUtil.findPreorder(this, {it.isImportSpecs}, {true}) + ?: throw UnsupportedOperationException("Module import doesn't contain \"import_specs\" token as an AST child") + var currNode: Node? = importSpecsNode.firstChild!! + val importSpecsList = mutableListOf() + do { + importSpecsList += currNode!! + currNode = currNode?.next + } while (currNode?.isImportSpec == true) + return importSpecsList + } + + /** + * Called upon IMPORT_SPEC Node. + * + * Returns name of imported object as [String]. + */ + fun Node.getImportSpecName(): String = this.firstChild!!.string + + /** + * Called upon IMPORT_SPEC Node. + * + * Returns import alias as [String]. + */ + fun Node.getImportSpecAliases(): String = this.firstChild!!.next!!.string + } From bd8d9194ffd3a248f8859cc6dd30d054fe5198dd Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:16:58 +0300 Subject: [PATCH 15/74] Implemented import/export providers (except generated test files) --- .../src/main/kotlin/api/JsTestGenerator.kt | 29 ++++++------- .../src/main/kotlin/parser/JsAstScrapper.kt | 4 ++ .../providers/exports/IExportsProvider.kt | 25 +++++++++++ .../exports/ModuleExportsProvider.kt | 16 ++++++++ .../exports/RequireExportsProvider.kt | 16 ++++++++ .../providers/imports/IImportsProvider.kt | 18 ++++++++ .../imports/ModuleImportsProvider.kt | 22 ++++++++++ .../imports/RequireImportsProvider.kt | 23 +++++++++++ .../kotlin/service/InstrumentationService.kt | 35 +++++++++++----- .../src/main/kotlin/service/TernService.kt | 20 ++++----- .../coverage/CoverageServiceProvider.kt | 9 +--- .../src/main/kotlin/utils/ExportsProvider.kt | 41 ------------------- 12 files changed, 171 insertions(+), 87 deletions(-) create mode 100644 utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt delete mode 100644 utbot-js/src/main/kotlin/utils/ExportsProvider.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 7e1032a508..92ff042243 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -14,6 +14,8 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing +import java.io.File +import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -41,6 +43,7 @@ import parser.JsParserUtils.getClassName import parser.JsParserUtils.getParamName import parser.JsParserUtils.runParser import parser.JsToplevelFunctionAstVisitor +import providers.exports.IExportsProvider import service.InstrumentationService import service.PackageJson import service.PackageJsonService @@ -51,17 +54,10 @@ import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold import settings.JsTestGenerationSettings.fuzzingTimeout -import utils.ExportsProvider.getExportsDelimiter -import utils.ExportsProvider.getExportsFrame -import utils.ExportsProvider.getExportsPostfix -import utils.ExportsProvider.getExportsPrefix -import utils.ExportsProvider.getExportsRegex import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny -import java.io.File -import java.util.concurrent.CancellationException private val logger = KotlinLogging.logger {} @@ -104,7 +100,7 @@ class JsTestGenerator( val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, - filePathToInference = listOf(sourceFilePath), + filePathToInference = astScrapper.filesToInfer, parsedFile = parsedFile, settings = settings, ) @@ -313,13 +309,14 @@ class JsTestGenerator( ) { val obligatoryExport = (classNode?.getClassName() ?: funcNode.getAbstractFunctionName()).toString() val collectedExports = collectExports(execId) + val exportsProvider = IExportsProvider.providerByPackageJson(packageJson) exports += (collectedExports + obligatoryExport) exportsManager(exports.toList()) { existingSection -> val existingExportsSet = existingSection?.let { section -> - val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)) - .substringBeforeLast(getExportsPostfix(packageJson)) - val exportRegex = getExportsRegex(packageJson) - val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)) + val trimmedSection = section.substringAfter(exportsProvider.exportsPrefix) + .substringBeforeLast(exportsProvider.exportsPostfix) + val exportRegex = exportsProvider.exportsRegex + val existingExports = trimmedSection.split(exportsProvider.exportsDelimiter) .filter { it.contains(exportRegex) && it.isNotBlank() } existingExports.map { rawLine -> exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() @@ -327,11 +324,11 @@ class JsTestGenerator( } ?: emptySet() val resultSet = existingExportsSet + exports.toSet() val resSection = resultSet.joinToString( - separator = getExportsDelimiter(packageJson), - prefix = getExportsPrefix(packageJson), - postfix = getExportsPostfix(packageJson), + separator = exportsProvider.exportsDelimiter, + prefix = exportsProvider.exportsPrefix, + postfix = exportsProvider.exportsPostfix, ) { - getExportsFrame(it, packageJson) + exportsProvider.getExportsFrame(it) } existingSection?.let { fileText.replace(existingSection, resSection) } ?: resSection } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index ce3f6d80f9..8699b73e3e 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -26,6 +26,9 @@ class JsAstScrapper( // Used not to parse the same file multiple times. private val _parsedFilesCache = mutableMapOf() + private val _filesToInfer: MutableList = mutableListOf(basePath) + val filesToInfer: List + get() = _filesToInfer.toList() fun findFunction(key: String, file: Node): Node? { if (importsMap[key]?.isFunction == true) return importsMap[key] @@ -60,6 +63,7 @@ class JsAstScrapper( private fun File.parseIfNecessary(): Node = _parsedFilesCache.getOrPut(this.path) { + _filesToInfer += this.path.replace("\\", "/") Compiler().parse(SourceFile.fromCode(this.path, readText())) } diff --git a/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt new file mode 100644 index 0000000000..895b0a8cea --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt @@ -0,0 +1,25 @@ +package providers.exports + +import service.PackageJson + +interface IExportsProvider { + + val exportsRegex: Regex + + val exportsDelimiter: String + + fun getExportsFrame(exportString: String): String + + val exportsPrefix: String + + val exportsPostfix: String + + fun instrumentationFunExport(funName: String): String + + companion object { + fun providerByPackageJson(packageJson: PackageJson): IExportsProvider = when (packageJson.isModule) { + true -> ModuleExportsProvider() + else -> RequireExportsProvider() + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt new file mode 100644 index 0000000000..77fae0fc1e --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt @@ -0,0 +1,16 @@ +package providers.exports + +class ModuleExportsProvider : IExportsProvider { + + override val exportsDelimiter: String = "," + + override val exportsPostfix: String = "}\n" + + override val exportsPrefix: String = "\nexport {" + + override val exportsRegex: Regex = Regex("(.*)") + + override fun getExportsFrame(exportString: String): String = exportString + + override fun instrumentationFunExport(funName: String): String = "\nexport {$funName}" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt new file mode 100644 index 0000000000..644744bd66 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt @@ -0,0 +1,16 @@ +package providers.exports + +class RequireExportsProvider : IExportsProvider { + + override val exportsDelimiter: String = "\n" + + override val exportsPostfix: String = "\n" + + override val exportsPrefix: String = "\n" + + override val exportsRegex: Regex = Regex("exports[.](.*) =") + + override fun getExportsFrame(exportString: String): String = "exports.$exportString = $exportString" + + override fun instrumentationFunExport(funName: String): String = "\nexports.$funName = $funName" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt new file mode 100644 index 0000000000..c2b821a904 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt @@ -0,0 +1,18 @@ +package providers.imports + +import service.PackageJson +import service.ServiceContext + +interface IImportsProvider { + + val ternScriptImports: String + + val tempFileImports: String + + companion object { + fun providerByPackageJson(packageJson: PackageJson, context: ServiceContext): IImportsProvider = when (packageJson.isModule) { + true -> ModuleImportsProvider(context) + else -> RequireImportsProvider(context) + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt new file mode 100644 index 0000000000..7d1aa55c01 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt @@ -0,0 +1,22 @@ +package providers.imports + +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings.fileUnderTestAliases + +class ModuleImportsProvider(context: ServiceContext) : IImportsProvider, ContextOwner by context { + + override val ternScriptImports: String = buildString { + appendLine("import * as tern from \"tern/lib/tern.js\"") + appendLine("import * as condense from \"tern/lib/condense.js\"") + appendLine("import * as util from \"tern/test/util.js\"") + appendLine("import * as fs from \"fs\"") + appendLine("import * as path from \"path\"") + } + + override val tempFileImports: String = buildString { + val importFileUnderTest = "./instr/${filePathToInference.first().substringAfterLast("/")}" + appendLine("import * as $fileUnderTestAliases from \"$importFileUnderTest\"") + appendLine("import * as fs from \"fs\"") + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt new file mode 100644 index 0000000000..d653b2b047 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt @@ -0,0 +1,23 @@ +package providers.imports + +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings.fileUnderTestAliases + +class RequireImportsProvider(context: ServiceContext) : IImportsProvider, ContextOwner by context { + + override val ternScriptImports: String = buildString { + appendLine("const tern = require(\"tern/lib/tern\")") + appendLine("const condense = require(\"tern/lib/condense.js\")") + appendLine("const util = require(\"tern/test/util.js\")") + appendLine("const fs = require(\"fs\")") + appendLine("const path = require(\"path\")") + } + + override val tempFileImports: String = buildString { + val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" + appendLine("const $fileUnderTestAliases = require(\"./$importFileUnderTest\")") + appendLine("const fs = require(\"fs\")\n") + } + +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt index 095dec1130..e36dd0e41d 100644 --- a/utbot-js/src/main/kotlin/service/InstrumentationService.kt +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -13,6 +13,8 @@ import utils.JsCmdExec import utils.PathResolver.getRelativePath import java.io.File import java.nio.file.Paths +import parser.JsParserUtils.getModuleImportText +import providers.exports.IExportsProvider import kotlin.io.path.pathString import kotlin.math.roundToInt @@ -144,7 +146,8 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset val covFunRegex = Regex("function (cov_.*)\\(\\).*") val funName = covFunRegex.find(instrumentedFileText.takeWhile { it != '{' })?.groups?.get(1)?.value ?: throw IllegalStateException("") - val fixedFileText = fixImportsInInstrumentedFile() + "\nexports.$funName = $funName" + val fixedFileText = fixImportsInInstrumentedFile() + + IExportsProvider.providerByPackageJson(packageJson).instrumentationFunExport(funName) File(instrumentedFilePath).writeTextAndUpdate(fixedFileText) covFunName = funName @@ -158,15 +161,27 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset private fun fixImportsInInstrumentedFile(): String { // nyc poorly handles imports paths in file to instrument. Manual fix required. NodeUtil.visitPreOrder(parsedInstrFile) { node -> - if (node.isRequireImport()) { - val currString = node.getRequireImportText() - val relPath = Paths.get( - getRelativePath( - "${projectPath}/${utbotDir}/instr", - File(filePathToInference.first()).parent - ) - ).resolve(currString).pathString.replace("\\", "/") - node.firstChild!!.next!!.string = relPath + when { + node.isRequireImport() -> { + val currString = node.getRequireImportText() + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.string = relPath + } + node.isImport -> { + val currString = node.getModuleImportText() + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.next!!.string = relPath + } } } return CodePrinter.Builder(parsedInstrFile).build() diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index a17e83e473..1ee7ab4553 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -16,25 +16,17 @@ import utils.constructClass import utils.data.MethodTypes import java.io.File import java.util.Locale - -/* - NOTE: this approach is quite bad, but we failed to implement alternatives. - TODO: 1. MINOR: Find a better solution after the first stable version. - 2. SEVERE: Load all necessary .js files in Tern.js since functions can be exported and used in other files. - */ +import providers.imports.IImportsProvider /** * Installs and sets up scripts for running Tern.js type guesser. */ class TernService(context: ServiceContext) : ContextOwner by context { + private val importProvider = IImportsProvider.providerByPackageJson(packageJson, context) private fun ternScriptCode() = """ -const tern = require("tern/lib/tern") -const condense = require("tern/lib/condense.js") -const util = require("tern/test/util.js") -const fs = require("fs") -const path = require("path") +${generateImportsSection()} var condenseDir = ""; @@ -60,11 +52,11 @@ function runTest(options) { } function test(options) { - if (typeof options == "string") options = {load: [options]}; + options = {load: options}; runTest(options); } -test("${filePathToInference.joinToString(separator = " ")}") +test(["${filePathToInference.joinToString(separator = "\", \"")}"]) """ init { @@ -77,6 +69,8 @@ test("${filePathToInference.joinToString(separator = " ")}") private lateinit var json: JSONObject + private fun generateImportsSection(): String = importProvider.ternScriptImports + private fun installDeps(path: String) { JsCmdExec.runCommand( dir = path, diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index df4f3b9e45..7a02e4c2f9 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -16,9 +16,8 @@ import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern +import providers.imports.IImportsProvider - -// TODO: Add "error" field in result json to not collide with "result" field upon error. class CoverageServiceProvider( private val context: ServiceContext, private val instrumentationService: InstrumentationService, @@ -26,11 +25,7 @@ class CoverageServiceProvider( private val description: JsMethodDescription ) : ContextOwner by context { - private val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" - - private val imports = - "const ${JsTestGenerationSettings.fileUnderTestAliases} = require(\"./$importFileUnderTest\")\n" + - "const fs = require(\"fs\")\n\n" + private val imports = IImportsProvider.providerByPackageJson(packageJson, context).tempFileImports private val filePredicate = """ function check_value(value, json) { diff --git a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt deleted file mode 100644 index 49fea8da23..0000000000 --- a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt +++ /dev/null @@ -1,41 +0,0 @@ -package utils - -import service.PackageJson - -object ExportsProvider { - - fun getExportsRegex(packageJson: PackageJson): Regex = with(packageJson) { - when (isModule) { - true -> Regex("(.*)") - false -> Regex("exports[.](.*) =") - } - } - - fun getExportsDelimiter(packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> "," - false -> "\n" - } - } - - fun getExportsFrame(exportString: String, packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> exportString - false -> "exports.$exportString = $exportString" - } - } - - fun getExportsPrefix(packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> "\nexport {" - false -> "\n" - } - } - - fun getExportsPostfix(packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> "}\n" - false -> "\n" - } - } -} \ No newline at end of file From 178534e5a9d411477ece8aeb410f896dd708ca01 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:59:13 +0300 Subject: [PATCH 16/74] [WIP] JavaScript test generation for multi-file projects --- .../src/main/kotlin/api/JsTestGenerator.kt | 5 ++-- .../src/main/kotlin/parser/JsAstScrapper.kt | 29 ++++++++++++------- .../src/main/kotlin/service/TernService.kt | 9 +++--- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 92ff042243..c5c6775bc5 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -364,11 +364,12 @@ class JsTestGenerator( private fun collectExports(methodId: JsMethodId): List { val res = mutableListOf() methodId.parameters.forEach { - if (!it.isJsBasic) { + if (!(it.isJsBasic || astScrapper.importsMap.contains(it.name))) { res += it.name } } - if (!methodId.returnType.isJsBasic) res += methodId.returnType.name + if (!methodId.returnType.isJsBasic && !astScrapper.importsMap.contains(methodId.returnType.name)) + res += methodId.returnType.name return res } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index 8699b73e3e..f90de9a48d 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -29,9 +29,26 @@ class JsAstScrapper( private val _filesToInfer: MutableList = mutableListOf(basePath) val filesToInfer: List get() = _filesToInfer.toList() + private val _importsMap = mutableMapOf() + val importsMap: Map + get() = _importsMap.toMap() + + init { + _importsMap.apply { + val visitor = Visitor() + visitor.accept(parsedFile) + val res = visitor.importNodes.fold(emptyMap()) { acc, node -> + val currAcc = acc.toList().toTypedArray() + val more = node.importedNodes().toList().toTypedArray() + mapOf(*currAcc, *more) + } + this.putAll(res) + this.toMap() + } + } fun findFunction(key: String, file: Node): Node? { - if (importsMap[key]?.isFunction == true) return importsMap[key] + if (_importsMap[key]?.isFunction == true) return _importsMap[key] val functionVisitor = JsFunctionAstVisitor(key, null) functionVisitor.accept(file) return try { @@ -40,7 +57,7 @@ class JsAstScrapper( } fun findClass(key: String, file: Node): Node? { - if (importsMap[key]?.isClass == true) return importsMap[key] + if (_importsMap[key]?.isClass == true) return _importsMap[key] val classVisitor = JsClassAstVisitor(key) classVisitor.accept(file) return try { @@ -53,14 +70,6 @@ class JsAstScrapper( return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } } - private val importsMap = run { - val visitor = Visitor() - visitor.accept(parsedFile) - visitor.importNodes.fold(emptyMap()) { acc, node -> - mapOf(*acc.toList().toTypedArray(), *node.importedNodes().toList().toTypedArray()) - } - } - private fun File.parseIfNecessary(): Node = _parsedFilesCache.getOrPut(this.path) { _filesToInfer += this.path.replace("\\", "/") diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 1ee7ab4553..44e69e01f2 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -4,6 +4,7 @@ import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMultipleClassId import framework.api.js.util.jsUndefinedClassId +import java.io.File import org.json.JSONException import org.json.JSONObject import parser.JsParserUtils @@ -11,12 +12,10 @@ import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getAbstractFunctionParams import parser.JsParserUtils.getClassName import parser.JsParserUtils.getConstructor +import providers.imports.IImportsProvider import utils.JsCmdExec import utils.constructClass import utils.data.MethodTypes -import java.io.File -import java.util.Locale -import providers.imports.IImportsProvider /** * Installs and sets up scripts for running Tern.js type guesser. @@ -178,8 +177,8 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) ) } - name.contains('|') -> JsMultipleClassId(name.lowercase(Locale.getDefault())) - else -> JsClassId(name.lowercase(Locale.getDefault())) + name.contains('|') -> JsMultipleClassId(name) + else -> JsClassId(name) } return try { From bae6e1378476193dbea4a91e3c1dd5561933d8e1 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sun, 19 Feb 2023 23:29:44 +0300 Subject: [PATCH 17/74] Added array support for JavaScript test generation --- .../src/main/kotlin/api/JsTestGenerator.kt | 5 +- .../kotlin/framework/api/js/util/JsIdUtil.kt | 20 ++- .../tree/JsCgVariableConstructor.kt | 156 +++++++++++++++++- .../model/constructor/visitor/CgJsRenderer.kt | 3 +- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 4 +- .../fuzzer/providers/ArrayValueProvider.kt | 43 +++++ .../coverage/CoverageServiceProvider.kt | 14 ++ 7 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index c5c6775bc5..2f22f07b82 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -4,6 +4,7 @@ import codegen.JsCodeGenerator import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMethodId +import framework.api.js.util.isExportable import framework.api.js.util.isJsBasic import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId @@ -364,11 +365,11 @@ class JsTestGenerator( private fun collectExports(methodId: JsMethodId): List { val res = mutableListOf() methodId.parameters.forEach { - if (!(it.isJsBasic || astScrapper.importsMap.contains(it.name))) { + if (it.isExportable && !astScrapper.importsMap.contains(it.name)) { res += it.name } } - if (!methodId.returnType.isJsBasic && !astScrapper.importsMap.contains(methodId.returnType.name)) + if (methodId.returnType.isExportable && !astScrapper.importsMap.contains(methodId.returnType.name)) res += methodId.returnType.name return res } diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index a54d7879de..5ac080bfc9 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -3,6 +3,9 @@ package framework.api.js.util import framework.api.js.JsClassId import framework.api.js.JsMultipleClassId import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId @@ -31,11 +34,26 @@ fun ClassId.toJsClassId() = else -> jsUndefinedClassId } +fun JsClassId.defaultJsValueModel(): UtModel = when (this) { + jsNumberClassId -> UtPrimitiveModel(0.0) + jsDoubleClassId -> UtPrimitiveModel(Double.POSITIVE_INFINITY) + jsBooleanClassId -> UtPrimitiveModel(false) + jsStringClassId -> UtPrimitiveModel("default") + jsUndefinedClassId -> UtPrimitiveModel(0.0) + else -> UtNullModel(this) +} + val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId +val JsClassId.isExportable: Boolean + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray) + val JsClassId.isClass: Boolean get() = !(this.isJsBasic || this == jsErrorClassId) val JsClassId.isUndefined: Boolean - get() = this == jsUndefinedClassId \ No newline at end of file + get() = this == jsUndefinedClassId + +val JsClassId.isJsArray: Boolean + get() = this.name == "array" && this.elementClassId is JsClassId \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt index 098babf9e0..db7db14eee 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt @@ -1,25 +1,177 @@ package framework.codegen.model.constructor.tree +import framework.api.js.JsClassId +import framework.api.js.JsNullModel import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isExportable +import framework.api.js.util.jsBooleanClassId +import framework.api.js.util.jsDoubleClassId +import framework.api.js.util.jsNumberClassId +import framework.api.js.util.jsStringClassId +import framework.api.js.util.jsUndefinedClassId import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgExpression import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.CgComponents import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.inc +import org.utbot.framework.codegen.util.lessThan import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set class JsCgVariableConstructor(ctx: CgContext) : CgVariableConstructor(ctx) { + + private val nameGenerator = CgComponents.getNameGeneratorBy(context) override fun getOrCreateVariable(model: UtModel, name: String?): CgValue { - return if (model is UtReferenceModel) valueByModelId.getOrPut(model.id) { + return if (model is UtAssembleModel) valueByModelId.getOrPut(model.id) { // TODO SEVERE: May lead to unexpected behavior in case of changes to the original method super.getOrCreateVariable(model, name) } else valueByModel.getOrPut(model) { + val baseName = name ?: nameGenerator.nameFrom(model.classId) when (model) { is JsPrimitiveModel -> CgLiteral(model.classId, model.value) + is UtArrayModel -> constructArray(model, baseName) else -> nullLiteral() } } } + + private val MAX_ARRAY_INITIALIZER_SIZE = 10 + + private operator fun UtArrayModel.get(index: Int): UtModel = stores[index] ?: constModel + + private val defaultByPrimitiveType: Map = mapOf( + jsBooleanClassId to false, + jsStringClassId to "default", + jsUndefinedClassId to 0.0, + jsNumberClassId to 0.0, + jsDoubleClassId to Double.POSITIVE_INFINITY + ) + + private infix fun UtModel.isNotJsDefaultValueOf(type: JsClassId): Boolean = !(this isJsDefaultValueOf type) + + private infix fun UtModel.isJsDefaultValueOf(type: JsClassId): Boolean = when (this) { + is JsNullModel -> type.isExportable + is JsPrimitiveModel -> value == defaultByPrimitiveType[type] + else -> false + } + + private fun CgVariable.setArrayElement(index: Any, value: CgValue) { + val i = index.resolve() + this.at(i) `=` value + } + + private fun basicForLoop(until: Any, body: (i: CgExpression) -> Unit) { + basicForLoop(start = 0, until, body) + } + + private fun basicForLoop(start: Any, until: Any, body: (i: CgExpression) -> Unit) { + forLoop { + val (i, init) = loopInitialization(jsNumberClassId, "i", start.resolve()) + initialization = init + condition = i lessThan until.resolve() + update = i.inc() + statements = block { body(i) } + } + } + + internal fun loopInitialization( + variableType: ClassId, + baseVariableName: String, + initializer: Any? + ): Pair { + val declaration = CgDeclaration(variableType, baseVariableName.toVarName(), initializer.resolve()) + val variable = declaration.variable + updateVariableScope(variable) + return variable to declaration + } + + private fun constructArray(arrayModel: UtArrayModel, baseName: String?): CgVariable { + val elementType = arrayModel.classId.elementClassId!! as JsClassId + val elementModels = (0 until arrayModel.length).map { + arrayModel.stores.getOrDefault(it, arrayModel.constModel) + } + + val allPrimitives = elementModels.all { it is JsPrimitiveModel } + val allNulls = elementModels.all { it is JsNullModel } + // we can use array initializer if all elements are primitives or all of them are null, + // and the size of an array is not greater than the fixed maximum size + val canInitWithValues = (allPrimitives || allNulls) && elementModels.size <= MAX_ARRAY_INITIALIZER_SIZE + + val initializer = if (canInitWithValues) { + val elements = elementModels.map { model -> + when (model) { + is JsPrimitiveModel -> model.value.resolve() + is UtNullModel -> null.resolve() + else -> error("Non primitive or null model $model is unexpected in array initializer") + } + } + CgArrayInitializer(arrayModel.classId, elementType, elements) + } else { + CgAllocateArray(arrayModel.classId, elementType, arrayModel.length) + } + + val array = newVar(arrayModel.classId, baseName) { initializer } + valueByModelId[arrayModel.id] = array + + if (canInitWithValues) { + return array + } + + if (arrayModel.length <= 0) return array + if (arrayModel.length == 1) { + // take first element value if it is present, otherwise use default value from model + val elementModel = arrayModel[0] + if (elementModel isNotJsDefaultValueOf elementType) { + array.setArrayElement(0, getOrCreateVariable(elementModel)) + } + } else { + val indexedValuesFromStores = + if (arrayModel.stores.size == arrayModel.length) { + // do not use constModel because stores fully cover array + arrayModel.stores.entries.filter { (_, element) -> element isNotJsDefaultValueOf elementType } + } else { + // fill array if constModel is not default type value + if (arrayModel.constModel isNotJsDefaultValueOf elementType) { + val defaultVariable = getOrCreateVariable(arrayModel.constModel, "defaultValue") + basicForLoop(arrayModel.length) { i -> + array.setArrayElement(i, defaultVariable) + } + } + + // choose all not default values + val defaultValue = if (arrayModel.constModel isJsDefaultValueOf elementType) { + arrayModel.constModel + } else { + elementType.defaultValueModel() + } + arrayModel.stores.entries.filter { (_, element) -> element != defaultValue } + } + + // set all values from stores manually + indexedValuesFromStores + .sortedBy { it.key } + .forEach { (index, element) -> array.setArrayElement(index, getOrCreateVariable(element)) } + } + + return array + } + + private fun String.toVarName(): String = nameGenerator.variableName(this) } diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index 7063f54b43..e5243e5fcf 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -196,8 +196,9 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP override fun visit(element: CgArrayInitializer) { val elementType = element.elementType val elementsInLine = arrayElementsInLine(elementType) - + print("[") element.values.renderElements(elementsInLine) + print("]") } override fun visit(element: CgClassFile) { diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index cc3420a45c..813baf97ff 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -1,6 +1,7 @@ package fuzzer import framework.api.js.JsClassId +import fuzzer.providers.ArrayValueProvider import fuzzer.providers.BoolValueProvider import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider @@ -14,7 +15,8 @@ fun defaultValueProviders() = listOf( BoolValueProvider, NumberValueProvider, StringValueProvider, - ObjectValueProvider() + ObjectValueProvider(), + ArrayValueProvider() ) class JsFuzzing( diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt new file mode 100644 index 0000000000..33ea48c933 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -0,0 +1,43 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.util.defaultJsValueModel +import framework.api.js.util.isJsArray +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator +import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +private val idGenerator = ReferencePreservingIntIdGenerator() + +class ArrayValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsArray + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + yield( + Seed.Collection( + construct = Routine.Collection { + UtArrayModel( + id = idGenerator.createId(), + classId = type, + length = it, + constModel = (type.elementClassId!! as JsClassId).defaultJsValueModel(), + stores = hashMapOf(), + ).fuzzed { + summary = "%var% = ${type.elementClassId!!.simpleName}[$it]" + } + }, + modify = Routine.ForEach(listOf(type.elementClassId!! as JsClassId)) { self, i, values -> + (self.model as UtArrayModel).stores[i] = values.first().model + } + )) + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 7a02e4c2f9..87cc161a26 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -16,6 +16,8 @@ import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtNullModel import providers.imports.IImportsProvider class CoverageServiceProvider( @@ -209,10 +211,22 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) return callConstructorString + paramsString } + private fun UtArrayModel.toParamString(): String { + val paramsString = stores.values.joinToString( + prefix = "[", + postfix = "]", + ) { + it.toCallString() + } + return paramsString + } + private fun UtModel.toCallString(): String = when (this) { is UtAssembleModel -> this.toParamString() + is UtArrayModel -> this.toParamString() + is UtNullModel -> "null" else -> { (this as JsPrimitiveModel).value.escapeSymbolsIfNecessary().quoteWrapIfNecessary() } From 6e01ae8e6bd04a20fbfbd355d6daa865e67fdb70 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 21 Feb 2023 12:27:39 +0300 Subject: [PATCH 18/74] [WIP] Map data structure support for JavaScript --- .../kotlin/framework/api/js/util/JsIdUtil.kt | 5 +- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 2 + .../fuzzer/providers/ArrayValueProvider.kt | 6 +- .../fuzzer/providers/MapValueProvider.kt | 80 +++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index 5ac080bfc9..2360fa0d6a 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -56,4 +56,7 @@ val JsClassId.isUndefined: Boolean get() = this == jsUndefinedClassId val JsClassId.isJsArray: Boolean - get() = this.name == "array" && this.elementClassId is JsClassId \ No newline at end of file + get() = this.name == "array" && this.elementClassId is JsClassId + +val JsClassId.isJsMap: Boolean + get() = this.name == "Map" diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index 813baf97ff..e345e1c6e0 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -3,6 +3,7 @@ package fuzzer import framework.api.js.JsClassId import fuzzer.providers.ArrayValueProvider import fuzzer.providers.BoolValueProvider +import fuzzer.providers.MapValueProvider import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider import fuzzer.providers.StringValueProvider @@ -15,6 +16,7 @@ fun defaultValueProviders() = listOf( BoolValueProvider, NumberValueProvider, StringValueProvider, + MapValueProvider, ObjectValueProvider(), ArrayValueProvider() ) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index 33ea48c933..04b49cf7bf 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -12,10 +12,10 @@ import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -private val idGenerator = ReferencePreservingIntIdGenerator() - class ArrayValueProvider : ValueProvider { + private val idGenerator = ReferencePreservingIntIdGenerator() + override fun accept(type: JsClassId): Boolean = type.isJsArray override fun generate( @@ -40,4 +40,4 @@ class ArrayValueProvider : ValueProvider { + + private val idGenerator = ReferencePreservingIntIdGenerator() + + override fun accept(type: JsClassId): Boolean = type.isJsMap + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + val modifications = mutableListOf>() + jsBasic.zip(jsBasic).map { (a, b) -> listOf(a, b) }.forEach { typeParameters -> + modifications += Routine.Call(typeParameters) { instance, arguments -> + val model = instance.model as UtAssembleModel + (model).modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "set", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId, jsUndefinedClassId) + ), + arguments.map { it.model } + ) + } + } + yield(Seed.Recursive( + construct = Routine.Create(listOf(jsUndefinedClassId, jsUndefinedClassId)) { + UtAssembleModel( + id = idGenerator.createId(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + empty = Routine.Empty { + UtAssembleModel( + id = idGenerator.createId(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + modify = modifications.asSequence() + )) + } +} From 9a398f07cac8efb819cf2e408c93986378fd5e97 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Tue, 21 Feb 2023 20:21:22 +0300 Subject: [PATCH 19/74] [WIP] JavaScript NPM packages installation enhancement --- .../plugin/language/js/JsDialogProcessor.kt | 5 +- .../kotlin/framework/api/js/util/JsIdUtil.kt | 3 +- .../main/kotlin/service/PackageJsonService.kt | 23 ++++---- .../kotlin/settings/JsPackagesSettings.kt | 55 ++++++++++++++----- 4 files changed, 56 insertions(+), 30 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 82041d5ab8..362957d8b4 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 {} @@ -256,12 +257,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/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index 2360fa0d6a..c74df2c2f7 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -9,6 +9,7 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.isMap val jsUndefinedClassId = JsClassId("undefined") val jsNumberClassId = JsClassId("number") @@ -47,7 +48,7 @@ val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId val JsClassId.isExportable: Boolean - get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray) + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap) val JsClassId.isClass: Boolean get() = !(this.isJsBasic || this == jsErrorClassId) diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index 354aa9869a..a838af0ec3 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -1,23 +1,25 @@ package service -import org.json.JSONException -import org.json.JSONObject import java.io.File import java.io.FilenameFilter +import org.json.JSONObject data class PackageJson( - val isModule: Boolean + val isModule: Boolean, + val deps: Set ) { companion object { - val defaultConfig = PackageJson(false) + val defaultConfig = PackageJson(false, emptySet()) } } - -class PackageJsonService(context: ServiceContext) : ContextOwner by context { +class PackageJsonService( + private val filePathToInference: String, + private val projectPath: String +) { fun findClosestConfig(): PackageJson { - var currDir = File(filePathToInference.first().substringBeforeLast("/")) + var currDir = File(filePathToInference.substringBeforeLast("/")) do { val matchingFiles: Array = currDir.listFiles( FilenameFilter { _, name -> @@ -33,11 +35,8 @@ class PackageJsonService(context: ServiceContext) : ContextOwner by context { private fun parseConfig(configFile: File): PackageJson { val configAsJson = JSONObject(configFile.readText()) return PackageJson( - isModule = try { - (configAsJson.getString("type") == "module") - } catch (e: JSONException) { - false - }, + 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 2a760202938c68d55d82916d7491b9183df49c0a Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 27 Feb 2023 13:13:16 +0300 Subject: [PATCH 20/74] Enhanced package installation for JavaScript --- .../plugin/language/js/JsDialogProcessor.kt | 64 ++++++++--------- .../language/js/NycSourceFileChooser.kt | 5 +- .../src/main/kotlin/api/JsTestGenerator.kt | 5 +- .../kotlin/settings/JsPackagesSettings.kt | 70 ++++++++++++++----- 4 files changed, 90 insertions(+), 54 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 362957d8b4..fd8f85f27c 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) } @@ -177,7 +174,12 @@ object JsDialogProcessor { if (name == dummyClassName) null else name }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), - exportsManager = partialApplication(JsDialogProcessor::manageExports, editor, project, editor.document.text), + exportsManager = partialApplication( + JsDialogProcessor::manageExports, + editor, + project, + editor.document.text + ), settings = JsDynamicSettings( pathToNode = model.pathToNode, pathToNYC = model.pathToNYC, @@ -213,7 +215,13 @@ object JsDialogProcessor { return { d: D, e: E -> f(a, b, c, d, e) } } - private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String) { + private fun manageExports( + editor: Editor, + project: Project, + fileText: String, + exports: List, + swappedText: (String?) -> String + ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { when { @@ -257,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 @@ -289,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 2f22f07b82..ecc9d3c250 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -105,7 +105,10 @@ class JsTestGenerator( parsedFile = parsedFile, settings = settings, ) - context.packageJson = PackageJsonService(context).findClosestConfig() + context.packageJson = PackageJsonService( + sourceFilePath, + projectPath, + ).findClosestConfig() val paramNames = mutableMapOf>() val testSets = mutableListOf() val classNode = 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 f834e520551371398d0a26f8cac18816b718f514 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 27 Feb 2023 13:47:39 +0300 Subject: [PATCH 21/74] Fixed id error for UtAssembleModel in JavaScript --- .../main/kotlin/api/JsUtModelConstructor.kt | 6 +- .../src/main/kotlin/fuzzer/JsFuzzerApi.kt | 7 ++ .../fuzzer/providers/ArrayValueProvider.kt | 6 +- .../fuzzer/providers/MapValueProvider.kt | 71 +++++++++---------- .../fuzzer/providers/ObjectValueProvider.kt | 16 +++-- 5 files changed, 55 insertions(+), 51 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt b/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt index 5de6a87971..98705c1d26 100644 --- a/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt +++ b/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt @@ -7,17 +7,15 @@ import framework.api.js.JsPrimitiveModel import framework.api.js.JsUndefinedModel import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel -import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructorInterface class JsUtModelConstructor : UtModelConstructorInterface { - private val idGenerator = ReferencePreservingIntIdGenerator() - // TODO SEVERE: Requires substantial expansion to other types @Suppress("NAME_SHADOWING") override fun construct(value: Any?, classId: ClassId): UtModel { @@ -52,7 +50,7 @@ class JsUtModelConstructor : UtModelConstructorInterface { val values = (value as Map).values.map { construct(it, JsEmptyClassId()) } - val id = idGenerator.createId() + val id = JsIdProvider.get() val instantiationCall = UtExecutableCallModel(null, constructor, values) return UtAssembleModel( id = id, diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt index 150a9343a6..4476c2a5c6 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -2,6 +2,7 @@ package fuzzer import framework.api.js.JsClassId import framework.api.js.util.isClass +import java.util.concurrent.atomic.AtomicInteger import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzer.FuzzedValue @@ -57,3 +58,9 @@ class JsFeedback( data class JsStatement( val number: Int ) + +object JsIdProvider { + private var _id = AtomicInteger(0) + + fun get() = _id.incrementAndGet() +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index 04b49cf7bf..70fda93be9 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -3,10 +3,10 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.util.defaultJsValueModel import framework.api.js.util.isJsArray +import fuzzer.JsIdProvider import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed @@ -14,8 +14,6 @@ import org.utbot.fuzzing.ValueProvider class ArrayValueProvider : ValueProvider { - private val idGenerator = ReferencePreservingIntIdGenerator() - override fun accept(type: JsClassId): Boolean = type.isJsArray override fun generate( @@ -26,7 +24,7 @@ class ArrayValueProvider : ValueProvider { - private val idGenerator = ReferencePreservingIntIdGenerator() - override fun accept(type: JsClassId): Boolean = type.isJsMap override fun generate( @@ -43,38 +41,39 @@ object MapValueProvider : ValueProvider { - private val idGenerator = ReferencePreservingIntIdGenerator() - override fun accept(type: JsClassId): Boolean { return type.isClass } @@ -31,10 +29,13 @@ class ObjectValueProvider : ValueProvider { + private fun createValue( + classId: JsClassId, + constructorId: JsConstructorId + ): Seed.Recursive { return Seed.Recursive( construct = Routine.Create(constructorId.parameters) { values -> - val id = idGenerator.createId() + val id = JsIdProvider.get() UtAssembleModel( id = id, classId = classId, @@ -45,7 +46,8 @@ class ObjectValueProvider : ValueProvider Date: Fri, 3 Mar 2023 02:34:37 +0300 Subject: [PATCH 22/74] Enhanced Map datastructure fuzzing for JavaScript --- .../model/constructor/visitor/CgJsRenderer.kt | 7 ++- .../src/main/kotlin/parser/JsParserUtils.kt | 5 ++ .../coverage/CoverageServiceProvider.kt | 63 +++++++++++++++---- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index e5243e5fcf..f8d30f51a3 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -1,5 +1,7 @@ package framework.codegen.model.constructor.visitor +import framework.api.js.JsClassId +import framework.api.js.util.isExportable import org.apache.commons.text.StringEscapeUtils import org.utbot.framework.codegen.domain.RegularImport import org.utbot.framework.codegen.domain.StaticImport @@ -248,7 +250,10 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP } override fun visit(element: CgConstructorCall) { - print("new $fileUnderTestAliases.${element.executableId.classId.name}") + val importPrefix = "$fileUnderTestAliases.".takeIf { + (element.executableId.classId as JsClassId).isExportable + } ?: "" + print("new $importPrefix${element.executableId.classId.name}") print("(") element.arguments.renderSeparated() print(")") diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index e3c1a97340..bb1f66c55b 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -69,6 +69,11 @@ object JsParserUtils { this.isString -> this.string this.isTrue -> true this.isFalse -> false + this.isCall -> { + if (this.firstChild?.isGetProp == true) { + this.firstChild?.next?.getAnyValue() + } else null + } else -> null } diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 87cc161a26..9bd25a4fa8 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -1,9 +1,12 @@ package service.coverage +import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isExportable import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription +import java.lang.StringBuilder import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic @@ -17,6 +20,7 @@ import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtNullModel import providers.imports.IImportsProvider @@ -142,7 +146,7 @@ fs.writeFileSync("$resFilePath", JSON.stringify(json)) index: Int, resFilePath: String, ): String { - val callString = makeCallFunctionString(fuzzedValue, method, containingClass) + val callString = makeCallFunctionString(fuzzedValue, method, containingClass, index) return """ let json$index = {} json$index.is_inf = false @@ -151,7 +155,7 @@ json$index.is_error = false json$index.spec_sign = 1 let res$index try { - res$index = $callString + $callString check_value(res$index, json$index) } catch(e) { res$index = e.message @@ -169,21 +173,34 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) private fun makeCallFunctionString( fuzzedValue: List, method: JsMethodId, - containingClass: String? + containingClass: String?, + index: Int ): String { + val paramsInit = initParams(fuzzedValue) val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue val initClass = containingClass?.let { if (!method.isStatic) { - description.thisInstance?.let { fuzzedValue[0].model.toCallString() } + description.thisInstance?.let { fuzzedValue[0].model.initModelAsString() } ?: "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" } else "${JsTestGenerationSettings.fileUnderTestAliases}.$it" } ?: JsTestGenerationSettings.fileUnderTestAliases var callString = "$initClass.${method.name}" - callString += actualParams.joinToString( - prefix = "(", + callString = List(actualParams.size) { idx -> "param$idx" }.joinToString( + prefix = "res$index = $callString(", postfix = ")", - ) { value -> value.model.toCallString() } - return callString + ) + return paramsInit + callString + } + + private fun initParams(fuzzedValue: List): String { + val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue + return actualParams.mapIndexed { index, param -> + val varName = "param$index" + buildString { + appendLine("let $varName = ${param.model.initModelAsString()}") + (param.model as? UtAssembleModel)?.initModificationsAsString(this, varName) + } + }.joinToString() } private fun Any.quoteWrapIfNecessary(): String = @@ -201,12 +218,15 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) } private fun UtAssembleModel.toParamString(): String { - val callConstructorString = "new ${JsTestGenerationSettings.fileUnderTestAliases}.${classId.name}" + val importPrefix = "new ${JsTestGenerationSettings.fileUnderTestAliases}.".takeIf { + (classId as JsClassId).isExportable + } ?: "new " + val callConstructorString = importPrefix + classId.name val paramsString = instantiationCall.params.joinToString( prefix = "(", postfix = ")", ) { - it.toCallString() + it.initModelAsString() } return callConstructorString + paramsString } @@ -216,13 +236,14 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) prefix = "[", postfix = "]", ) { - it.toCallString() + it.initModelAsString() } return paramsString } - private fun UtModel.toCallString(): String = + + private fun UtModel.initModelAsString(): String = when (this) { is UtAssembleModel -> this.toParamString() is UtArrayModel -> this.toParamString() @@ -231,4 +252,22 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) (this as JsPrimitiveModel).value.escapeSymbolsIfNecessary().quoteWrapIfNecessary() } } + + private fun UtAssembleModel.initModificationsAsString(stringBuilder: StringBuilder, varName: String) { + with(stringBuilder) { + this@initModificationsAsString.modificationsChain.forEach { + if (it is UtExecutableCallModel) { + val exec = it.executable as JsMethodId + appendLine( + it.params.joinToString( + prefix = "$varName.${exec.name}(", + postfix = ")" + ) { model -> + model.initModelAsString() + } + ) + } + } + } + } } From 83527be56bf0f208e9457d09eaf790460231617a Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 15:58:23 +0300 Subject: [PATCH 23/74] 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 5de67a718a4da3d10562f536f334f00a46d36559 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 16:00:43 +0300 Subject: [PATCH 24/74] Add message about time expiration for npm packages installation --- .../plugin/language/js/JsDialogProcessor.kt | 140 +++++++++--------- .../kotlin/settings/JsPackagesSettings.kt | 45 ++++-- 2 files changed, 99 insertions(+), 86 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 fd8f85f27c..f80e550b78 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."), @@ -175,10 +169,7 @@ object JsDialogProcessor { }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), exportsManager = partialApplication( - JsDialogProcessor::manageExports, - editor, - project, - editor.document.text + JsDialogProcessor::manageExports, editor, project, editor.document.text ), settings = JsDynamicSettings( pathToNode = model.pathToNode, @@ -187,8 +178,7 @@ object JsDialogProcessor { timeout = model.timeout, coverageMode = model.coverageMode ), - isCancelled = { indicator.isCanceled } - ) + isCancelled = { indicator.isCanceled }) indicator.fraction = indicator.fraction.coerceAtLeast(0.9) indicator.text = "Generate code for tests" @@ -196,11 +186,9 @@ object JsDialogProcessor { val generatedCode = testGenerator.run() invokeLater { runWriteAction { - val testPsiFile = - testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) - .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) - val testFileEditor = - CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) + val testPsiFile = testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) + .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + val testFileEditor = CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) unblockDocument(project, testFileEditor.document) testFileEditor.document.setText(generatedCode) unblockDocument(project, testFileEditor.document) @@ -216,11 +204,7 @@ object JsDialogProcessor { } private fun manageExports( - editor: Editor, - project: Project, - fileText: String, - exports: List, - swappedText: (String?) -> String + editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { @@ -265,33 +249,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 db15ad2fc76f8bd81dc72fdfdd3e1a78e37e4cbd Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 3 Mar 2023 17:04:04 +0300 Subject: [PATCH 25/74] JavaScript test generation for Set data structure implemented --- .../kotlin/framework/api/js/util/JsIdUtil.kt | 6 +- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 2 + .../fuzzer/providers/SetValueProvider.kt | 79 +++++++++++++++++++ .../main/kotlin/parser/JsFuzzerAstVisitor.kt | 19 +++-- .../src/main/kotlin/service/TernService.kt | 9 --- 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index c74df2c2f7..deb0dbc2b6 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -9,7 +9,6 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.isMap val jsUndefinedClassId = JsClassId("undefined") val jsNumberClassId = JsClassId("number") @@ -48,7 +47,7 @@ val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId val JsClassId.isExportable: Boolean - get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap) + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap || this.isJsSet) val JsClassId.isClass: Boolean get() = !(this.isJsBasic || this == jsErrorClassId) @@ -61,3 +60,6 @@ val JsClassId.isJsArray: Boolean val JsClassId.isJsMap: Boolean get() = this.name == "Map" + +val JsClassId.isJsSet: Boolean + get() = this.name == "Set" diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index e345e1c6e0..53dfbbf130 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -6,6 +6,7 @@ import fuzzer.providers.BoolValueProvider import fuzzer.providers.MapValueProvider import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider +import fuzzer.providers.SetValueProvider import fuzzer.providers.StringValueProvider import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzing.Fuzzing @@ -17,6 +18,7 @@ fun defaultValueProviders() = listOf( NumberValueProvider, StringValueProvider, MapValueProvider, + SetValueProvider, ObjectValueProvider(), ArrayValueProvider() ) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt new file mode 100644 index 0000000000..829f14bf79 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt @@ -0,0 +1,79 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsMethodId +import framework.api.js.util.isJsSet +import framework.api.js.util.jsBasic +import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +object SetValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsSet + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + val modifications = mutableListOf>() + jsBasic.forEach { typeParameter -> + modifications += Routine.Call(listOf(typeParameter)) { instance, arguments -> + val model = instance.model as UtAssembleModel + (model).modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "add", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId) + ), + arguments.map { it.model } + ) + } + } + yield( + Seed.Recursive( + construct = Routine.Create(listOf(jsUndefinedClassId)) { + UtAssembleModel( + id = JsIdProvider.get(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + empty = Routine.Empty { + UtAssembleModel( + id = JsIdProvider.get(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ) + ).fuzzed { + summary = "%var% = collection" + } + }, + modify = modifications.asSequence() + ) + ) + } +} diff --git a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt index 08a6c41d49..bb50667b3b 100644 --- a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt +++ b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt @@ -21,27 +21,30 @@ class JsFuzzerAstVisitor : IAstVisitor { NodeUtil.visitPreOrder(rootNode) { node -> val currentFuzzedOp = node.toFuzzedContextComparisonOrNull() when { - node.isCase -> validateNode(node.firstChild?.getAnyValue()) + node.isCase -> validateNode(node.firstChild?.getAnyValue(), FuzzedContext.Comparison.NE) + node.isCall -> { + validateNode(node.getAnyValue(), FuzzedContext.Comparison.NE) + } currentFuzzedOp != null -> { lastFuzzedOpGlobal = currentFuzzedOp - validateNode(node.getBinaryExprLeftOperand().getAnyValue()) + validateNode(node.getBinaryExprLeftOperand().getAnyValue(), lastFuzzedOpGlobal) lastFuzzedOpGlobal = if (lastFuzzedOpGlobal is FuzzedContext.Comparison) (lastFuzzedOpGlobal as FuzzedContext.Comparison).reverse() else FuzzedContext.Unknown - validateNode(node.getBinaryExprRightOperand().getAnyValue()) + validateNode(node.getBinaryExprRightOperand().getAnyValue(), lastFuzzedOpGlobal) } } } } - private fun validateNode(value: Any?) { + private fun validateNode(value: Any?, fuzzedOp: FuzzedContext) { when (value) { is String -> { fuzzedConcreteValues.add( FuzzedConcreteValue( jsStringClassId, value.toString(), - lastFuzzedOpGlobal + fuzzedOp ) ) } @@ -51,14 +54,14 @@ class JsFuzzerAstVisitor : IAstVisitor { FuzzedConcreteValue( jsBooleanClassId, value, - lastFuzzedOpGlobal + fuzzedOp ) ) } is Double -> { - fuzzedConcreteValues.add(FuzzedConcreteValue(jsDoubleClassId, value, lastFuzzedOpGlobal)) + fuzzedConcreteValues.add(FuzzedConcreteValue(jsDoubleClassId, value, fuzzedOp)) } } } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 44e69e01f2..3ed4d7b780 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -61,7 +61,6 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) init { with(context) { setupTernEnv("$projectPath/$utbotDir") - installDeps("$projectPath/$utbotDir") runTypeInferencer() } } @@ -70,14 +69,6 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) private fun generateImportsSection(): String = importProvider.ternScriptImports - private fun installDeps(path: String) { - JsCmdExec.runCommand( - dir = path, - shouldWait = true, - cmd = arrayOf("\"${settings.pathToNPM}\"", "i", "tern", "-l"), - ) - } - private fun setupTernEnv(path: String) { File(path).mkdirs() val ternScriptFile = File("$path/ternScript.js") From d90e12c0752891f4ccf1224febddb83d37ad885e Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Sun, 5 Feb 2023 01:23:46 +0300 Subject: [PATCH 26/74] [WIP] Better imports manager for JS + removed thisInstance builder --- .../src/main/kotlin/api/JsTestGenerator.kt | 22 +++-- .../src/main/kotlin/parser/JsAstScrapper.kt | 90 +++++++++++++++++++ .../src/main/kotlin/parser/JsParserUtils.kt | 7 ++ 3 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 utbot-js/src/main/kotlin/parser/JsAstScrapper.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index a35344f7b9..2ca5460527 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -30,8 +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 parser.JsClassAstVisitor -import parser.JsFunctionAstVisitor +import parser.JsAstScrapper import parser.JsFuzzerAstVisitor import parser.JsParserUtils import parser.JsParserUtils.getAbstractFunctionName @@ -47,7 +46,6 @@ import service.InstrumentationService import service.ServiceContext import service.TernService import settings.JsDynamicSettings -import settings.JsTestGenerationSettings.dummyClassName import settings.JsTestGenerationSettings.fuzzingThreshold import settings.JsTestGenerationSettings.fuzzingTimeout import utils.PathResolver @@ -76,6 +74,8 @@ class JsTestGenerator( private lateinit var parsedFile: Node + private lateinit var astScrapper: JsAstScrapper + private val utbotDir = "utbotJs" init { @@ -93,6 +93,7 @@ class JsTestGenerator( */ fun run(): String { parsedFile = runParser(fileText) + astScrapper = JsAstScrapper(parsedFile) val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, @@ -347,12 +348,11 @@ class JsTestGenerator( } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { - val visitor = JsFunctionAstVisitor( - focusedMethodName, - if (parentClassName != dummyClassName) parentClassName else null - ) - visitor.accept(parsedFile) - return visitor.targetFunctionNode + return parentClassName?.let { astScrapper.findMethod(focusedMethodName, parentClassName) } + ?: astScrapper.findFunction(focusedMethodName) + ?: throw IllegalStateException( + "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" + ) } private fun getMethodsToTest() = @@ -363,9 +363,7 @@ class JsTestGenerator( } private fun getClassMethods(className: String): List { - val visitor = JsClassAstVisitor(className) - visitor.accept(parsedFile) - val classNode = JsParserUtils.searchForClassDecl(className, parsedFile) + val classNode = astScrapper.findClass(className) return classNode?.getClassMethods() ?: throw IllegalStateException("Can't extract methods of class $className") } } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt new file mode 100644 index 0000000000..9691fbc109 --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -0,0 +1,90 @@ +package parser + +import com.google.javascript.jscomp.Compiler +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.jscomp.SourceFile +import com.google.javascript.rhino.Node +import java.io.File +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.isRequireImport + +class JsAstScrapper(private val parsedFile: Node) { + + // Used not to parse the same file multiple times. + private val _parsedFilesCache = mutableMapOf() + + fun findFunction(key: String): Node? { + if (importsMap[key]?.isFunction == true) return importsMap[key] + val functionVisitor = JsFunctionAstVisitor(key, null) + functionVisitor.accept(parsedFile) + return try { + functionVisitor.targetFunctionNode + } catch(e: Exception) { null } + } + + fun findClass(key: String): Node? { + if (importsMap[key]?.isClass == true) return importsMap[key] + val classVisitor = JsClassAstVisitor(key) + classVisitor.accept(parsedFile) + return try { + classVisitor.targetClassNode + } catch (e: Exception) { null } + } + + fun findMethod(classKey: String, methodKey: String): Node? { + val classNode = findClass(classKey) + return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } + } + + private val importsMap = run { + val visitor = Visitor() + visitor.accept(parsedFile) + visitor.importNodes.fold(emptyMap()) { acc, node -> + mapOf(*acc.toList().toTypedArray(), *node.importedNodes().toList().toTypedArray()) + } + } + + private fun Node.importedNodes(): Map { + return when { + this.isRequireImport() -> mapOf( + this.parent!!.string to this.firstChild!!.next!! + ) + else -> emptyMap() + } + } + + private fun makePathFromImport() { +// val relPath = node.importText + if (node.importText.endsWith(".ts")) "" else ".ts" +// // If import text doesn't contain "/", then it is NodeJS stdlib import. +// if (!relPath.contains("/")) return true +// val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)) + } + + private fun File.findEntityInFile(key: String): Node? { + val parsedFile = _parsedFilesCache.putIfAbsent( + path, + Compiler().parse(SourceFile.fromCode("jsFile", readText())) + )!! + val localScrapper = JsAstScrapper(parsedFile) + return localScrapper.findClass(key) + ?: localScrapper.findFunction(key) + + } + + + + private class Visitor: IAstVisitor { + + private val _importNodes = mutableListOf() + + val importNodes: List + get() = _importNodes.toList() + + override fun accept(rootNode: Node) { + NodeUtil.visitPreOrder(rootNode) { node -> + if (node.isImport || node.isRequireImport()) _importNodes += node + } + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 05a3676a83..4bf6aa8259 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -3,7 +3,9 @@ package parser import com.google.javascript.jscomp.Compiler import com.google.javascript.jscomp.SourceFile 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") @@ -142,4 +144,9 @@ object JsParserUtils { * Called upon node with Method token. */ fun Node.isStatic(): Boolean = this.isStaticMember + + /** + * Checks if node is "required" JavaScript import. + */ + fun Node.isRequireImport(): Boolean = this.isCall && this.firstChild?.string == "require" } From 6c64136c2c6e08f7e2addb92524676d49aed51a0 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Sat, 11 Feb 2023 12:06:59 +0300 Subject: [PATCH 27/74] Implemented AST scrapper for better imports handling --- .../src/main/kotlin/api/JsTestGenerator.kt | 2 +- .../src/main/kotlin/parser/JsAstScrapper.kt | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 2ca5460527..d680fcd21e 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -93,7 +93,7 @@ class JsTestGenerator( */ fun run(): String { parsedFile = runParser(fileText) - astScrapper = JsAstScrapper(parsedFile) + astScrapper = JsAstScrapper(parsedFile, sourceFilePath) val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index 9691fbc109..cbb6da4c02 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -5,11 +5,16 @@ import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.io.File +import java.nio.file.Paths import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getClassMethods import parser.JsParserUtils.isRequireImport +import kotlin.io.path.pathString -class JsAstScrapper(private val parsedFile: Node) { +class JsAstScrapper( + private val parsedFile: Node, + private val basePath: String, +) { // Used not to parse the same file multiple times. private val _parsedFilesCache = mutableMapOf() @@ -48,27 +53,32 @@ class JsAstScrapper(private val parsedFile: Node) { private fun Node.importedNodes(): Map { return when { this.isRequireImport() -> mapOf( - this.parent!!.string to this.firstChild!!.next!! + this.parent!!.string to (makePathFromImport(this.firstChild!!.next!!)?.let { + File(it).findEntityInFile() + // Workaround for std imports. + } ?: this.firstChild!!.next!!) ) else -> emptyMap() } } - private fun makePathFromImport() { -// val relPath = node.importText + if (node.importText.endsWith(".ts")) "" else ".ts" -// // If import text doesn't contain "/", then it is NodeJS stdlib import. -// if (!relPath.contains("/")) return true -// val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)) + private fun makePathFromImport(importNode: Node): String? { + val relPath = importNode.string + if (importNode.string.endsWith(".js")) "" else ".js" + // If import text doesn't contain "/", then it is NodeJS stdlib import. + if (!relPath.contains("/")) return null + val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString + return finalPath } - private fun File.findEntityInFile(key: String): Node? { + private fun File.findEntityInFile(): Node { val parsedFile = _parsedFilesCache.putIfAbsent( path, Compiler().parse(SourceFile.fromCode("jsFile", readText())) )!! - val localScrapper = JsAstScrapper(parsedFile) - return localScrapper.findClass(key) - ?: localScrapper.findFunction(key) +// val localScrapper = JsAstScrapper(parsedFile, basePath) +// return localScrapper.findClass(key) +// ?: localScrapper.findFunction(key) + return parsedFile } From bd413e1e049f735572c11c6f7cfdce7202a52115 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sun, 12 Feb 2023 19:16:54 +0300 Subject: [PATCH 28/74] Fixed wrong argument sequence in function call --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index d680fcd21e..d7f572dd58 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -348,7 +348,7 @@ class JsTestGenerator( } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { - return parentClassName?.let { astScrapper.findMethod(focusedMethodName, parentClassName) } + return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName) } ?: astScrapper.findFunction(focusedMethodName) ?: throw IllegalStateException( "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" From b0ced7525d0cfc4ff98d70bd01209d77a7fe390a Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 09:43:33 +0300 Subject: [PATCH 29/74] Implemented require import manager for JavaScript --- .../src/main/kotlin/parser/JsAstScrapper.kt | 22 +++-------- .../src/main/kotlin/parser/JsParserUtils.kt | 15 +++++++- .../kotlin/service/InstrumentationService.kt | 38 ++++++++++++++++--- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index cbb6da4c02..dec6e73f1a 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -8,6 +8,7 @@ import java.io.File import java.nio.file.Paths import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getRequireImportText import parser.JsParserUtils.isRequireImport import kotlin.io.path.pathString @@ -53,7 +54,7 @@ class JsAstScrapper( private fun Node.importedNodes(): Map { return when { this.isRequireImport() -> mapOf( - this.parent!!.string to (makePathFromImport(this.firstChild!!.next!!)?.let { + this.parent!!.string to (makePathFromImport(this.getRequireImportText())?.let { File(it).findEntityInFile() // Workaround for std imports. } ?: this.firstChild!!.next!!) @@ -62,28 +63,17 @@ class JsAstScrapper( } } - private fun makePathFromImport(importNode: Node): String? { - val relPath = importNode.string + if (importNode.string.endsWith(".js")) "" else ".js" + private fun makePathFromImport(importText: String): String? { + val relPath = importText + if (importText.endsWith(".js")) "" else ".js" // If import text doesn't contain "/", then it is NodeJS stdlib import. if (!relPath.contains("/")) return null - val finalPath = Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString - return finalPath + return Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString } private fun File.findEntityInFile(): Node { - val parsedFile = _parsedFilesCache.putIfAbsent( - path, - Compiler().parse(SourceFile.fromCode("jsFile", readText())) - )!! -// val localScrapper = JsAstScrapper(parsedFile, basePath) -// return localScrapper.findClass(key) -// ?: localScrapper.findFunction(key) - return parsedFile - + return Compiler().parse(SourceFile.fromCode("jsFile", readText())) } - - private class Visitor: IAstVisitor { private val _importNodes = mutableListOf() diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 4bf6aa8259..495fb7303c 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -146,7 +146,18 @@ object JsParserUtils { fun Node.isStatic(): Boolean = this.isStaticMember /** - * Checks if node is "required" JavaScript import. + * Checks if node is "require" JavaScript import. */ - fun Node.isRequireImport(): Boolean = this.isCall && this.firstChild?.string == "require" + fun Node.isRequireImport(): Boolean = try { + this.isCall && this.firstChild?.string == "require" + } catch (e: ClassCastException) { + false + } + + /** + * Called upon "require" JavaScript import. + * + * Returns path to imported file as [String]. + */ + fun Node.getRequireImportText(): String = this.firstChild!!.next!!.string } diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt index dc81a71f09..f613c1f635 100644 --- a/utbot-js/src/main/kotlin/service/InstrumentationService.kt +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -1,19 +1,26 @@ package service +import com.google.javascript.jscomp.CodePrinter import com.google.javascript.jscomp.NodeUtil import com.google.javascript.rhino.Node +import java.io.File +import java.nio.file.Paths import org.apache.commons.io.FileUtils import parser.JsFunctionAstVisitor import parser.JsParserUtils.getAnyValue +import parser.JsParserUtils.getRequireImportText +import parser.JsParserUtils.isRequireImport import parser.JsParserUtils.runParser import utils.JsCmdExec -import java.io.File +import utils.PathResolver.getRelativePath +import kotlin.io.path.pathString import kotlin.math.roundToInt class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair): ContextOwner by context { private val destinationFolderPath = "${projectPath}/${utbotDir}/instr" private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.substringAfterLast("/")}" + private lateinit var parsedInstrFile: Node lateinit var covFunName: String val allStatements: Set @@ -92,10 +99,8 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset } private fun getStatementMapKeys() = buildSet { - val fileText = File(instrumentedFilePath).readText() - val rootNode = runParser(fileText) val funcVisitor = JsFunctionAstVisitor(covFunName, null) - funcVisitor.accept(rootNode) + funcVisitor.accept(parsedInstrFile) val funcNode = funcVisitor.targetFunctionNode val funcLocation = getFuncLocation(funcNode) funcNode.findAndIterateOver("statementMap") { currKey -> @@ -134,15 +139,36 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset timeout = settings.timeout, ) val instrumentedFileText = File(instrumentedFilePath).readText() + parsedInstrFile = runParser(instrumentedFileText) val covFunRegex = Regex("function (cov_.*)\\(\\).*") val funName = covFunRegex.find(instrumentedFileText.takeWhile { it != '{' })?.groups?.get(1)?.value ?: throw IllegalStateException("") - val fixedFileText = "$instrumentedFileText\nexports.$funName = $funName" - File(instrumentedFilePath).writeText(fixedFileText) + val fixedFileText = fixImportsInInstrumentedFile() + "\nexports.$funName = $funName" + File(instrumentedFilePath).writeTextAndUpdate(fixedFileText) covFunName = funName } + private fun File.writeTextAndUpdate(newText: String) { + this.writeText(newText) + parsedInstrFile = runParser(File(instrumentedFilePath).readText()) + } + + private fun fixImportsInInstrumentedFile(): String { + // nyc poorly handles imports paths in file to instrument. Manual fix required. + NodeUtil.visitPreOrder(parsedInstrFile) { node -> + if (node.isRequireImport()) { + val currString = node.getRequireImportText() + val relPath = Paths.get(getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference).parent + )).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.string = relPath + } + } + return CodePrinter.Builder(parsedInstrFile).build() + } + fun removeTempFiles() { FileUtils.deleteDirectory(File("$projectPath/$utbotDir/instr")) } From 6534255f6684c264a000ed2750846c80a366f3fa Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 11:40:38 +0300 Subject: [PATCH 30/74] Implemented package.json parser --- .../src/main/kotlin/api/JsTestGenerator.kt | 5 ++- .../main/kotlin/service/PackageJsonService.kt | 38 +++++++++++++++++++ .../src/main/kotlin/service/ServiceContext.kt | 2 + 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 utbot-js/src/main/kotlin/service/PackageJsonService.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index d7f572dd58..ac70d7d57b 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -43,6 +43,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 @@ -101,7 +102,7 @@ class JsTestGenerator( parsedFile = parsedFile, settings = settings, ) - val ternService = TernService(context) + context.packageJson = PackageJsonService(context).findClosestConfig() val paramNames = mutableMapOf>() val testSets = mutableListOf() val classNode = @@ -111,7 +112,7 @@ class JsTestGenerator( strict = selectedMethods?.isNotEmpty() ?: false ) parentClassName = classNode?.getClassName() - val classId = makeJsClassId(classNode, ternService) + val classId = makeJsClassId(classNode, TernService(context)) val methods = makeMethodsToTest() if (methods.isEmpty()) throw IllegalArgumentException("No methods to test were found!") methods.forEach { funcNode -> 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..81acc21ab2 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -0,0 +1,38 @@ +package service + +import java.io.File +import java.io.FilenameFilter +import org.json.JSONObject + +data class PackageJson( + val isModule: Boolean +) { + companion object { + val defaultConfig = PackageJson(false) + } +} + + +class PackageJsonService(context: ServiceContext) : ContextOwner by context { + + 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.getString("type") == "module"), + ) + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/ServiceContext.kt b/utbot-js/src/main/kotlin/service/ServiceContext.kt index 387b567641..36fb0a0376 100644 --- a/utbot-js/src/main/kotlin/service/ServiceContext.kt +++ b/utbot-js/src/main/kotlin/service/ServiceContext.kt @@ -9,6 +9,7 @@ class ServiceContext( override val filePathToInference: String, override val parsedFile: Node, override val settings: JsDynamicSettings, + override var packageJson: PackageJson = PackageJson.defaultConfig ): ContextOwner interface ContextOwner { @@ -17,4 +18,5 @@ interface ContextOwner { val filePathToInference: String val parsedFile: Node val settings: JsDynamicSettings + var packageJson: PackageJson } \ No newline at end of file From a342a2244efcde67621a6bd871609a80cd97aae6 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 13:25:15 +0300 Subject: [PATCH 31/74] [WIP] Redesigned exports managers + better file division in utbot-js module --- .../utbot/cli/js/JsGenerateTestsCommand.kt | 22 ++++++------- .../plugin/language/js/JsDialogProcessor.kt | 20 ++++-------- .../plugin/language/js/JsTestsModel.kt | 2 +- .../src/main/kotlin/api/JsTestGenerator.kt | 31 +++++++++++++------ .../main/kotlin/service/PackageJsonService.kt | 5 ++- .../src/main/kotlin/service/TernService.kt | 2 +- .../{ => coverage}/BasicCoverageService.kt | 8 +++-- .../service/{ => coverage}/CoverageMode.kt | 2 +- .../service/{ => coverage}/CoverageService.kt | 13 +++++--- .../{ => coverage}/CoverageServiceProvider.kt | 12 ++++--- .../{ => coverage}/FastCoverageService.kt | 10 +++--- .../main/kotlin/settings/JsDynamicSettings.kt | 2 +- .../src/main/kotlin/utils/ExportsProvider.kt | 20 ++++++++++++ utbot-js/src/main/kotlin/utils/ValueUtil.kt | 5 +-- .../kotlin/utils/{ => data}/CoverageData.kt | 2 +- .../kotlin/utils/{ => data}/MethodTypes.kt | 2 +- .../kotlin/utils/{ => data}/ResultData.kt | 2 +- 17 files changed, 98 insertions(+), 62 deletions(-) rename utbot-js/src/main/kotlin/service/{ => coverage}/BasicCoverageService.kt (93%) rename utbot-js/src/main/kotlin/service/{ => coverage}/CoverageMode.kt (65%) rename utbot-js/src/main/kotlin/service/{ => coverage}/CoverageService.kt (91%) rename utbot-js/src/main/kotlin/service/{ => coverage}/CoverageServiceProvider.kt (97%) rename utbot-js/src/main/kotlin/service/{ => coverage}/FastCoverageService.kt (91%) create mode 100644 utbot-js/src/main/kotlin/utils/ExportsProvider.kt rename utbot-js/src/main/kotlin/utils/{ => data}/CoverageData.kt (77%) rename utbot-js/src/main/kotlin/utils/{ => data}/MethodTypes.kt (88%) rename utbot-js/src/main/kotlin/utils/{ => data}/ResultData.kt (97%) diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt index 110c2e121d..b93a3c15b9 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt @@ -6,7 +6,7 @@ import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.choice import mu.KotlinLogging import org.utbot.cli.js.JsUtils.makeAbsolutePath -import service.CoverageMode +import service.coverage.CoverageMode import settings.JsDynamicSettings import settings.JsExportsSettings.endComment import settings.JsExportsSettings.startComment @@ -88,7 +88,7 @@ class JsGenerateTestsCommand : sourceFilePath = sourceFileAbsolutePath, parentClassName = targetClass, outputFilePath = outputAbsolutePath, - exportsManager = ::manageExports, + exportsManager = partialApplication(::manageExports, fileText), settings = JsDynamicSettings( pathToNode = pathToNode, pathToNYC = pathToNYC, @@ -118,25 +118,17 @@ class JsGenerateTestsCommand : } } - private fun manageExports(exports: List) { + private fun manageExports(fileText: String, exports: List, swappedText: (String) -> String) { val exportSection = exports.joinToString("\n") { "exports.$it = $it" } val file = File(sourceCodeFile) - val fileText = file.readText() when { fileText.contains(exportSection) -> {} fileText.contains(startComment) && !fileText.contains(exportSection) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> - val exportRegex = Regex("exports[.](.*) =") - val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } - val existingExportsSet = existingExports.map { rawLine -> - exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() - }.toSet() - val resultSet = existingExportsSet + exports.toSet() - val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } - val swappedText = fileText.replace(existingSection, "\n$resSection\n") - file.writeText(swappedText) + val newText = swappedText(existingSection) + file.writeText(newText) } } @@ -150,4 +142,8 @@ class JsGenerateTestsCommand : } } } + + private fun partialApplication(f: (A, B, C) -> Unit, a: A): (B, C) -> Unit { + return { b: B, c: C -> f(a, b, c) } + } } \ No newline at end of file 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..7b768d4baa 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 @@ -176,7 +176,7 @@ object JsDialogProcessor { if (name == dummyClassName) null else name }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), - exportsManager = partialApplication(JsDialogProcessor::manageExports, editor, project), + exportsManager = partialApplication(JsDialogProcessor::manageExports, editor, project, editor.document.text), settings = JsDynamicSettings( pathToNode = model.pathToNode, pathToNYC = model.pathToNYC, @@ -208,33 +208,25 @@ object JsDialogProcessor { }).queue() } - private fun partialApplication(f: (A, B, C) -> Unit, a: A, b: B): (C) -> Unit { - return { c: C -> f(a, b, c) } + private fun partialApplication(f: (A, B, C, D, E) -> Unit, a: A, b: B, c: C): (D, E) -> Unit { + return { d: D, e: E -> f(a, b, c, d, e) } } - private fun manageExports(editor: Editor, project: Project, exports: List) { + private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String) -> String) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { val exportSection = exports.joinToString("\n") { "exports.$it = $it" } - val fileText = editor.document.text when { fileText.contains(exportSection) -> {} fileText.contains(startComment) && !fileText.contains(exportSection) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> - val exportRegex = Regex("exports[.](.*) =") - val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } - val existingExportsSet = existingExports.map { rawLine -> - exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() - }.toSet() - val resultSet = existingExportsSet + exports.toSet() - val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } - val swappedText = fileText.replace(existingSection, "\n$resSection\n") + val newText = swappedText(existingSection) runWriteAction { with(editor.document) { unblockDocument(project, this) - setText(swappedText) + setText(newText) unblockDocument(project, this) } with(FileDocumentManager.getInstance()) { diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt index fb869d835b..8c8508953d 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsTestsModel.kt @@ -6,7 +6,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import org.utbot.framework.codegen.domain.TestFramework import org.utbot.intellij.plugin.models.BaseTestsModel -import service.CoverageMode +import service.coverage.CoverageMode import settings.JsTestGenerationSettings.defaultTimeout class JsTestsModel( diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index ac70d7d57b..777679406f 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -14,6 +14,8 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing +import java.io.File +import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -43,19 +45,19 @@ import parser.JsToplevelFunctionAstVisitor import service.CoverageMode import service.CoverageServiceProvider import service.InstrumentationService +import service.PackageJson import service.PackageJsonService import service.ServiceContext import service.TernService +import service.coverage.CoverageMode +import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold -import settings.JsTestGenerationSettings.fuzzingTimeout +import utils.ExportsProvider.getExportsRegex import utils.PathResolver -import utils.ResultData import utils.constructClass +import utils.data.ResultData import utils.toJsAny -import java.io.File -import java.util.concurrent.CancellationException -import org.utbot.fuzzing.utils.Trie private val logger = KotlinLogging.logger {} @@ -66,7 +68,7 @@ class JsTestGenerator( private val selectedMethods: List? = null, private var parentClassName: String? = null, private var outputFilePath: String?, - private val exportsManager: (List) -> Unit, + private val exportsManager: (List, (String) -> String) -> Unit, private val settings: JsDynamicSettings, private val isCancelled: () -> Boolean = { false } ) { @@ -138,7 +140,7 @@ class JsTestGenerator( val execId = classId.allMethods.find { it.name == funcNode.getAbstractFunctionName() } ?: throw IllegalStateException() - manageExports(classNode, funcNode, execId) + manageExports(classNode, funcNode, execId, context.packageJson) val executionResults = mutableListOf() try { runBlockingWithCancellationPredicate(isCancelled) { @@ -302,12 +304,23 @@ class JsTestGenerator( private fun manageExports( classNode: Node?, funcNode: Node, - execId: JsMethodId + execId: JsMethodId, + packageJson: PackageJson ) { val obligatoryExport = (classNode?.getClassName() ?: funcNode.getAbstractFunctionName()).toString() val collectedExports = collectExports(execId) exports += (collectedExports + obligatoryExport) - exportsManager(exports.toList()) + exportsManager(exports.toList()) { existingSection -> + val exportRegex = getExportsRegex(packageJson) + val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } + val existingExportsSet = existingExports.map { rawLine -> + exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() + }.toSet() + val resultSet = existingExportsSet + exports.toSet() + val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } + val swappedText = fileText.replace(existingSection, "\n$resSection\n") + swappedText + } } private fun makeMethodsToTest(): List { diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index 81acc21ab2..61480edb4e 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -2,6 +2,7 @@ package service import java.io.File import java.io.FilenameFilter +import org.json.JSONException import org.json.JSONObject data class PackageJson( @@ -32,7 +33,9 @@ class PackageJsonService(context: ServiceContext) : ContextOwner by context { private fun parseConfig(configFile: File): PackageJson { val configAsJson = JSONObject(configFile.readText()) return PackageJson( - isModule = (configAsJson.getString("type") == "module"), + isModule = try { + (configAsJson.getString("type") == "module") + } catch (e: JSONException) { false }, ) } } \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 6e8f0271fe..6326f99f98 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -12,7 +12,7 @@ import parser.JsParserUtils.getAbstractFunctionParams import parser.JsParserUtils.getClassName import parser.JsParserUtils.getConstructor import utils.JsCmdExec -import utils.MethodTypes +import utils.data.MethodTypes import utils.constructClass import java.io.File import java.util.Locale diff --git a/utbot-js/src/main/kotlin/service/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt similarity index 93% rename from utbot-js/src/main/kotlin/service/BasicCoverageService.kt rename to utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index a5c5f9482a..08ba22015e 100644 --- a/utbot-js/src/main/kotlin/service/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -1,12 +1,14 @@ -package service +package service.coverage +import java.io.File import mu.KotlinLogging import org.json.JSONObject import org.utbot.framework.plugin.api.TimeoutException -import settings.JsTestGenerationSettings.tempFileName +import service.ServiceContext +import settings.JsTestGenerationSettings import utils.JsCmdExec -import utils.ResultData import java.io.File +import utils.data.ResultData private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/CoverageMode.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt similarity index 65% rename from utbot-js/src/main/kotlin/service/CoverageMode.kt rename to utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt index a1f0c0d9cc..5fee242ac0 100644 --- a/utbot-js/src/main/kotlin/service/CoverageMode.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt @@ -1,4 +1,4 @@ -package service +package service.coverage enum class CoverageMode { FAST, diff --git a/utbot-js/src/main/kotlin/service/CoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt similarity index 91% rename from utbot-js/src/main/kotlin/service/CoverageService.kt rename to utbot-js/src/main/kotlin/service/coverage/CoverageService.kt index a5bfd0425b..aca8f8ae03 100644 --- a/utbot-js/src/main/kotlin/service/CoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt @@ -1,13 +1,15 @@ -package service +package service.coverage import java.io.File import java.util.Collections import org.json.JSONException import org.json.JSONObject +import service.ContextOwner +import service.ServiceContext import settings.JsTestGenerationSettings -import utils.CoverageData import utils.JsCmdExec -import utils.ResultData +import utils.data.CoverageData +import utils.data.ResultData abstract class CoverageService( context: ServiceContext, @@ -39,7 +41,10 @@ abstract class CoverageService( scriptText = baseCoverageScriptText ) JsCmdExec.runCommand( - cmd = arrayOf("\"${settings.pathToNode}\"", "\"$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js\""), + cmd = arrayOf( + "\"${settings.pathToNode}\"", + "\"$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js\"" + ), dir = projectPath, shouldWait = true, timeout = settings.timeout, diff --git a/utbot-js/src/main/kotlin/service/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt similarity index 97% rename from utbot-js/src/main/kotlin/service/CoverageServiceProvider.kt rename to utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index aaa401025c..24d368aacc 100644 --- a/utbot-js/src/main/kotlin/service/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -1,19 +1,23 @@ -package service +package service.coverage import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription +import java.util.regex.Pattern import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic import org.utbot.fuzzer.FuzzedValue +import service.ContextOwner +import service.InstrumentationService +import service.ServiceContext import settings.JsTestGenerationSettings -import settings.JsTestGenerationSettings.tempFileName -import utils.CoverageData -import utils.ResultData +import utils.data.CoverageData +import utils.data.ResultData import java.util.regex.Pattern + // TODO: Add "error" field in result json to not collide with "result" field upon error. class CoverageServiceProvider( private val context: ServiceContext, diff --git a/utbot-js/src/main/kotlin/service/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt similarity index 91% rename from utbot-js/src/main/kotlin/service/FastCoverageService.kt rename to utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index c6f762bd25..5e99b8c601 100644 --- a/utbot-js/src/main/kotlin/service/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -1,12 +1,12 @@ -package service +package service.coverage +import java.io.File import mu.KotlinLogging import org.json.JSONObject -import settings.JsTestGenerationSettings.fuzzingThreshold -import settings.JsTestGenerationSettings.tempFileName +import service.ServiceContext +import settings.JsTestGenerationSettings import utils.JsCmdExec -import utils.ResultData -import java.io.File +import utils.data.ResultData private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt b/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt index 81d1ac02a0..c985b2454f 100644 --- a/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt +++ b/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt @@ -1,6 +1,6 @@ package settings -import service.CoverageMode +import service.coverage.CoverageMode data class JsDynamicSettings( val pathToNode: String = "node", diff --git a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt new file mode 100644 index 0000000000..a100296eeb --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt @@ -0,0 +1,20 @@ +package utils + +import service.PackageJson + +object ExportsProvider { + + fun getExportsRegex(packageJson: PackageJson): Regex = with(packageJson) { + when (isModule) { + true -> Regex("") + false -> Regex("exports[.](.*) =") + } + } + + fun getExportsDelimiter(packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> "," + false -> "\n" + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/utils/ValueUtil.kt b/utbot-js/src/main/kotlin/utils/ValueUtil.kt index 2349a7a674..50862cd4b4 100644 --- a/utbot-js/src/main/kotlin/utils/ValueUtil.kt +++ b/utbot-js/src/main/kotlin/utils/ValueUtil.kt @@ -1,5 +1,7 @@ package utils +import org.json.JSONException +import org.json.JSONObject import framework.api.js.JsClassId import framework.api.js.util.jsBooleanClassId import framework.api.js.util.jsDoubleClassId @@ -7,8 +9,7 @@ import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsNumberClassId import framework.api.js.util.jsStringClassId import framework.api.js.util.jsUndefinedClassId -import org.json.JSONException -import org.json.JSONObject +import utils.data.ResultData fun ResultData.toJsAny(returnType: JsClassId = jsUndefinedClassId): Pair { this.buildUniqueValue()?.let { return it } diff --git a/utbot-js/src/main/kotlin/utils/CoverageData.kt b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt similarity index 77% rename from utbot-js/src/main/kotlin/utils/CoverageData.kt rename to utbot-js/src/main/kotlin/utils/data/CoverageData.kt index 6dc2538f73..2237243745 100644 --- a/utbot-js/src/main/kotlin/utils/CoverageData.kt +++ b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt @@ -1,4 +1,4 @@ -package utils +package utils.data data class CoverageData( val additionalCoverage: Set diff --git a/utbot-js/src/main/kotlin/utils/MethodTypes.kt b/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt similarity index 88% rename from utbot-js/src/main/kotlin/utils/MethodTypes.kt rename to utbot-js/src/main/kotlin/utils/data/MethodTypes.kt index 342acdf508..cea1c403ee 100644 --- a/utbot-js/src/main/kotlin/utils/MethodTypes.kt +++ b/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt @@ -1,4 +1,4 @@ -package utils +package utils.data import framework.api.js.JsClassId diff --git a/utbot-js/src/main/kotlin/utils/ResultData.kt b/utbot-js/src/main/kotlin/utils/data/ResultData.kt similarity index 97% rename from utbot-js/src/main/kotlin/utils/ResultData.kt rename to utbot-js/src/main/kotlin/utils/data/ResultData.kt index fe4a274175..ed8e6ef0e1 100644 --- a/utbot-js/src/main/kotlin/utils/ResultData.kt +++ b/utbot-js/src/main/kotlin/utils/data/ResultData.kt @@ -1,4 +1,4 @@ -package utils +package utils.data /** * Represents results after running function with arguments using Node.js From 7d9a6f6b050ddf92f57af78034b8de15ff888b5b Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 13 Feb 2023 17:38:04 +0300 Subject: [PATCH 32/74] Implemented JavaScript exports manager based on package.json --- .../utbot/cli/js/JsGenerateTestsCommand.kt | 12 ++++---- .../plugin/language/js/JsDialogProcessor.kt | 13 ++++---- .../src/main/kotlin/api/JsTestGenerator.kt | 30 +++++++++++++------ .../src/main/kotlin/utils/ExportsProvider.kt | 23 +++++++++++++- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt index b93a3c15b9..3eec14facd 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt @@ -118,13 +118,11 @@ class JsGenerateTestsCommand : } } - private fun manageExports(fileText: String, exports: List, swappedText: (String) -> String) { - val exportSection = exports.joinToString("\n") { "exports.$it = $it" } + private fun manageExports(fileText: String, exports: List, swappedText: (String?) -> String) { val file = File(sourceCodeFile) when { - fileText.contains(exportSection) -> {} - fileText.contains(startComment) && !fileText.contains(exportSection) -> { + fileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> val newText = swappedText(existingSection) @@ -134,9 +132,9 @@ class JsGenerateTestsCommand : else -> { val line = buildString { - append("\n$startComment\n") - append(exportSection) - append("\n$endComment") + append("\n$startComment") + append(swappedText(null)) + append(endComment) } file.appendText(line) } 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 7b768d4baa..82041d5ab8 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 @@ -212,14 +212,11 @@ object JsDialogProcessor { return { d: D, e: E -> f(a, b, c, d, e) } } - private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String) -> String) { + private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { - val exportSection = exports.joinToString("\n") { "exports.$it = $it" } when { - fileText.contains(exportSection) -> {} - - fileText.contains(startComment) && !fileText.contains(exportSection) -> { + fileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> val newText = swappedText(existingSection) @@ -238,9 +235,9 @@ object JsDialogProcessor { else -> { val line = buildString { - append("\n$startComment\n") - append(exportSection) - append("\n$endComment") + append("\n$startComment") + append(swappedText(null)) + append(endComment) } runWriteAction { with(editor.document) { diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 777679406f..4203c3218c 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -53,6 +53,10 @@ import service.coverage.CoverageMode import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold +import utils.ExportsProvider.getExportsDelimiter +import utils.ExportsProvider.getExportsFrame +import utils.ExportsProvider.getExportsPostfix +import utils.ExportsProvider.getExportsPrefix import utils.ExportsProvider.getExportsRegex import utils.PathResolver import utils.constructClass @@ -68,7 +72,7 @@ class JsTestGenerator( private val selectedMethods: List? = null, private var parentClassName: String? = null, private var outputFilePath: String?, - private val exportsManager: (List, (String) -> String) -> Unit, + private val exportsManager: (List, (String?) -> String) -> Unit, private val settings: JsDynamicSettings, private val isCancelled: () -> Boolean = { false } ) { @@ -311,15 +315,23 @@ class JsTestGenerator( val collectedExports = collectExports(execId) exports += (collectedExports + obligatoryExport) exportsManager(exports.toList()) { existingSection -> - val exportRegex = getExportsRegex(packageJson) - val existingExports = existingSection.split("\n").filter { it.contains(exportRegex) } - val existingExportsSet = existingExports.map { rawLine -> - exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() - }.toSet() + val existingExportsSet = existingSection?.let { section -> + val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)).substringBeforeLast(getExportsPostfix(packageJson)) + val exportRegex = getExportsRegex(packageJson) + val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)).filter { it.contains(exportRegex) && it.isNotBlank() } + existingExports.map { rawLine -> + exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() + }.toSet() + } ?: emptySet() val resultSet = existingExportsSet + exports.toSet() - val resSection = resultSet.joinToString("\n") { "exports.$it = $it" } - val swappedText = fileText.replace(existingSection, "\n$resSection\n") - swappedText + val resSection = resultSet.joinToString( + separator = getExportsDelimiter(packageJson), + prefix = getExportsPrefix(packageJson), + postfix = getExportsPostfix(packageJson), + ) { + getExportsFrame(it, packageJson) + } + existingSection?.let { fileText.replace(existingSection, resSection) } ?: resSection } } diff --git a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt index a100296eeb..49fea8da23 100644 --- a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt +++ b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt @@ -6,7 +6,7 @@ object ExportsProvider { fun getExportsRegex(packageJson: PackageJson): Regex = with(packageJson) { when (isModule) { - true -> Regex("") + true -> Regex("(.*)") false -> Regex("exports[.](.*) =") } } @@ -17,4 +17,25 @@ object ExportsProvider { false -> "\n" } } + + fun getExportsFrame(exportString: String, packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> exportString + false -> "exports.$exportString = $exportString" + } + } + + fun getExportsPrefix(packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> "\nexport {" + false -> "\n" + } + } + + fun getExportsPostfix(packageJson: PackageJson): String = with(packageJson) { + when(isModule) { + true -> "}\n" + false -> "\n" + } + } } \ No newline at end of file From b58c9ee62fb1c3f2a8ea41820a33bfee8e701abf Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Tue, 14 Feb 2023 16:26:59 +0300 Subject: [PATCH 33/74] Fix phantom !0 in tern --- utbot-js/src/main/kotlin/service/TernService.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 6326f99f98..e403cba68c 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -12,8 +12,8 @@ import parser.JsParserUtils.getAbstractFunctionParams import parser.JsParserUtils.getClassName import parser.JsParserUtils.getConstructor import utils.JsCmdExec -import utils.data.MethodTypes import utils.constructClass +import utils.data.MethodTypes import java.io.File import java.util.Locale @@ -80,7 +80,6 @@ test("$filePathToInference") private fun installDeps(path: String) { JsCmdExec.runCommand( dir = path, - shouldWait = true, cmd = arrayOf("\"${settings.pathToNPM}\"", "i", "tern", "-l"), ) } @@ -92,14 +91,15 @@ test("$filePathToInference") } private fun runTypeInferencer() { - val (inputText, _) = JsCmdExec.runCommand( + val (reader, _) = JsCmdExec.runCommand( dir = "$projectPath/$utbotDir/", shouldWait = true, timeout = 20, cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""), ) + val text = reader.readText().replaceAfterLast("}", "") json = try { - JSONObject(inputText.replaceAfterLast("}", "")) + JSONObject(text) } catch (_: Throwable) { JSONObject() } @@ -160,7 +160,7 @@ test("$filePathToInference") } val methodJson = scope.getJSONObject(funcNode.getAbstractFunctionName()) val typesString = methodJson.getString("!type") - .filterNot { setOf(' ', '+', '!').contains(it) } + .filterNot { setOf(' ', '+').contains(it) } val parametersList = lazy { extractParameters(typesString) } val returnType = lazy { extractReturnType(typesString) } @@ -173,11 +173,9 @@ test("$filePathToInference") } } - //TODO MINOR: move to appropriate place (JsIdUtil or JsClassId constructor) private fun makeClassId(name: String): JsClassId { val classId = when { - // TODO SEVERE: I don't know why Tern sometimes says that type is "0" - name == "?" || name.toIntOrNull() != null -> jsUndefinedClassId + name == "?" || name.toIntOrNull() != null || name.contains('!') -> jsUndefinedClassId Regex("\\[(.*)]").matches(name) -> { val arrType = Regex("\\[(.*)]").find(name)?.groups?.get(1)?.value ?: throw IllegalStateException() JsClassId( From a08aaa61086b70f5e5886e74014cb1538ff064f6 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 13 Jan 2023 12:20:43 +0300 Subject: [PATCH 34/74] Switch to IU for this branch --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 56447555bc..326f008335 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ kotlin.code.style=official # IU, IC, PC, PY # IC for AndroidStudio -ideType=IC +ideType=IU # ALL, NOJS buildType=NOJS ideVersion=222.4167.29 From 203a1990dbd47c2da7f45977c54366be18cc6280 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 17 Feb 2023 09:53:23 +0300 Subject: [PATCH 35/74] After VCS fix --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 4 ++-- utbot-js/src/main/kotlin/service/TernService.kt | 2 +- .../src/main/kotlin/service/coverage/BasicCoverageService.kt | 2 -- .../main/kotlin/service/coverage/CoverageServiceProvider.kt | 1 - .../src/main/kotlin/service/coverage/FastCoverageService.kt | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 4203c3218c..fbfd8d3af1 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -32,6 +32,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.JsAstScrapper import parser.JsFuzzerAstVisitor import parser.JsParserUtils @@ -42,8 +43,6 @@ import parser.JsParserUtils.getClassName import parser.JsParserUtils.getParamName import parser.JsParserUtils.runParser import parser.JsToplevelFunctionAstVisitor -import service.CoverageMode -import service.CoverageServiceProvider import service.InstrumentationService import service.PackageJson import service.PackageJsonService @@ -53,6 +52,7 @@ import service.coverage.CoverageMode import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold +import settings.JsTestGenerationSettings.fuzzingTimeout import utils.ExportsProvider.getExportsDelimiter import utils.ExportsProvider.getExportsFrame import utils.ExportsProvider.getExportsPostfix diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index e403cba68c..d393f67dcd 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -109,7 +109,7 @@ test("$filePathToInference") return try { val classJson = json.getJSONObject(classNode.getClassName()) val constructorFunc = classJson.getString("!type") - .filterNot { setOf(' ', '+', '!').contains(it) } + .filterNot { setOf(' ', '+').contains(it) } extractParameters(constructorFunc) } catch (e: JSONException) { classNode.getConstructor()?.getAbstractFunctionParams()?.map { jsUndefinedClassId } ?: emptyList() diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index 08ba22015e..6a4c528edc 100644 --- a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -5,9 +5,7 @@ import mu.KotlinLogging import org.json.JSONObject import org.utbot.framework.plugin.api.TimeoutException import service.ServiceContext -import settings.JsTestGenerationSettings import utils.JsCmdExec -import java.io.File import utils.data.ResultData private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 24d368aacc..20719e9e57 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -15,7 +15,6 @@ import service.ServiceContext import settings.JsTestGenerationSettings import utils.data.CoverageData import utils.data.ResultData -import java.util.regex.Pattern // TODO: Add "error" field in result json to not collide with "result" field upon error. diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index 5e99b8c601..517676d108 100644 --- a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -4,7 +4,6 @@ import java.io.File import mu.KotlinLogging import org.json.JSONObject import service.ServiceContext -import settings.JsTestGenerationSettings import utils.JsCmdExec import utils.data.ResultData From db20b04d4e96392052f2023063b898007c871839 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:15:48 +0300 Subject: [PATCH 36/74] Fix after merge --- utbot-js/src/main/kotlin/service/TernService.kt | 5 ++--- .../main/kotlin/service/coverage/BasicCoverageService.kt | 3 ++- .../kotlin/service/coverage/CoverageServiceProvider.kt | 9 +++++---- .../main/kotlin/service/coverage/FastCoverageService.kt | 4 +++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index d393f67dcd..a376280d25 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -91,15 +91,14 @@ test("$filePathToInference") } private fun runTypeInferencer() { - val (reader, _) = JsCmdExec.runCommand( + val (inputText, _) = JsCmdExec.runCommand( dir = "$projectPath/$utbotDir/", shouldWait = true, timeout = 20, cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""), ) - val text = reader.readText().replaceAfterLast("}", "") json = try { - JSONObject(text) + JSONObject(inputText.replaceAfterLast("}", "")) } catch (_: Throwable) { JSONObject() } diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index 6a4c528edc..c777b799ce 100644 --- a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -1,12 +1,13 @@ package service.coverage -import java.io.File import mu.KotlinLogging import org.json.JSONObject import org.utbot.framework.plugin.api.TimeoutException import service.ServiceContext +import settings.JsTestGenerationSettings.tempFileName import utils.JsCmdExec import utils.data.ResultData +import java.io.File private val logger = KotlinLogging.logger {} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 20719e9e57..99da262768 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -4,7 +4,6 @@ import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription -import java.util.regex.Pattern import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic @@ -13,8 +12,10 @@ import service.ContextOwner import service.InstrumentationService import service.ServiceContext import settings.JsTestGenerationSettings +import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData +import java.util.regex.Pattern // TODO: Add "error" field in result json to not collide with "result" field upon error. @@ -173,11 +174,11 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) method: JsMethodId, containingClass: String? ): String { - val actualParams = description.thisInstance?.let{ fuzzedValue.drop(1) } ?: fuzzedValue + val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue val initClass = containingClass?.let { if (!method.isStatic) { - description.thisInstance?.let { fuzzedValue[0].model.toCallString() } ?: - "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" + description.thisInstance?.let { fuzzedValue[0].model.toCallString() } + ?: "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" } else "${JsTestGenerationSettings.fileUnderTestAliases}.$it" } ?: JsTestGenerationSettings.fileUnderTestAliases var callString = "$initClass.${method.name}" diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index 517676d108..94bb56b8a6 100644 --- a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -1,11 +1,13 @@ package service.coverage -import java.io.File import mu.KotlinLogging import org.json.JSONObject import service.ServiceContext +import settings.JsTestGenerationSettings.fuzzingThreshold +import settings.JsTestGenerationSettings.tempFileName import utils.JsCmdExec import utils.data.ResultData +import java.io.File private val logger = KotlinLogging.logger {} From c9ca0f771a9e045d31124b619c9460e8ddae5107 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:28:09 +0300 Subject: [PATCH 37/74] Fix text annotation for generated tests --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index fbfd8d3af1..7bce83391b 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -14,8 +14,6 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing -import java.io.File -import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -62,6 +60,8 @@ import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny +import java.io.File +import java.util.concurrent.CancellationException private val logger = KotlinLogging.logger {} @@ -261,7 +261,7 @@ class JsTestGenerator( getUtModelResult( execId = execId, resultData = resultData, - params + jsDescription.thisInstance?.let { params.drop(1) } ?: params ) if (result is UtTimeoutException) { emit(JsTimeoutExecution(result)) @@ -316,9 +316,11 @@ class JsTestGenerator( exports += (collectedExports + obligatoryExport) exportsManager(exports.toList()) { existingSection -> val existingExportsSet = existingSection?.let { section -> - val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)).substringBeforeLast(getExportsPostfix(packageJson)) + val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)) + .substringBeforeLast(getExportsPostfix(packageJson)) val exportRegex = getExportsRegex(packageJson) - val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)).filter { it.contains(exportRegex) && it.isNotBlank() } + val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)) + .filter { it.contains(exportRegex) && it.isNotBlank() } existingExports.map { rawLine -> exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() }.toSet() From 321e8649d856116b0cfc9d0ed627369c841ae140 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:56:16 +0300 Subject: [PATCH 38/74] Implement support for multiple files for tern --- .../src/main/kotlin/api/JsTestGenerator.kt | 2 +- .../kotlin/service/InstrumentationService.kt | 25 +++++++++++-------- .../main/kotlin/service/PackageJsonService.kt | 12 +++++---- .../src/main/kotlin/service/ServiceContext.kt | 8 +++--- .../src/main/kotlin/service/TernService.kt | 3 ++- .../coverage/CoverageServiceProvider.kt | 2 +- .../main/kotlin/utils/JsClassConstructors.kt | 4 +-- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 7bce83391b..87151165b9 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -104,7 +104,7 @@ class JsTestGenerator( val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, - filePathToInference = sourceFilePath, + filePathToInference = listOf(sourceFilePath), parsedFile = parsedFile, settings = settings, ) diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt index f613c1f635..095dec1130 100644 --- a/utbot-js/src/main/kotlin/service/InstrumentationService.kt +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -3,8 +3,6 @@ package service import com.google.javascript.jscomp.CodePrinter import com.google.javascript.jscomp.NodeUtil import com.google.javascript.rhino.Node -import java.io.File -import java.nio.file.Paths import org.apache.commons.io.FileUtils import parser.JsFunctionAstVisitor import parser.JsParserUtils.getAnyValue @@ -13,13 +11,16 @@ import parser.JsParserUtils.isRequireImport import parser.JsParserUtils.runParser import utils.JsCmdExec import utils.PathResolver.getRelativePath +import java.io.File +import java.nio.file.Paths import kotlin.io.path.pathString import kotlin.math.roundToInt -class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair): ContextOwner by context { +class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair) : + ContextOwner by context { private val destinationFolderPath = "${projectPath}/${utbotDir}/instr" - private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.substringAfterLast("/")}" + private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.first().substringAfterLast("/")}" private lateinit var parsedInstrFile: Node lateinit var covFunName: String @@ -122,7 +123,7 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset covFuncNode.findAndIterateOver("fnMap") { currKey -> val declLocation = currKey!!.getObjectLocation("decl") if (funcDeclOffset == declLocation.start) { - result = currKey.getObjectLocation("loc") + result = currKey.getObjectLocation("loc") return@findAndIterateOver } } @@ -130,11 +131,11 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset } fun instrument() { - val fileName = filePathToInference.substringAfterLast("/") + val fileName = filePathToInference.first().substringAfterLast("/") JsCmdExec.runCommand( cmd = arrayOf(settings.pathToNYC, "instrument", fileName, destinationFolderPath), - dir = filePathToInference.substringBeforeLast("/"), + dir = filePathToInference.first().substringBeforeLast("/"), shouldWait = true, timeout = settings.timeout, ) @@ -159,10 +160,12 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset NodeUtil.visitPreOrder(parsedInstrFile) { node -> if (node.isRequireImport()) { val currString = node.getRequireImportText() - val relPath = Paths.get(getRelativePath( - "${projectPath}/${utbotDir}/instr", - File(filePathToInference).parent - )).resolve(currString).pathString.replace("\\", "/") + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") node.firstChild!!.next!!.string = relPath } } diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index 61480edb4e..354aa9869a 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -1,9 +1,9 @@ package service -import java.io.File -import java.io.FilenameFilter import org.json.JSONException import org.json.JSONObject +import java.io.File +import java.io.FilenameFilter data class PackageJson( val isModule: Boolean @@ -17,7 +17,7 @@ data class PackageJson( class PackageJsonService(context: ServiceContext) : ContextOwner by context { fun findClosestConfig(): PackageJson { - var currDir = File(filePathToInference.substringBeforeLast("/")) + var currDir = File(filePathToInference.first().substringBeforeLast("/")) do { val matchingFiles: Array = currDir.listFiles( FilenameFilter { _, name -> @@ -35,7 +35,9 @@ class PackageJsonService(context: ServiceContext) : ContextOwner by context { return PackageJson( isModule = try { (configAsJson.getString("type") == "module") - } catch (e: JSONException) { false }, + } catch (e: JSONException) { + false + }, ) } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/ServiceContext.kt b/utbot-js/src/main/kotlin/service/ServiceContext.kt index 36fb0a0376..d30ccf1fea 100644 --- a/utbot-js/src/main/kotlin/service/ServiceContext.kt +++ b/utbot-js/src/main/kotlin/service/ServiceContext.kt @@ -6,17 +6,17 @@ import settings.JsDynamicSettings class ServiceContext( override val utbotDir: String, override val projectPath: String, - override val filePathToInference: String, + override val filePathToInference: List, override val parsedFile: Node, override val settings: JsDynamicSettings, override var packageJson: PackageJson = PackageJson.defaultConfig -): ContextOwner +) : ContextOwner interface ContextOwner { val utbotDir: String val projectPath: String - val filePathToInference: String + val filePathToInference: List val parsedFile: Node val settings: JsDynamicSettings var packageJson: PackageJson -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index a376280d25..a17e83e473 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -64,7 +64,7 @@ function test(options) { runTest(options); } -test("$filePathToInference") +test("${filePathToInference.joinToString(separator = " ")}") """ init { @@ -80,6 +80,7 @@ test("$filePathToInference") private fun installDeps(path: String) { JsCmdExec.runCommand( dir = path, + shouldWait = true, cmd = arrayOf("\"${settings.pathToNPM}\"", "i", "tern", "-l"), ) } diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 99da262768..df4f3b9e45 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -26,7 +26,7 @@ class CoverageServiceProvider( private val description: JsMethodDescription ) : ContextOwner by context { - private val importFileUnderTest = "instr/${filePathToInference.substringAfterLast("/")}" + private val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" private val imports = "const ${JsTestGenerationSettings.fileUnderTestAliases} = require(\"./$importFileUnderTest\")\n" + diff --git a/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt b/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt index bace7de39b..54149f9202 100644 --- a/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt +++ b/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt @@ -30,7 +30,7 @@ fun JsClassId.constructClass( methods = methods, constructor = constructor, classPackagePath = ternService.projectPath, - classFilePath = ternService.filePathToInference, + classFilePath = ternService.filePathToInference.first(), ) methods.forEach { it.classId = newClassId @@ -73,4 +73,4 @@ private fun JsClassId.constructMethods( }.asSequence() return methods } -} \ No newline at end of file +} From 5d0c761e0a4f4090ad97277cac77fe2341af7843 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Sat, 18 Feb 2023 11:52:35 +0300 Subject: [PATCH 39/74] [WIP] Module imports for JavaScript --- .../src/main/kotlin/api/JsTestGenerator.kt | 6 +- .../src/main/kotlin/parser/JsAstScrapper.kt | 67 ++++++++++++++++--- .../src/main/kotlin/parser/JsParserUtils.kt | 41 ++++++++++++ 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 87151165b9..7e1032a508 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -376,8 +376,8 @@ class JsTestGenerator( } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { - return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName) } - ?: astScrapper.findFunction(focusedMethodName) + return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName, parsedFile) } + ?: astScrapper.findFunction(focusedMethodName, parsedFile) ?: throw IllegalStateException( "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" ) @@ -391,7 +391,7 @@ class JsTestGenerator( } private fun getClassMethods(className: String): List { - val classNode = astScrapper.findClass(className) + val classNode = astScrapper.findClass(className, parsedFile) return classNode?.getClassMethods() ?: throw IllegalStateException("Can't extract methods of class $className") } } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index dec6e73f1a..ce3f6d80f9 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -6,12 +6,19 @@ import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.io.File import java.nio.file.Paths +import mu.KotlinLogging import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getImportSpecAliases +import parser.JsParserUtils.getImportSpecName +import parser.JsParserUtils.getModuleImportSpecsAsList +import parser.JsParserUtils.getModuleImportText import parser.JsParserUtils.getRequireImportText import parser.JsParserUtils.isRequireImport import kotlin.io.path.pathString +private val logger = KotlinLogging.logger {} + class JsAstScrapper( private val parsedFile: Node, private val basePath: String, @@ -20,26 +27,26 @@ class JsAstScrapper( // Used not to parse the same file multiple times. private val _parsedFilesCache = mutableMapOf() - fun findFunction(key: String): Node? { + fun findFunction(key: String, file: Node): Node? { if (importsMap[key]?.isFunction == true) return importsMap[key] val functionVisitor = JsFunctionAstVisitor(key, null) - functionVisitor.accept(parsedFile) + functionVisitor.accept(file) return try { functionVisitor.targetFunctionNode - } catch(e: Exception) { null } + } catch (e: Exception) { null } } - fun findClass(key: String): Node? { + fun findClass(key: String, file: Node): Node? { if (importsMap[key]?.isClass == true) return importsMap[key] val classVisitor = JsClassAstVisitor(key) - classVisitor.accept(parsedFile) + classVisitor.accept(file) return try { classVisitor.targetClassNode } catch (e: Exception) { null } } - fun findMethod(classKey: String, methodKey: String): Node? { - val classNode = findClass(classKey) + fun findMethod(classKey: String, methodKey: String, file: Node): Node? { + val classNode = findClass(classKey, file) return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } } @@ -51,18 +58,52 @@ class JsAstScrapper( } } + private fun File.parseIfNecessary(): Node = + _parsedFilesCache.getOrPut(this.path) { + Compiler().parse(SourceFile.fromCode(this.path, readText())) + } + private fun Node.importedNodes(): Map { return when { this.isRequireImport() -> mapOf( this.parent!!.string to (makePathFromImport(this.getRequireImportText())?.let { - File(it).findEntityInFile() + File(it).parseIfNecessary().findEntityInFile(null) // Workaround for std imports. } ?: this.firstChild!!.next!!) ) + this.isImport -> this.processModuleImport() else -> emptyMap() } } + private fun Node.processModuleImport(): Map { + try { + val pathToFile = makePathFromImport(this.getModuleImportText()) ?: return emptyMap() + val pFile = File(pathToFile).parseIfNecessary() + return when { + NodeUtil.findPreorder(this, { it.isImportSpecs }, { true }) != null -> { + this.getModuleImportSpecsAsList().associate { spec -> + val realName = spec.getImportSpecName() + val aliases = spec.getImportSpecAliases() + aliases to pFile.findEntityInFile(realName) + } + } + NodeUtil.findPreorder(this, { it.isImportStar }, { true }) != null -> { + val aliases = this.getImportSpecAliases() + mapOf(aliases to pFile) + } + // For example: import foo from "bar" + else -> { + val realName = this.getImportSpecName() + mapOf(realName to pFile.findEntityInFile(realName)) + } + } + } catch (e: Exception) { + logger.error { e.toString() } + return emptyMap() + } + } + private fun makePathFromImport(importText: String): String? { val relPath = importText + if (importText.endsWith(".js")) "" else ".js" // If import text doesn't contain "/", then it is NodeJS stdlib import. @@ -70,8 +111,12 @@ class JsAstScrapper( return Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString } - private fun File.findEntityInFile(): Node { - return Compiler().parse(SourceFile.fromCode("jsFile", readText())) + private fun Node.findEntityInFile(key: String?): Node { + return key?.let { k -> + findClass(k, this) + ?: findFunction(k, this) + ?: throw ClassNotFoundException("Could not locate entity $k in ${this.sourceFileName}") + } ?: this } private class Visitor: IAstVisitor { @@ -87,4 +132,4 @@ class JsAstScrapper( } } } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 495fb7303c..e3c1a97340 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -1,12 +1,14 @@ package parser import com.google.javascript.jscomp.Compiler +import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.lang.IllegalStateException import org.utbot.fuzzer.FuzzedContext import parser.JsParserUtils.getMethodName +// TODO: make methods more safe by checking the Node method is called on. // Used for .children() calls. @Suppress("DEPRECATION") object JsParserUtils { @@ -160,4 +162,43 @@ object JsParserUtils { * Returns path to imported file as [String]. */ fun Node.getRequireImportText(): String = this.firstChild!!.next!!.string + + /** + * Called upon "import" JavaScript import. + * + * Returns path to imported file as [String]. + */ + fun Node.getModuleImportText(): String = this.firstChild!!.next!!.next!!.string + + /** + * Called upon "import" JavaScript import. + * + * Returns imported objects as [List]. + */ + fun Node.getModuleImportSpecsAsList(): List { + val importSpecsNode = NodeUtil.findPreorder(this, {it.isImportSpecs}, {true}) + ?: throw UnsupportedOperationException("Module import doesn't contain \"import_specs\" token as an AST child") + var currNode: Node? = importSpecsNode.firstChild!! + val importSpecsList = mutableListOf() + do { + importSpecsList += currNode!! + currNode = currNode?.next + } while (currNode?.isImportSpec == true) + return importSpecsList + } + + /** + * Called upon IMPORT_SPEC Node. + * + * Returns name of imported object as [String]. + */ + fun Node.getImportSpecName(): String = this.firstChild!!.string + + /** + * Called upon IMPORT_SPEC Node. + * + * Returns import alias as [String]. + */ + fun Node.getImportSpecAliases(): String = this.firstChild!!.next!!.string + } From a2950f2ea38f6e3814f6d14b294b03dd22bb0f18 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:16:58 +0300 Subject: [PATCH 40/74] Implemented import/export providers (except generated test files) --- .../src/main/kotlin/api/JsTestGenerator.kt | 29 ++++++------- .../src/main/kotlin/parser/JsAstScrapper.kt | 4 ++ .../providers/exports/IExportsProvider.kt | 25 +++++++++++ .../exports/ModuleExportsProvider.kt | 16 ++++++++ .../exports/RequireExportsProvider.kt | 16 ++++++++ .../providers/imports/IImportsProvider.kt | 18 ++++++++ .../imports/ModuleImportsProvider.kt | 22 ++++++++++ .../imports/RequireImportsProvider.kt | 23 +++++++++++ .../kotlin/service/InstrumentationService.kt | 35 +++++++++++----- .../src/main/kotlin/service/TernService.kt | 20 ++++----- .../coverage/CoverageServiceProvider.kt | 9 +--- .../src/main/kotlin/utils/ExportsProvider.kt | 41 ------------------- 12 files changed, 171 insertions(+), 87 deletions(-) create mode 100644 utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt create mode 100644 utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt delete mode 100644 utbot-js/src/main/kotlin/utils/ExportsProvider.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 7e1032a508..92ff042243 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -14,6 +14,8 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing +import java.io.File +import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -41,6 +43,7 @@ import parser.JsParserUtils.getClassName import parser.JsParserUtils.getParamName import parser.JsParserUtils.runParser import parser.JsToplevelFunctionAstVisitor +import providers.exports.IExportsProvider import service.InstrumentationService import service.PackageJson import service.PackageJsonService @@ -51,17 +54,10 @@ import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings import settings.JsTestGenerationSettings.fuzzingThreshold import settings.JsTestGenerationSettings.fuzzingTimeout -import utils.ExportsProvider.getExportsDelimiter -import utils.ExportsProvider.getExportsFrame -import utils.ExportsProvider.getExportsPostfix -import utils.ExportsProvider.getExportsPrefix -import utils.ExportsProvider.getExportsRegex import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny -import java.io.File -import java.util.concurrent.CancellationException private val logger = KotlinLogging.logger {} @@ -104,7 +100,7 @@ class JsTestGenerator( val context = ServiceContext( utbotDir = utbotDir, projectPath = projectPath, - filePathToInference = listOf(sourceFilePath), + filePathToInference = astScrapper.filesToInfer, parsedFile = parsedFile, settings = settings, ) @@ -313,13 +309,14 @@ class JsTestGenerator( ) { val obligatoryExport = (classNode?.getClassName() ?: funcNode.getAbstractFunctionName()).toString() val collectedExports = collectExports(execId) + val exportsProvider = IExportsProvider.providerByPackageJson(packageJson) exports += (collectedExports + obligatoryExport) exportsManager(exports.toList()) { existingSection -> val existingExportsSet = existingSection?.let { section -> - val trimmedSection = section.substringAfter(getExportsPrefix(packageJson)) - .substringBeforeLast(getExportsPostfix(packageJson)) - val exportRegex = getExportsRegex(packageJson) - val existingExports = trimmedSection.split(getExportsDelimiter(packageJson)) + val trimmedSection = section.substringAfter(exportsProvider.exportsPrefix) + .substringBeforeLast(exportsProvider.exportsPostfix) + val exportRegex = exportsProvider.exportsRegex + val existingExports = trimmedSection.split(exportsProvider.exportsDelimiter) .filter { it.contains(exportRegex) && it.isNotBlank() } existingExports.map { rawLine -> exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() @@ -327,11 +324,11 @@ class JsTestGenerator( } ?: emptySet() val resultSet = existingExportsSet + exports.toSet() val resSection = resultSet.joinToString( - separator = getExportsDelimiter(packageJson), - prefix = getExportsPrefix(packageJson), - postfix = getExportsPostfix(packageJson), + separator = exportsProvider.exportsDelimiter, + prefix = exportsProvider.exportsPrefix, + postfix = exportsProvider.exportsPostfix, ) { - getExportsFrame(it, packageJson) + exportsProvider.getExportsFrame(it) } existingSection?.let { fileText.replace(existingSection, resSection) } ?: resSection } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index ce3f6d80f9..8699b73e3e 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -26,6 +26,9 @@ class JsAstScrapper( // Used not to parse the same file multiple times. private val _parsedFilesCache = mutableMapOf() + private val _filesToInfer: MutableList = mutableListOf(basePath) + val filesToInfer: List + get() = _filesToInfer.toList() fun findFunction(key: String, file: Node): Node? { if (importsMap[key]?.isFunction == true) return importsMap[key] @@ -60,6 +63,7 @@ class JsAstScrapper( private fun File.parseIfNecessary(): Node = _parsedFilesCache.getOrPut(this.path) { + _filesToInfer += this.path.replace("\\", "/") Compiler().parse(SourceFile.fromCode(this.path, readText())) } diff --git a/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt new file mode 100644 index 0000000000..895b0a8cea --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt @@ -0,0 +1,25 @@ +package providers.exports + +import service.PackageJson + +interface IExportsProvider { + + val exportsRegex: Regex + + val exportsDelimiter: String + + fun getExportsFrame(exportString: String): String + + val exportsPrefix: String + + val exportsPostfix: String + + fun instrumentationFunExport(funName: String): String + + companion object { + fun providerByPackageJson(packageJson: PackageJson): IExportsProvider = when (packageJson.isModule) { + true -> ModuleExportsProvider() + else -> RequireExportsProvider() + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt new file mode 100644 index 0000000000..77fae0fc1e --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt @@ -0,0 +1,16 @@ +package providers.exports + +class ModuleExportsProvider : IExportsProvider { + + override val exportsDelimiter: String = "," + + override val exportsPostfix: String = "}\n" + + override val exportsPrefix: String = "\nexport {" + + override val exportsRegex: Regex = Regex("(.*)") + + override fun getExportsFrame(exportString: String): String = exportString + + override fun instrumentationFunExport(funName: String): String = "\nexport {$funName}" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt new file mode 100644 index 0000000000..644744bd66 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt @@ -0,0 +1,16 @@ +package providers.exports + +class RequireExportsProvider : IExportsProvider { + + override val exportsDelimiter: String = "\n" + + override val exportsPostfix: String = "\n" + + override val exportsPrefix: String = "\n" + + override val exportsRegex: Regex = Regex("exports[.](.*) =") + + override fun getExportsFrame(exportString: String): String = "exports.$exportString = $exportString" + + override fun instrumentationFunExport(funName: String): String = "\nexports.$funName = $funName" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt new file mode 100644 index 0000000000..c2b821a904 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt @@ -0,0 +1,18 @@ +package providers.imports + +import service.PackageJson +import service.ServiceContext + +interface IImportsProvider { + + val ternScriptImports: String + + val tempFileImports: String + + companion object { + fun providerByPackageJson(packageJson: PackageJson, context: ServiceContext): IImportsProvider = when (packageJson.isModule) { + true -> ModuleImportsProvider(context) + else -> RequireImportsProvider(context) + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt new file mode 100644 index 0000000000..7d1aa55c01 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt @@ -0,0 +1,22 @@ +package providers.imports + +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings.fileUnderTestAliases + +class ModuleImportsProvider(context: ServiceContext) : IImportsProvider, ContextOwner by context { + + override val ternScriptImports: String = buildString { + appendLine("import * as tern from \"tern/lib/tern.js\"") + appendLine("import * as condense from \"tern/lib/condense.js\"") + appendLine("import * as util from \"tern/test/util.js\"") + appendLine("import * as fs from \"fs\"") + appendLine("import * as path from \"path\"") + } + + override val tempFileImports: String = buildString { + val importFileUnderTest = "./instr/${filePathToInference.first().substringAfterLast("/")}" + appendLine("import * as $fileUnderTestAliases from \"$importFileUnderTest\"") + appendLine("import * as fs from \"fs\"") + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt new file mode 100644 index 0000000000..d653b2b047 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt @@ -0,0 +1,23 @@ +package providers.imports + +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings.fileUnderTestAliases + +class RequireImportsProvider(context: ServiceContext) : IImportsProvider, ContextOwner by context { + + override val ternScriptImports: String = buildString { + appendLine("const tern = require(\"tern/lib/tern\")") + appendLine("const condense = require(\"tern/lib/condense.js\")") + appendLine("const util = require(\"tern/test/util.js\")") + appendLine("const fs = require(\"fs\")") + appendLine("const path = require(\"path\")") + } + + override val tempFileImports: String = buildString { + val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" + appendLine("const $fileUnderTestAliases = require(\"./$importFileUnderTest\")") + appendLine("const fs = require(\"fs\")\n") + } + +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt index 095dec1130..e36dd0e41d 100644 --- a/utbot-js/src/main/kotlin/service/InstrumentationService.kt +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -13,6 +13,8 @@ import utils.JsCmdExec import utils.PathResolver.getRelativePath import java.io.File import java.nio.file.Paths +import parser.JsParserUtils.getModuleImportText +import providers.exports.IExportsProvider import kotlin.io.path.pathString import kotlin.math.roundToInt @@ -144,7 +146,8 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset val covFunRegex = Regex("function (cov_.*)\\(\\).*") val funName = covFunRegex.find(instrumentedFileText.takeWhile { it != '{' })?.groups?.get(1)?.value ?: throw IllegalStateException("") - val fixedFileText = fixImportsInInstrumentedFile() + "\nexports.$funName = $funName" + val fixedFileText = fixImportsInInstrumentedFile() + + IExportsProvider.providerByPackageJson(packageJson).instrumentationFunExport(funName) File(instrumentedFilePath).writeTextAndUpdate(fixedFileText) covFunName = funName @@ -158,15 +161,27 @@ class InstrumentationService(context: ServiceContext, private val funcDeclOffset private fun fixImportsInInstrumentedFile(): String { // nyc poorly handles imports paths in file to instrument. Manual fix required. NodeUtil.visitPreOrder(parsedInstrFile) { node -> - if (node.isRequireImport()) { - val currString = node.getRequireImportText() - val relPath = Paths.get( - getRelativePath( - "${projectPath}/${utbotDir}/instr", - File(filePathToInference.first()).parent - ) - ).resolve(currString).pathString.replace("\\", "/") - node.firstChild!!.next!!.string = relPath + when { + node.isRequireImport() -> { + val currString = node.getRequireImportText() + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.string = relPath + } + node.isImport -> { + val currString = node.getModuleImportText() + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.next!!.string = relPath + } } } return CodePrinter.Builder(parsedInstrFile).build() diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index a17e83e473..1ee7ab4553 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -16,25 +16,17 @@ import utils.constructClass import utils.data.MethodTypes import java.io.File import java.util.Locale - -/* - NOTE: this approach is quite bad, but we failed to implement alternatives. - TODO: 1. MINOR: Find a better solution after the first stable version. - 2. SEVERE: Load all necessary .js files in Tern.js since functions can be exported and used in other files. - */ +import providers.imports.IImportsProvider /** * Installs and sets up scripts for running Tern.js type guesser. */ class TernService(context: ServiceContext) : ContextOwner by context { + private val importProvider = IImportsProvider.providerByPackageJson(packageJson, context) private fun ternScriptCode() = """ -const tern = require("tern/lib/tern") -const condense = require("tern/lib/condense.js") -const util = require("tern/test/util.js") -const fs = require("fs") -const path = require("path") +${generateImportsSection()} var condenseDir = ""; @@ -60,11 +52,11 @@ function runTest(options) { } function test(options) { - if (typeof options == "string") options = {load: [options]}; + options = {load: options}; runTest(options); } -test("${filePathToInference.joinToString(separator = " ")}") +test(["${filePathToInference.joinToString(separator = "\", \"")}"]) """ init { @@ -77,6 +69,8 @@ test("${filePathToInference.joinToString(separator = " ")}") private lateinit var json: JSONObject + private fun generateImportsSection(): String = importProvider.ternScriptImports + private fun installDeps(path: String) { JsCmdExec.runCommand( dir = path, diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index df4f3b9e45..7a02e4c2f9 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -16,9 +16,8 @@ import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern +import providers.imports.IImportsProvider - -// TODO: Add "error" field in result json to not collide with "result" field upon error. class CoverageServiceProvider( private val context: ServiceContext, private val instrumentationService: InstrumentationService, @@ -26,11 +25,7 @@ class CoverageServiceProvider( private val description: JsMethodDescription ) : ContextOwner by context { - private val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" - - private val imports = - "const ${JsTestGenerationSettings.fileUnderTestAliases} = require(\"./$importFileUnderTest\")\n" + - "const fs = require(\"fs\")\n\n" + private val imports = IImportsProvider.providerByPackageJson(packageJson, context).tempFileImports private val filePredicate = """ function check_value(value, json) { diff --git a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt b/utbot-js/src/main/kotlin/utils/ExportsProvider.kt deleted file mode 100644 index 49fea8da23..0000000000 --- a/utbot-js/src/main/kotlin/utils/ExportsProvider.kt +++ /dev/null @@ -1,41 +0,0 @@ -package utils - -import service.PackageJson - -object ExportsProvider { - - fun getExportsRegex(packageJson: PackageJson): Regex = with(packageJson) { - when (isModule) { - true -> Regex("(.*)") - false -> Regex("exports[.](.*) =") - } - } - - fun getExportsDelimiter(packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> "," - false -> "\n" - } - } - - fun getExportsFrame(exportString: String, packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> exportString - false -> "exports.$exportString = $exportString" - } - } - - fun getExportsPrefix(packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> "\nexport {" - false -> "\n" - } - } - - fun getExportsPostfix(packageJson: PackageJson): String = with(packageJson) { - when(isModule) { - true -> "}\n" - false -> "\n" - } - } -} \ No newline at end of file From 33d3ef97c4fdb49f42587caa142dbea8f321e0a9 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:59:13 +0300 Subject: [PATCH 41/74] [WIP] JavaScript test generation for multi-file projects --- .../src/main/kotlin/api/JsTestGenerator.kt | 5 ++-- .../src/main/kotlin/parser/JsAstScrapper.kt | 29 ++++++++++++------- .../src/main/kotlin/service/TernService.kt | 9 +++--- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 92ff042243..c5c6775bc5 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -364,11 +364,12 @@ class JsTestGenerator( private fun collectExports(methodId: JsMethodId): List { val res = mutableListOf() methodId.parameters.forEach { - if (!it.isJsBasic) { + if (!(it.isJsBasic || astScrapper.importsMap.contains(it.name))) { res += it.name } } - if (!methodId.returnType.isJsBasic) res += methodId.returnType.name + if (!methodId.returnType.isJsBasic && !astScrapper.importsMap.contains(methodId.returnType.name)) + res += methodId.returnType.name return res } diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index 8699b73e3e..f90de9a48d 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -29,9 +29,26 @@ class JsAstScrapper( private val _filesToInfer: MutableList = mutableListOf(basePath) val filesToInfer: List get() = _filesToInfer.toList() + private val _importsMap = mutableMapOf() + val importsMap: Map + get() = _importsMap.toMap() + + init { + _importsMap.apply { + val visitor = Visitor() + visitor.accept(parsedFile) + val res = visitor.importNodes.fold(emptyMap()) { acc, node -> + val currAcc = acc.toList().toTypedArray() + val more = node.importedNodes().toList().toTypedArray() + mapOf(*currAcc, *more) + } + this.putAll(res) + this.toMap() + } + } fun findFunction(key: String, file: Node): Node? { - if (importsMap[key]?.isFunction == true) return importsMap[key] + if (_importsMap[key]?.isFunction == true) return _importsMap[key] val functionVisitor = JsFunctionAstVisitor(key, null) functionVisitor.accept(file) return try { @@ -40,7 +57,7 @@ class JsAstScrapper( } fun findClass(key: String, file: Node): Node? { - if (importsMap[key]?.isClass == true) return importsMap[key] + if (_importsMap[key]?.isClass == true) return _importsMap[key] val classVisitor = JsClassAstVisitor(key) classVisitor.accept(file) return try { @@ -53,14 +70,6 @@ class JsAstScrapper( return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } } - private val importsMap = run { - val visitor = Visitor() - visitor.accept(parsedFile) - visitor.importNodes.fold(emptyMap()) { acc, node -> - mapOf(*acc.toList().toTypedArray(), *node.importedNodes().toList().toTypedArray()) - } - } - private fun File.parseIfNecessary(): Node = _parsedFilesCache.getOrPut(this.path) { _filesToInfer += this.path.replace("\\", "/") diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 1ee7ab4553..44e69e01f2 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -4,6 +4,7 @@ import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMultipleClassId import framework.api.js.util.jsUndefinedClassId +import java.io.File import org.json.JSONException import org.json.JSONObject import parser.JsParserUtils @@ -11,12 +12,10 @@ import parser.JsParserUtils.getAbstractFunctionName import parser.JsParserUtils.getAbstractFunctionParams import parser.JsParserUtils.getClassName import parser.JsParserUtils.getConstructor +import providers.imports.IImportsProvider import utils.JsCmdExec import utils.constructClass import utils.data.MethodTypes -import java.io.File -import java.util.Locale -import providers.imports.IImportsProvider /** * Installs and sets up scripts for running Tern.js type guesser. @@ -178,8 +177,8 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) ) } - name.contains('|') -> JsMultipleClassId(name.lowercase(Locale.getDefault())) - else -> JsClassId(name.lowercase(Locale.getDefault())) + name.contains('|') -> JsMultipleClassId(name) + else -> JsClassId(name) } return try { From 9298cbfc0195f05da8b970b2178443873a790fa0 Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Sun, 19 Feb 2023 23:29:44 +0300 Subject: [PATCH 42/74] Added array support for JavaScript test generation --- .../src/main/kotlin/api/JsTestGenerator.kt | 5 +- .../kotlin/framework/api/js/util/JsIdUtil.kt | 20 ++- .../tree/JsCgVariableConstructor.kt | 156 +++++++++++++++++- .../model/constructor/visitor/CgJsRenderer.kt | 3 +- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 4 +- .../fuzzer/providers/ArrayValueProvider.kt | 43 +++++ .../coverage/CoverageServiceProvider.kt | 14 ++ 7 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index c5c6775bc5..2f22f07b82 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -4,6 +4,7 @@ import codegen.JsCodeGenerator import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMethodId +import framework.api.js.util.isExportable import framework.api.js.util.isJsBasic import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId @@ -364,11 +365,11 @@ class JsTestGenerator( private fun collectExports(methodId: JsMethodId): List { val res = mutableListOf() methodId.parameters.forEach { - if (!(it.isJsBasic || astScrapper.importsMap.contains(it.name))) { + if (it.isExportable && !astScrapper.importsMap.contains(it.name)) { res += it.name } } - if (!methodId.returnType.isJsBasic && !astScrapper.importsMap.contains(methodId.returnType.name)) + if (methodId.returnType.isExportable && !astScrapper.importsMap.contains(methodId.returnType.name)) res += methodId.returnType.name return res } diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index a54d7879de..5ac080bfc9 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -3,6 +3,9 @@ package framework.api.js.util import framework.api.js.JsClassId import framework.api.js.JsMultipleClassId import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId @@ -31,11 +34,26 @@ fun ClassId.toJsClassId() = else -> jsUndefinedClassId } +fun JsClassId.defaultJsValueModel(): UtModel = when (this) { + jsNumberClassId -> UtPrimitiveModel(0.0) + jsDoubleClassId -> UtPrimitiveModel(Double.POSITIVE_INFINITY) + jsBooleanClassId -> UtPrimitiveModel(false) + jsStringClassId -> UtPrimitiveModel("default") + jsUndefinedClassId -> UtPrimitiveModel(0.0) + else -> UtNullModel(this) +} + val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId +val JsClassId.isExportable: Boolean + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray) + val JsClassId.isClass: Boolean get() = !(this.isJsBasic || this == jsErrorClassId) val JsClassId.isUndefined: Boolean - get() = this == jsUndefinedClassId \ No newline at end of file + get() = this == jsUndefinedClassId + +val JsClassId.isJsArray: Boolean + get() = this.name == "array" && this.elementClassId is JsClassId \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt index 098babf9e0..db7db14eee 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt @@ -1,25 +1,177 @@ package framework.codegen.model.constructor.tree +import framework.api.js.JsClassId +import framework.api.js.JsNullModel import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isExportable +import framework.api.js.util.jsBooleanClassId +import framework.api.js.util.jsDoubleClassId +import framework.api.js.util.jsNumberClassId +import framework.api.js.util.jsStringClassId +import framework.api.js.util.jsUndefinedClassId import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgExpression import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.CgComponents import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.inc +import org.utbot.framework.codegen.util.lessThan import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set class JsCgVariableConstructor(ctx: CgContext) : CgVariableConstructor(ctx) { + + private val nameGenerator = CgComponents.getNameGeneratorBy(context) override fun getOrCreateVariable(model: UtModel, name: String?): CgValue { - return if (model is UtReferenceModel) valueByModelId.getOrPut(model.id) { + return if (model is UtAssembleModel) valueByModelId.getOrPut(model.id) { // TODO SEVERE: May lead to unexpected behavior in case of changes to the original method super.getOrCreateVariable(model, name) } else valueByModel.getOrPut(model) { + val baseName = name ?: nameGenerator.nameFrom(model.classId) when (model) { is JsPrimitiveModel -> CgLiteral(model.classId, model.value) + is UtArrayModel -> constructArray(model, baseName) else -> nullLiteral() } } } + + private val MAX_ARRAY_INITIALIZER_SIZE = 10 + + private operator fun UtArrayModel.get(index: Int): UtModel = stores[index] ?: constModel + + private val defaultByPrimitiveType: Map = mapOf( + jsBooleanClassId to false, + jsStringClassId to "default", + jsUndefinedClassId to 0.0, + jsNumberClassId to 0.0, + jsDoubleClassId to Double.POSITIVE_INFINITY + ) + + private infix fun UtModel.isNotJsDefaultValueOf(type: JsClassId): Boolean = !(this isJsDefaultValueOf type) + + private infix fun UtModel.isJsDefaultValueOf(type: JsClassId): Boolean = when (this) { + is JsNullModel -> type.isExportable + is JsPrimitiveModel -> value == defaultByPrimitiveType[type] + else -> false + } + + private fun CgVariable.setArrayElement(index: Any, value: CgValue) { + val i = index.resolve() + this.at(i) `=` value + } + + private fun basicForLoop(until: Any, body: (i: CgExpression) -> Unit) { + basicForLoop(start = 0, until, body) + } + + private fun basicForLoop(start: Any, until: Any, body: (i: CgExpression) -> Unit) { + forLoop { + val (i, init) = loopInitialization(jsNumberClassId, "i", start.resolve()) + initialization = init + condition = i lessThan until.resolve() + update = i.inc() + statements = block { body(i) } + } + } + + internal fun loopInitialization( + variableType: ClassId, + baseVariableName: String, + initializer: Any? + ): Pair { + val declaration = CgDeclaration(variableType, baseVariableName.toVarName(), initializer.resolve()) + val variable = declaration.variable + updateVariableScope(variable) + return variable to declaration + } + + private fun constructArray(arrayModel: UtArrayModel, baseName: String?): CgVariable { + val elementType = arrayModel.classId.elementClassId!! as JsClassId + val elementModels = (0 until arrayModel.length).map { + arrayModel.stores.getOrDefault(it, arrayModel.constModel) + } + + val allPrimitives = elementModels.all { it is JsPrimitiveModel } + val allNulls = elementModels.all { it is JsNullModel } + // we can use array initializer if all elements are primitives or all of them are null, + // and the size of an array is not greater than the fixed maximum size + val canInitWithValues = (allPrimitives || allNulls) && elementModels.size <= MAX_ARRAY_INITIALIZER_SIZE + + val initializer = if (canInitWithValues) { + val elements = elementModels.map { model -> + when (model) { + is JsPrimitiveModel -> model.value.resolve() + is UtNullModel -> null.resolve() + else -> error("Non primitive or null model $model is unexpected in array initializer") + } + } + CgArrayInitializer(arrayModel.classId, elementType, elements) + } else { + CgAllocateArray(arrayModel.classId, elementType, arrayModel.length) + } + + val array = newVar(arrayModel.classId, baseName) { initializer } + valueByModelId[arrayModel.id] = array + + if (canInitWithValues) { + return array + } + + if (arrayModel.length <= 0) return array + if (arrayModel.length == 1) { + // take first element value if it is present, otherwise use default value from model + val elementModel = arrayModel[0] + if (elementModel isNotJsDefaultValueOf elementType) { + array.setArrayElement(0, getOrCreateVariable(elementModel)) + } + } else { + val indexedValuesFromStores = + if (arrayModel.stores.size == arrayModel.length) { + // do not use constModel because stores fully cover array + arrayModel.stores.entries.filter { (_, element) -> element isNotJsDefaultValueOf elementType } + } else { + // fill array if constModel is not default type value + if (arrayModel.constModel isNotJsDefaultValueOf elementType) { + val defaultVariable = getOrCreateVariable(arrayModel.constModel, "defaultValue") + basicForLoop(arrayModel.length) { i -> + array.setArrayElement(i, defaultVariable) + } + } + + // choose all not default values + val defaultValue = if (arrayModel.constModel isJsDefaultValueOf elementType) { + arrayModel.constModel + } else { + elementType.defaultValueModel() + } + arrayModel.stores.entries.filter { (_, element) -> element != defaultValue } + } + + // set all values from stores manually + indexedValuesFromStores + .sortedBy { it.key } + .forEach { (index, element) -> array.setArrayElement(index, getOrCreateVariable(element)) } + } + + return array + } + + private fun String.toVarName(): String = nameGenerator.variableName(this) } diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index 45715897df..a36766857d 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -196,8 +196,9 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP override fun visit(element: CgArrayInitializer) { val elementType = element.elementType val elementsInLine = arrayElementsInLine(elementType) - + print("[") element.values.renderElements(elementsInLine) + print("]") } override fun visit(element: CgClassFile) { diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index cc3420a45c..813baf97ff 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -1,6 +1,7 @@ package fuzzer import framework.api.js.JsClassId +import fuzzer.providers.ArrayValueProvider import fuzzer.providers.BoolValueProvider import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider @@ -14,7 +15,8 @@ fun defaultValueProviders() = listOf( BoolValueProvider, NumberValueProvider, StringValueProvider, - ObjectValueProvider() + ObjectValueProvider(), + ArrayValueProvider() ) class JsFuzzing( diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt new file mode 100644 index 0000000000..33ea48c933 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -0,0 +1,43 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.util.defaultJsValueModel +import framework.api.js.util.isJsArray +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator +import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +private val idGenerator = ReferencePreservingIntIdGenerator() + +class ArrayValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsArray + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + yield( + Seed.Collection( + construct = Routine.Collection { + UtArrayModel( + id = idGenerator.createId(), + classId = type, + length = it, + constModel = (type.elementClassId!! as JsClassId).defaultJsValueModel(), + stores = hashMapOf(), + ).fuzzed { + summary = "%var% = ${type.elementClassId!!.simpleName}[$it]" + } + }, + modify = Routine.ForEach(listOf(type.elementClassId!! as JsClassId)) { self, i, values -> + (self.model as UtArrayModel).stores[i] = values.first().model + } + )) + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 7a02e4c2f9..87cc161a26 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -16,6 +16,8 @@ import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtNullModel import providers.imports.IImportsProvider class CoverageServiceProvider( @@ -209,10 +211,22 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) return callConstructorString + paramsString } + private fun UtArrayModel.toParamString(): String { + val paramsString = stores.values.joinToString( + prefix = "[", + postfix = "]", + ) { + it.toCallString() + } + return paramsString + } + private fun UtModel.toCallString(): String = when (this) { is UtAssembleModel -> this.toParamString() + is UtArrayModel -> this.toParamString() + is UtNullModel -> "null" else -> { (this as JsPrimitiveModel).value.escapeSymbolsIfNecessary().quoteWrapIfNecessary() } From dce90a4e04b668028cb6f6dc00921491af4f7bdf Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 21 Feb 2023 12:27:39 +0300 Subject: [PATCH 43/74] [WIP] Map data structure support for JavaScript --- .../kotlin/framework/api/js/util/JsIdUtil.kt | 5 +- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 2 + .../fuzzer/providers/ArrayValueProvider.kt | 6 +- .../fuzzer/providers/MapValueProvider.kt | 80 +++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index 5ac080bfc9..2360fa0d6a 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -56,4 +56,7 @@ val JsClassId.isUndefined: Boolean get() = this == jsUndefinedClassId val JsClassId.isJsArray: Boolean - get() = this.name == "array" && this.elementClassId is JsClassId \ No newline at end of file + get() = this.name == "array" && this.elementClassId is JsClassId + +val JsClassId.isJsMap: Boolean + get() = this.name == "Map" diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index 813baf97ff..e345e1c6e0 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -3,6 +3,7 @@ package fuzzer import framework.api.js.JsClassId import fuzzer.providers.ArrayValueProvider import fuzzer.providers.BoolValueProvider +import fuzzer.providers.MapValueProvider import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider import fuzzer.providers.StringValueProvider @@ -15,6 +16,7 @@ fun defaultValueProviders() = listOf( BoolValueProvider, NumberValueProvider, StringValueProvider, + MapValueProvider, ObjectValueProvider(), ArrayValueProvider() ) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index 33ea48c933..04b49cf7bf 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -12,10 +12,10 @@ import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -private val idGenerator = ReferencePreservingIntIdGenerator() - class ArrayValueProvider : ValueProvider { + private val idGenerator = ReferencePreservingIntIdGenerator() + override fun accept(type: JsClassId): Boolean = type.isJsArray override fun generate( @@ -40,4 +40,4 @@ class ArrayValueProvider : ValueProvider { + + private val idGenerator = ReferencePreservingIntIdGenerator() + + override fun accept(type: JsClassId): Boolean = type.isJsMap + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + val modifications = mutableListOf>() + jsBasic.zip(jsBasic).map { (a, b) -> listOf(a, b) }.forEach { typeParameters -> + modifications += Routine.Call(typeParameters) { instance, arguments -> + val model = instance.model as UtAssembleModel + (model).modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "set", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId, jsUndefinedClassId) + ), + arguments.map { it.model } + ) + } + } + yield(Seed.Recursive( + construct = Routine.Create(listOf(jsUndefinedClassId, jsUndefinedClassId)) { + UtAssembleModel( + id = idGenerator.createId(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + empty = Routine.Empty { + UtAssembleModel( + id = idGenerator.createId(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + modify = modifications.asSequence() + )) + } +} From 2dcc1e4980be88fa0da531a01a217ac9ef75a53d Mon Sep 17 00:00:00 2001 From: Sergey Loktev <71882967+zishkaz@users.noreply.github.com> Date: Tue, 21 Feb 2023 20:21:22 +0300 Subject: [PATCH 44/74] [WIP] JavaScript NPM packages installation enhancement --- .../plugin/language/js/JsDialogProcessor.kt | 5 +- .../kotlin/framework/api/js/util/JsIdUtil.kt | 3 +- .../main/kotlin/service/PackageJsonService.kt | 23 ++++---- .../kotlin/settings/JsPackagesSettings.kt | 55 ++++++++++++++----- 4 files changed, 56 insertions(+), 30 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 82041d5ab8..362957d8b4 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 {} @@ -256,12 +257,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/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index 2360fa0d6a..c74df2c2f7 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -9,6 +9,7 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.isMap val jsUndefinedClassId = JsClassId("undefined") val jsNumberClassId = JsClassId("number") @@ -47,7 +48,7 @@ val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId val JsClassId.isExportable: Boolean - get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray) + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap) val JsClassId.isClass: Boolean get() = !(this.isJsBasic || this == jsErrorClassId) diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt index 354aa9869a..a838af0ec3 100644 --- a/utbot-js/src/main/kotlin/service/PackageJsonService.kt +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -1,23 +1,25 @@ package service -import org.json.JSONException -import org.json.JSONObject import java.io.File import java.io.FilenameFilter +import org.json.JSONObject data class PackageJson( - val isModule: Boolean + val isModule: Boolean, + val deps: Set ) { companion object { - val defaultConfig = PackageJson(false) + val defaultConfig = PackageJson(false, emptySet()) } } - -class PackageJsonService(context: ServiceContext) : ContextOwner by context { +class PackageJsonService( + private val filePathToInference: String, + private val projectPath: String +) { fun findClosestConfig(): PackageJson { - var currDir = File(filePathToInference.first().substringBeforeLast("/")) + var currDir = File(filePathToInference.substringBeforeLast("/")) do { val matchingFiles: Array = currDir.listFiles( FilenameFilter { _, name -> @@ -33,11 +35,8 @@ class PackageJsonService(context: ServiceContext) : ContextOwner by context { private fun parseConfig(configFile: File): PackageJson { val configAsJson = JSONObject(configFile.readText()) return PackageJson( - isModule = try { - (configAsJson.getString("type") == "module") - } catch (e: JSONException) { - false - }, + 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 0008d19117cf35d47d169339545f7db2ec0b5968 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 27 Feb 2023 13:13:16 +0300 Subject: [PATCH 45/74] Enhanced package installation for JavaScript --- .../plugin/language/js/JsDialogProcessor.kt | 64 ++++++++--------- .../language/js/NycSourceFileChooser.kt | 5 +- .../src/main/kotlin/api/JsTestGenerator.kt | 5 +- .../kotlin/settings/JsPackagesSettings.kt | 70 ++++++++++++++----- 4 files changed, 90 insertions(+), 54 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 362957d8b4..fd8f85f27c 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) } @@ -177,7 +174,12 @@ object JsDialogProcessor { if (name == dummyClassName) null else name }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), - exportsManager = partialApplication(JsDialogProcessor::manageExports, editor, project, editor.document.text), + exportsManager = partialApplication( + JsDialogProcessor::manageExports, + editor, + project, + editor.document.text + ), settings = JsDynamicSettings( pathToNode = model.pathToNode, pathToNYC = model.pathToNYC, @@ -213,7 +215,13 @@ object JsDialogProcessor { return { d: D, e: E -> f(a, b, c, d, e) } } - private fun manageExports(editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String) { + private fun manageExports( + editor: Editor, + project: Project, + fileText: String, + exports: List, + swappedText: (String?) -> String + ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { when { @@ -257,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 @@ -289,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 2f22f07b82..ecc9d3c250 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -105,7 +105,10 @@ class JsTestGenerator( parsedFile = parsedFile, settings = settings, ) - context.packageJson = PackageJsonService(context).findClosestConfig() + context.packageJson = PackageJsonService( + sourceFilePath, + projectPath, + ).findClosestConfig() val paramNames = mutableMapOf>() val testSets = mutableListOf() val classNode = 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 64b572aec3c574ceda56b5dbd6ba8c768e65f722 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 27 Feb 2023 13:47:39 +0300 Subject: [PATCH 46/74] Fixed id error for UtAssembleModel in JavaScript --- .../main/kotlin/api/JsUtModelConstructor.kt | 6 +- .../src/main/kotlin/fuzzer/JsFuzzerApi.kt | 7 ++ .../fuzzer/providers/ArrayValueProvider.kt | 6 +- .../fuzzer/providers/MapValueProvider.kt | 71 +++++++++---------- .../fuzzer/providers/ObjectValueProvider.kt | 16 +++-- 5 files changed, 55 insertions(+), 51 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt b/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt index 5de6a87971..98705c1d26 100644 --- a/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt +++ b/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt @@ -7,17 +7,15 @@ import framework.api.js.JsPrimitiveModel import framework.api.js.JsUndefinedModel import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel -import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructorInterface class JsUtModelConstructor : UtModelConstructorInterface { - private val idGenerator = ReferencePreservingIntIdGenerator() - // TODO SEVERE: Requires substantial expansion to other types @Suppress("NAME_SHADOWING") override fun construct(value: Any?, classId: ClassId): UtModel { @@ -52,7 +50,7 @@ class JsUtModelConstructor : UtModelConstructorInterface { val values = (value as Map).values.map { construct(it, JsEmptyClassId()) } - val id = idGenerator.createId() + val id = JsIdProvider.get() val instantiationCall = UtExecutableCallModel(null, constructor, values) return UtAssembleModel( id = id, diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt index 150a9343a6..4476c2a5c6 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -2,6 +2,7 @@ package fuzzer import framework.api.js.JsClassId import framework.api.js.util.isClass +import java.util.concurrent.atomic.AtomicInteger import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzer.FuzzedValue @@ -57,3 +58,9 @@ class JsFeedback( data class JsStatement( val number: Int ) + +object JsIdProvider { + private var _id = AtomicInteger(0) + + fun get() = _id.incrementAndGet() +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index 04b49cf7bf..70fda93be9 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -3,10 +3,10 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.util.defaultJsValueModel import framework.api.js.util.isJsArray +import fuzzer.JsIdProvider import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed @@ -14,8 +14,6 @@ import org.utbot.fuzzing.ValueProvider class ArrayValueProvider : ValueProvider { - private val idGenerator = ReferencePreservingIntIdGenerator() - override fun accept(type: JsClassId): Boolean = type.isJsArray override fun generate( @@ -26,7 +24,7 @@ class ArrayValueProvider : ValueProvider { - private val idGenerator = ReferencePreservingIntIdGenerator() - override fun accept(type: JsClassId): Boolean = type.isJsMap override fun generate( @@ -43,38 +41,39 @@ object MapValueProvider : ValueProvider { - private val idGenerator = ReferencePreservingIntIdGenerator() - override fun accept(type: JsClassId): Boolean { return type.isClass } @@ -31,10 +29,13 @@ class ObjectValueProvider : ValueProvider { + private fun createValue( + classId: JsClassId, + constructorId: JsConstructorId + ): Seed.Recursive { return Seed.Recursive( construct = Routine.Create(constructorId.parameters) { values -> - val id = idGenerator.createId() + val id = JsIdProvider.get() UtAssembleModel( id = id, classId = classId, @@ -45,7 +46,8 @@ class ObjectValueProvider : ValueProvider Date: Fri, 3 Mar 2023 02:34:37 +0300 Subject: [PATCH 47/74] Enhanced Map datastructure fuzzing for JavaScript --- .../model/constructor/visitor/CgJsRenderer.kt | 7 ++- .../src/main/kotlin/parser/JsParserUtils.kt | 5 ++ .../coverage/CoverageServiceProvider.kt | 63 +++++++++++++++---- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index a36766857d..28f02ffe18 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -1,5 +1,7 @@ package framework.codegen.model.constructor.visitor +import framework.api.js.JsClassId +import framework.api.js.util.isExportable import org.apache.commons.text.StringEscapeUtils import org.utbot.framework.codegen.domain.RegularImport import org.utbot.framework.codegen.domain.StaticImport @@ -248,7 +250,10 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP } override fun visit(element: CgConstructorCall) { - print("new $fileUnderTestAliases.${element.executableId.classId.name}") + val importPrefix = "$fileUnderTestAliases.".takeIf { + (element.executableId.classId as JsClassId).isExportable + } ?: "" + print("new $importPrefix${element.executableId.classId.name}") print("(") element.arguments.renderSeparated() print(")") diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index e3c1a97340..bb1f66c55b 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -69,6 +69,11 @@ object JsParserUtils { this.isString -> this.string this.isTrue -> true this.isFalse -> false + this.isCall -> { + if (this.firstChild?.isGetProp == true) { + this.firstChild?.next?.getAnyValue() + } else null + } else -> null } diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 87cc161a26..9bd25a4fa8 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -1,9 +1,12 @@ package service.coverage +import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isExportable import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription +import java.lang.StringBuilder import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic @@ -17,6 +20,7 @@ import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtNullModel import providers.imports.IImportsProvider @@ -142,7 +146,7 @@ fs.writeFileSync("$resFilePath", JSON.stringify(json)) index: Int, resFilePath: String, ): String { - val callString = makeCallFunctionString(fuzzedValue, method, containingClass) + val callString = makeCallFunctionString(fuzzedValue, method, containingClass, index) return """ let json$index = {} json$index.is_inf = false @@ -151,7 +155,7 @@ json$index.is_error = false json$index.spec_sign = 1 let res$index try { - res$index = $callString + $callString check_value(res$index, json$index) } catch(e) { res$index = e.message @@ -169,21 +173,34 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) private fun makeCallFunctionString( fuzzedValue: List, method: JsMethodId, - containingClass: String? + containingClass: String?, + index: Int ): String { + val paramsInit = initParams(fuzzedValue) val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue val initClass = containingClass?.let { if (!method.isStatic) { - description.thisInstance?.let { fuzzedValue[0].model.toCallString() } + description.thisInstance?.let { fuzzedValue[0].model.initModelAsString() } ?: "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" } else "${JsTestGenerationSettings.fileUnderTestAliases}.$it" } ?: JsTestGenerationSettings.fileUnderTestAliases var callString = "$initClass.${method.name}" - callString += actualParams.joinToString( - prefix = "(", + callString = List(actualParams.size) { idx -> "param$idx" }.joinToString( + prefix = "res$index = $callString(", postfix = ")", - ) { value -> value.model.toCallString() } - return callString + ) + return paramsInit + callString + } + + private fun initParams(fuzzedValue: List): String { + val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue + return actualParams.mapIndexed { index, param -> + val varName = "param$index" + buildString { + appendLine("let $varName = ${param.model.initModelAsString()}") + (param.model as? UtAssembleModel)?.initModificationsAsString(this, varName) + } + }.joinToString() } private fun Any.quoteWrapIfNecessary(): String = @@ -201,12 +218,15 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) } private fun UtAssembleModel.toParamString(): String { - val callConstructorString = "new ${JsTestGenerationSettings.fileUnderTestAliases}.${classId.name}" + val importPrefix = "new ${JsTestGenerationSettings.fileUnderTestAliases}.".takeIf { + (classId as JsClassId).isExportable + } ?: "new " + val callConstructorString = importPrefix + classId.name val paramsString = instantiationCall.params.joinToString( prefix = "(", postfix = ")", ) { - it.toCallString() + it.initModelAsString() } return callConstructorString + paramsString } @@ -216,13 +236,14 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) prefix = "[", postfix = "]", ) { - it.toCallString() + it.initModelAsString() } return paramsString } - private fun UtModel.toCallString(): String = + + private fun UtModel.initModelAsString(): String = when (this) { is UtAssembleModel -> this.toParamString() is UtArrayModel -> this.toParamString() @@ -231,4 +252,22 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) (this as JsPrimitiveModel).value.escapeSymbolsIfNecessary().quoteWrapIfNecessary() } } + + private fun UtAssembleModel.initModificationsAsString(stringBuilder: StringBuilder, varName: String) { + with(stringBuilder) { + this@initModificationsAsString.modificationsChain.forEach { + if (it is UtExecutableCallModel) { + val exec = it.executable as JsMethodId + appendLine( + it.params.joinToString( + prefix = "$varName.${exec.name}(", + postfix = ")" + ) { model -> + model.initModelAsString() + } + ) + } + } + } + } } From c750637c542525982ebf6eac92f24b68b6718641 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 15:58:23 +0300 Subject: [PATCH 48/74] 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 7eb3ee8801dce2d4794e7a4460f2fe79f12be1e2 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 3 Mar 2023 16:00:43 +0300 Subject: [PATCH 49/74] Add message about time expiration for npm packages installation --- .../plugin/language/js/JsDialogProcessor.kt | 140 +++++++++--------- .../kotlin/settings/JsPackagesSettings.kt | 45 ++++-- 2 files changed, 99 insertions(+), 86 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 fd8f85f27c..f80e550b78 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."), @@ -175,10 +169,7 @@ object JsDialogProcessor { }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), exportsManager = partialApplication( - JsDialogProcessor::manageExports, - editor, - project, - editor.document.text + JsDialogProcessor::manageExports, editor, project, editor.document.text ), settings = JsDynamicSettings( pathToNode = model.pathToNode, @@ -187,8 +178,7 @@ object JsDialogProcessor { timeout = model.timeout, coverageMode = model.coverageMode ), - isCancelled = { indicator.isCanceled } - ) + isCancelled = { indicator.isCanceled }) indicator.fraction = indicator.fraction.coerceAtLeast(0.9) indicator.text = "Generate code for tests" @@ -196,11 +186,9 @@ object JsDialogProcessor { val generatedCode = testGenerator.run() invokeLater { runWriteAction { - val testPsiFile = - testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) - .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) - val testFileEditor = - CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) + val testPsiFile = testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) + .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + val testFileEditor = CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) unblockDocument(project, testFileEditor.document) testFileEditor.document.setText(generatedCode) unblockDocument(project, testFileEditor.document) @@ -216,11 +204,7 @@ object JsDialogProcessor { } private fun manageExports( - editor: Editor, - project: Project, - fileText: String, - exports: List, - swappedText: (String?) -> String + editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { @@ -265,33 +249,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 f02dfff9ab60aa769ef274c94aa6dd2ef17671fe Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 3 Mar 2023 17:04:04 +0300 Subject: [PATCH 50/74] JavaScript test generation for Set data structure implemented --- .../kotlin/framework/api/js/util/JsIdUtil.kt | 6 +- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 2 + .../fuzzer/providers/SetValueProvider.kt | 79 +++++++++++++++++++ .../main/kotlin/parser/JsFuzzerAstVisitor.kt | 19 +++-- .../src/main/kotlin/service/TernService.kt | 9 --- 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index c74df2c2f7..deb0dbc2b6 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -9,7 +9,6 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.isMap val jsUndefinedClassId = JsClassId("undefined") val jsNumberClassId = JsClassId("number") @@ -48,7 +47,7 @@ val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId val JsClassId.isExportable: Boolean - get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap) + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap || this.isJsSet) val JsClassId.isClass: Boolean get() = !(this.isJsBasic || this == jsErrorClassId) @@ -61,3 +60,6 @@ val JsClassId.isJsArray: Boolean val JsClassId.isJsMap: Boolean get() = this.name == "Map" + +val JsClassId.isJsSet: Boolean + get() = this.name == "Set" diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index e345e1c6e0..53dfbbf130 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -6,6 +6,7 @@ import fuzzer.providers.BoolValueProvider import fuzzer.providers.MapValueProvider import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider +import fuzzer.providers.SetValueProvider import fuzzer.providers.StringValueProvider import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzing.Fuzzing @@ -17,6 +18,7 @@ fun defaultValueProviders() = listOf( NumberValueProvider, StringValueProvider, MapValueProvider, + SetValueProvider, ObjectValueProvider(), ArrayValueProvider() ) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt new file mode 100644 index 0000000000..829f14bf79 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt @@ -0,0 +1,79 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsMethodId +import framework.api.js.util.isJsSet +import framework.api.js.util.jsBasic +import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +object SetValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsSet + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + val modifications = mutableListOf>() + jsBasic.forEach { typeParameter -> + modifications += Routine.Call(listOf(typeParameter)) { instance, arguments -> + val model = instance.model as UtAssembleModel + (model).modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "add", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId) + ), + arguments.map { it.model } + ) + } + } + yield( + Seed.Recursive( + construct = Routine.Create(listOf(jsUndefinedClassId)) { + UtAssembleModel( + id = JsIdProvider.get(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + empty = Routine.Empty { + UtAssembleModel( + id = JsIdProvider.get(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ) + ).fuzzed { + summary = "%var% = collection" + } + }, + modify = modifications.asSequence() + ) + ) + } +} diff --git a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt index 08a6c41d49..bb50667b3b 100644 --- a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt +++ b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt @@ -21,27 +21,30 @@ class JsFuzzerAstVisitor : IAstVisitor { NodeUtil.visitPreOrder(rootNode) { node -> val currentFuzzedOp = node.toFuzzedContextComparisonOrNull() when { - node.isCase -> validateNode(node.firstChild?.getAnyValue()) + node.isCase -> validateNode(node.firstChild?.getAnyValue(), FuzzedContext.Comparison.NE) + node.isCall -> { + validateNode(node.getAnyValue(), FuzzedContext.Comparison.NE) + } currentFuzzedOp != null -> { lastFuzzedOpGlobal = currentFuzzedOp - validateNode(node.getBinaryExprLeftOperand().getAnyValue()) + validateNode(node.getBinaryExprLeftOperand().getAnyValue(), lastFuzzedOpGlobal) lastFuzzedOpGlobal = if (lastFuzzedOpGlobal is FuzzedContext.Comparison) (lastFuzzedOpGlobal as FuzzedContext.Comparison).reverse() else FuzzedContext.Unknown - validateNode(node.getBinaryExprRightOperand().getAnyValue()) + validateNode(node.getBinaryExprRightOperand().getAnyValue(), lastFuzzedOpGlobal) } } } } - private fun validateNode(value: Any?) { + private fun validateNode(value: Any?, fuzzedOp: FuzzedContext) { when (value) { is String -> { fuzzedConcreteValues.add( FuzzedConcreteValue( jsStringClassId, value.toString(), - lastFuzzedOpGlobal + fuzzedOp ) ) } @@ -51,14 +54,14 @@ class JsFuzzerAstVisitor : IAstVisitor { FuzzedConcreteValue( jsBooleanClassId, value, - lastFuzzedOpGlobal + fuzzedOp ) ) } is Double -> { - fuzzedConcreteValues.add(FuzzedConcreteValue(jsDoubleClassId, value, lastFuzzedOpGlobal)) + fuzzedConcreteValues.add(FuzzedConcreteValue(jsDoubleClassId, value, fuzzedOp)) } } } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 44e69e01f2..3ed4d7b780 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -61,7 +61,6 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) init { with(context) { setupTernEnv("$projectPath/$utbotDir") - installDeps("$projectPath/$utbotDir") runTypeInferencer() } } @@ -70,14 +69,6 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) private fun generateImportsSection(): String = importProvider.ternScriptImports - private fun installDeps(path: String) { - JsCmdExec.runCommand( - dir = path, - shouldWait = true, - cmd = arrayOf("\"${settings.pathToNPM}\"", "i", "tern", "-l"), - ) - } - private fun setupTernEnv(path: String) { File(path).mkdirs() val ternScriptFile = File("$path/ternScript.js") From 4fb92f2d861d89cd12f5073c9c664ccc1a3bdebb Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 3 Mar 2023 18:08:10 +0300 Subject: [PATCH 51/74] Removed extra dep --- utbot-js/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/utbot-js/build.gradle.kts b/utbot-js/build.gradle.kts index 0c365b0be5..0dfcdc95c0 100644 --- a/utbot-js/build.gradle.kts +++ b/utbot-js/build.gradle.kts @@ -29,7 +29,6 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") api(project(":utbot-framework")) - api(project(":utbot-fuzzers")) // https://mvnrepository.com/artifact/com.google.javascript/closure-compiler implementation("com.google.javascript:closure-compiler:v20221102") From 636a406eb725435e40a79a4aa77fe26c51442348 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 3 Mar 2023 18:18:41 +0300 Subject: [PATCH 52/74] [WIP] removing "fuzzers" dep form JavaScript --- .../src/main/kotlin/api/JsTestGenerator.kt | 4 ++-- utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt | 18 ++++++++++++++---- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 2 +- .../fuzzer/providers/ArrayValueProvider.kt | 4 ++-- .../fuzzer/providers/BoolValueProvider.kt | 4 ++-- .../fuzzer/providers/MapValueProvider.kt | 4 ++-- .../fuzzer/providers/NumberValueProvider.kt | 14 +++++++------- .../fuzzer/providers/ObjectValueProvider.kt | 4 ++-- .../fuzzer/providers/SetValueProvider.kt | 4 ++-- .../fuzzer/providers/StringValueProvider.kt | 4 ++-- .../main/kotlin/parser/JsFuzzerAstVisitor.kt | 4 ++-- .../src/main/kotlin/parser/JsParserUtils.kt | 2 +- .../coverage/CoverageServiceProvider.kt | 2 +- 13 files changed, 40 insertions(+), 30 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index ecc9d3c250..828f7103ac 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -30,8 +30,8 @@ import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException 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.JsAstScrapper diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt index 4476c2a5c6..474a2a98b8 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -3,10 +3,9 @@ package fuzzer import framework.api.js.JsClassId import framework.api.js.util.isClass import java.util.concurrent.atomic.AtomicInteger +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtTimeoutException -import org.utbot.fuzzer.FuzzedConcreteValue -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control import org.utbot.fuzzing.Description import org.utbot.fuzzing.Feedback @@ -43,7 +42,7 @@ class JsMethodDescription( class JsFeedback( override val control: Control = Control.CONTINUE, val result: Trie.Node = Trie.emptyNode() -) : Feedback { +) : Feedback { override fun equals(other: Any?): Boolean { val castOther = other as? JsFeedback @@ -59,6 +58,17 @@ data class JsStatement( val number: Int ) +data class JsFuzzedValue( + val model: UtModel, + val summary: String? = null, +) + +data class JsFuzzedConcreteValue( + val classId: ClassId, + val value: Any, + val fuzzedContext: FuzzedContext = FuzzedContext.Unknown, +) + object JsIdProvider { private var _id = AtomicInteger(0) diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index 53dfbbf130..90cc58dec4 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -8,7 +8,7 @@ import fuzzer.providers.NumberValueProvider import fuzzer.providers.ObjectValueProvider import fuzzer.providers.SetValueProvider import fuzzer.providers.StringValueProvider -import org.utbot.fuzzer.FuzzedValue + import org.utbot.fuzzing.Fuzzing import org.utbot.fuzzing.Seed import org.utbot.fuzzing.fuzz diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index 70fda93be9..e829e3e5b7 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -6,8 +6,8 @@ import framework.api.js.util.isJsArray import fuzzer.JsIdProvider import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed + + import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt index 3c889bdb41..016dc2d4b7 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt @@ -4,8 +4,8 @@ import framework.api.js.JsClassId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic import fuzzer.JsMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed + + import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.seeds.Bool diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt index 00d7a76154..56c85f98fe 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt @@ -10,8 +10,8 @@ import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed + + import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt index 33862d3019..b3c18b9e7a 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt @@ -4,13 +4,13 @@ import framework.api.js.JsClassId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic import fuzzer.JsMethodDescription -import org.utbot.fuzzer.FuzzedContext.Comparison.EQ -import org.utbot.fuzzer.FuzzedContext.Comparison.GE -import org.utbot.fuzzer.FuzzedContext.Comparison.GT -import org.utbot.fuzzer.FuzzedContext.Comparison.LE -import org.utbot.fuzzer.FuzzedContext.Comparison.LT -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed + + + + + + + import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.seeds.DefaultFloatBound diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt index 10414afc88..fa7e478de5 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt @@ -8,8 +8,8 @@ import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed + + import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt index 829f14bf79..8966af1a2a 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt @@ -10,8 +10,8 @@ import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed + + import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt index ba07ae1749..8adc21e5d9 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt @@ -5,8 +5,8 @@ import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic import framework.api.js.util.jsStringClassId import fuzzer.JsMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed + + import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.seeds.StringValue diff --git a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt index bb50667b3b..9ec5188104 100644 --- a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt +++ b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt @@ -5,8 +5,8 @@ import com.google.javascript.rhino.Node import framework.api.js.util.jsBooleanClassId import framework.api.js.util.jsDoubleClassId import framework.api.js.util.jsStringClassId -import org.utbot.fuzzer.FuzzedConcreteValue -import org.utbot.fuzzer.FuzzedContext + + import parser.JsParserUtils.getAnyValue import parser.JsParserUtils.getBinaryExprLeftOperand import parser.JsParserUtils.getBinaryExprRightOperand diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index bb1f66c55b..2f769a33e9 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -5,7 +5,7 @@ import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import java.lang.IllegalStateException -import org.utbot.fuzzer.FuzzedContext + import parser.JsParserUtils.getMethodName // TODO: make methods more safe by checking the Node method is called on. diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 9bd25a4fa8..ff8813aac4 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -10,7 +10,7 @@ import java.lang.StringBuilder import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.isStatic -import org.utbot.fuzzer.FuzzedValue + import service.ContextOwner import service.InstrumentationService import service.ServiceContext From 398d9d428b937b43cdb4a97118883513c154cfc2 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 3 Mar 2023 19:49:38 +0300 Subject: [PATCH 53/74] [WIP] No "fuzzers" dep in JavaScript (except UtFuzzedExecution) --- .../plugin/language/js/CoverageModeButtons.kt | 2 +- .../src/main/kotlin/api/JsTestGenerator.kt | 8 ++--- .../src/main/kotlin/framework/api/js/JsApi.kt | 6 +--- .../tree/JsCgVariableConstructor.kt | 2 +- .../src/main/kotlin/fuzzer/JsFuzzerApi.kt | 31 ++++++++++++++++--- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 15 +++++---- .../fuzzer/providers/ArrayValueProvider.kt | 8 ++--- .../fuzzer/providers/BoolValueProvider.kt | 6 ++-- .../fuzzer/providers/MapValueProvider.kt | 8 +++-- .../fuzzer/providers/NumberValueProvider.kt | 18 +++++------ .../fuzzer/providers/ObjectValueProvider.kt | 6 ++-- .../fuzzer/providers/SetValueProvider.kt | 10 +++--- .../fuzzer/providers/StringValueProvider.kt | 6 ++-- .../main/kotlin/parser/JsFuzzerAstVisitor.kt | 21 +++++++------ .../src/main/kotlin/parser/JsParserUtils.kt | 18 +++++------ .../coverage/CoverageServiceProvider.kt | 13 ++++---- 16 files changed, 103 insertions(+), 75 deletions(-) diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt index f44ca170d7..874a3f1fe9 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/CoverageModeButtons.kt @@ -1,8 +1,8 @@ package org.utbot.intellij.plugin.language.js -import service.CoverageMode import javax.swing.ButtonGroup import javax.swing.JRadioButton +import service.coverage.CoverageMode object CoverageModeButtons { diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 828f7103ac..a1b9a089fd 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -9,6 +9,7 @@ import framework.api.js.util.isJsBasic import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId import fuzzer.JsFeedback +import fuzzer.JsFuzzedValue import fuzzer.JsFuzzingExecutionFeedback import fuzzer.JsMethodDescription import fuzzer.JsStatement @@ -30,8 +31,7 @@ import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtTimeoutException - - +import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control import org.utbot.fuzzing.utils.Trie import parser.JsAstScrapper @@ -190,7 +190,7 @@ class JsTestGenerator( private fun getUtModelResult( execId: JsMethodId, resultData: ResultData, - fuzzedValues: List + fuzzedValues: List ): UtExecutionResult { if (resultData.isError && resultData.rawString == "Timeout") return UtTimeoutException( TimeoutException(" Timeout in generating test for ${ @@ -228,7 +228,7 @@ class JsTestGenerator( concreteValues = fuzzerVisitor.fuzzedConcreteValues, tracer = Trie(JsStatement::number) ) - val collectedValues = mutableListOf>() + val collectedValues = mutableListOf>() // .location field gets us "jsFile:A:B", then we get A and B as ints val funcLocation = funcNode.firstChild!!.location.substringAfter("jsFile:") .split(":").map { it.toInt() } diff --git a/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt b/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt index 1e7f19b528..ee5eb7858b 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt @@ -93,11 +93,7 @@ class JsConstructorId( get() = 0 } -class JsMultipleClassId(private val jsJoinedName: String) : JsClassId(jsJoinedName) { - - val types: Sequence - get() = jsJoinedName.split('|').map { JsClassId(it) }.asSequence() -} +class JsMultipleClassId(jsJoinedName: String) : JsClassId(jsJoinedName) open class JsUtModel( override val classId: JsClassId diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt index db7db14eee..e95cc06b86 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt @@ -91,7 +91,7 @@ class JsCgVariableConstructor(ctx: CgContext) : CgVariableConstructor(ctx) { } } - internal fun loopInitialization( + private fun loopInitialization( variableType: ClassId, baseVariableName: String, initializer: Any? diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt index 474a2a98b8..4a8bd41b96 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -6,6 +6,7 @@ import java.util.concurrent.atomic.AtomicInteger import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control import org.utbot.fuzzing.Description import org.utbot.fuzzing.Feedback @@ -19,7 +20,7 @@ class JsTimeoutExecution(val utTimeout: UtTimeoutException) : JsFuzzingExecution class JsMethodDescription( val name: String, parameters: List, - val concreteValues: Collection, + val concreteValues: Collection, val thisInstance: JsClassId? = null, val tracer: Trie ) : Description(parameters) { @@ -28,7 +29,7 @@ class JsMethodDescription( name: String, parameters: List, classId: JsClassId, - concreteValues: Collection, + concreteValues: Collection, tracer: Trie ) : this( name, @@ -60,15 +61,37 @@ data class JsStatement( data class JsFuzzedValue( val model: UtModel, - val summary: String? = null, + var summary: String? = null, ) data class JsFuzzedConcreteValue( val classId: ClassId, val value: Any, - val fuzzedContext: FuzzedContext = FuzzedContext.Unknown, + val fuzzedContext: JsFuzzedContext = JsFuzzedContext.Unknown, ) +enum class JsFuzzedContext { + EQ, + NE, + GT, + GE, + LT, + LE, + Unknown; + + fun reverse(): JsFuzzedContext = when (this) { + EQ -> NE + NE -> EQ + GT -> LE + LT -> GE + LE -> GT + GE -> LT + Unknown -> Unknown + } +} + +fun UtModel.fuzzed(block: JsFuzzedValue.() -> Unit = {}): JsFuzzedValue = JsFuzzedValue(this).apply(block) + object JsIdProvider { private var _id = AtomicInteger(0) diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index 90cc58dec4..c43b06fbcd 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -1,17 +1,16 @@ package fuzzer import framework.api.js.JsClassId -import fuzzer.providers.ArrayValueProvider import fuzzer.providers.BoolValueProvider import fuzzer.providers.MapValueProvider import fuzzer.providers.NumberValueProvider -import fuzzer.providers.ObjectValueProvider import fuzzer.providers.SetValueProvider import fuzzer.providers.StringValueProvider - import org.utbot.fuzzing.Fuzzing import org.utbot.fuzzing.Seed import org.utbot.fuzzing.fuzz +import fuzzer.providers.ArrayValueProvider +import fuzzer.providers.ObjectValueProvider fun defaultValueProviders() = listOf( BoolValueProvider, @@ -24,10 +23,10 @@ fun defaultValueProviders() = listOf( ) class JsFuzzing( - val exec: suspend (JsMethodDescription, List) -> JsFeedback -) : Fuzzing { + val exec: suspend (JsMethodDescription, List) -> JsFeedback +) : Fuzzing { - override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> { + override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> { return defaultValueProviders().asSequence().flatMap { provider -> if (provider.accept(type)) { provider.generate(description, type) @@ -37,12 +36,12 @@ class JsFuzzing( } } - override suspend fun handle(description: JsMethodDescription, values: List): JsFeedback { + override suspend fun handle(description: JsMethodDescription, values: List): JsFeedback { return exec(description, values) } } suspend fun runFuzzing( description: JsMethodDescription, - exec: suspend (JsMethodDescription, List) -> JsFeedback + exec: suspend (JsMethodDescription, List) -> JsFeedback ) = JsFuzzing(exec).fuzz(description) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index e829e3e5b7..631de09d51 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -3,23 +3,23 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.util.defaultJsValueModel import framework.api.js.util.isJsArray +import fuzzer.JsFuzzedValue import fuzzer.JsIdProvider import fuzzer.JsMethodDescription +import fuzzer.fuzzed import org.utbot.framework.plugin.api.UtArrayModel - - import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -class ArrayValueProvider : ValueProvider { +class ArrayValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean = type.isJsArray override fun generate( description: JsMethodDescription, type: JsClassId - ) = sequence> { + ) = sequence> { yield( Seed.Collection( construct = Routine.Collection { diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt index 016dc2d4b7..90fd2f7b24 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt @@ -3,20 +3,22 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic +import fuzzer.JsFuzzedValue import fuzzer.JsMethodDescription +import fuzzer.fuzzed import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.seeds.Bool -object BoolValueProvider : ValueProvider { +object BoolValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean { return type.isJsBasic } - override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> = + override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> = sequence { yield(Seed.Known(Bool.TRUE()) { JsPrimitiveModel(true).fuzzed { diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt index 56c85f98fe..11d04e85d8 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt @@ -5,8 +5,10 @@ import framework.api.js.JsMethodId import framework.api.js.util.isJsMap import framework.api.js.util.jsBasic import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsFuzzedValue import fuzzer.JsIdProvider import fuzzer.JsMethodDescription +import fuzzer.fuzzed import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel @@ -16,15 +18,15 @@ import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -object MapValueProvider : ValueProvider { +object MapValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean = type.isJsMap override fun generate( description: JsMethodDescription, type: JsClassId - ) = sequence> { - val modifications = mutableListOf>() + ) = sequence> { + val modifications = mutableListOf>() jsBasic.zip(jsBasic).map { (a, b) -> listOf(a, b) }.forEach { typeParameters -> modifications += Routine.Call(typeParameters) { instance, arguments -> val model = instance.model as UtAssembleModel diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt index b3c18b9e7a..0913dca3cc 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt @@ -3,26 +3,26 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic +import fuzzer.JsFuzzedContext.EQ +import fuzzer.JsFuzzedContext.GE +import fuzzer.JsFuzzedContext.GT +import fuzzer.JsFuzzedContext.LE +import fuzzer.JsFuzzedContext.LT +import fuzzer.JsFuzzedValue import fuzzer.JsMethodDescription - - - - - - - +import fuzzer.fuzzed import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.seeds.DefaultFloatBound import org.utbot.fuzzing.seeds.IEEE754Value -object NumberValueProvider : ValueProvider { +object NumberValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean { return type.isJsBasic } - override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> = + override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> = sequence { description.concreteValues.forEach { (_, v, c) -> if (v is Double) { diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt index fa7e478de5..fbc255a0f7 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt @@ -3,8 +3,10 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.JsConstructorId import framework.api.js.util.isClass +import fuzzer.JsFuzzedValue import fuzzer.JsIdProvider import fuzzer.JsMethodDescription +import fuzzer.fuzzed import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtNullModel @@ -15,7 +17,7 @@ import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.utils.hex -class ObjectValueProvider : ValueProvider { +class ObjectValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean { return type.isClass @@ -32,7 +34,7 @@ class ObjectValueProvider : ValueProvider { + ): Seed.Recursive { return Seed.Recursive( construct = Routine.Create(constructorId.parameters) { values -> val id = JsIdProvider.get() diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt index 8966af1a2a..eb80811715 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt @@ -5,26 +5,26 @@ import framework.api.js.JsMethodId import framework.api.js.util.isJsSet import framework.api.js.util.jsBasic import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsFuzzedValue import fuzzer.JsIdProvider import fuzzer.JsMethodDescription +import fuzzer.fuzzed import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel - - import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -object SetValueProvider : ValueProvider { +object SetValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean = type.isJsSet override fun generate( description: JsMethodDescription, type: JsClassId - ) = sequence> { - val modifications = mutableListOf>() + ) = sequence> { + val modifications = mutableListOf>() jsBasic.forEach { typeParameter -> modifications += Routine.Call(listOf(typeParameter)) { instance, arguments -> val model = instance.model as UtAssembleModel diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt index 8adc21e5d9..26ce82a2d7 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt @@ -4,14 +4,16 @@ import framework.api.js.JsClassId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic import framework.api.js.util.jsStringClassId +import fuzzer.JsFuzzedValue import fuzzer.JsMethodDescription +import fuzzer.fuzzed import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.fuzzing.seeds.StringValue -object StringValueProvider : ValueProvider { +object StringValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean { return type.isJsBasic @@ -20,7 +22,7 @@ object StringValueProvider : ValueProvider> = sequence { + ): Sequence> = sequence { val constants = description.concreteValues.asSequence() .filter { it.classId == jsStringClassId } val values = constants diff --git a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt index 9ec5188104..e6fbc7055f 100644 --- a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt +++ b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt @@ -5,6 +5,8 @@ import com.google.javascript.rhino.Node import framework.api.js.util.jsBooleanClassId import framework.api.js.util.jsDoubleClassId import framework.api.js.util.jsStringClassId +import fuzzer.JsFuzzedConcreteValue +import fuzzer.JsFuzzedContext import parser.JsParserUtils.getAnyValue @@ -14,22 +16,21 @@ import parser.JsParserUtils.toFuzzedContextComparisonOrNull class JsFuzzerAstVisitor : IAstVisitor { - private var lastFuzzedOpGlobal: FuzzedContext = FuzzedContext.Unknown - val fuzzedConcreteValues = mutableSetOf() + private var lastFuzzedOpGlobal: JsFuzzedContext = JsFuzzedContext.Unknown + val fuzzedConcreteValues = mutableSetOf() override fun accept(rootNode: Node) { NodeUtil.visitPreOrder(rootNode) { node -> val currentFuzzedOp = node.toFuzzedContextComparisonOrNull() when { - node.isCase -> validateNode(node.firstChild?.getAnyValue(), FuzzedContext.Comparison.NE) + node.isCase -> validateNode(node.firstChild?.getAnyValue(), JsFuzzedContext.NE) node.isCall -> { - validateNode(node.getAnyValue(), FuzzedContext.Comparison.NE) + validateNode(node.getAnyValue(), JsFuzzedContext.NE) } currentFuzzedOp != null -> { lastFuzzedOpGlobal = currentFuzzedOp validateNode(node.getBinaryExprLeftOperand().getAnyValue(), lastFuzzedOpGlobal) - lastFuzzedOpGlobal = if (lastFuzzedOpGlobal is FuzzedContext.Comparison) - (lastFuzzedOpGlobal as FuzzedContext.Comparison).reverse() else FuzzedContext.Unknown + lastFuzzedOpGlobal = lastFuzzedOpGlobal.reverse() validateNode(node.getBinaryExprRightOperand().getAnyValue(), lastFuzzedOpGlobal) } } @@ -37,11 +38,11 @@ class JsFuzzerAstVisitor : IAstVisitor { } - private fun validateNode(value: Any?, fuzzedOp: FuzzedContext) { + private fun validateNode(value: Any?, fuzzedOp: JsFuzzedContext) { when (value) { is String -> { fuzzedConcreteValues.add( - FuzzedConcreteValue( + JsFuzzedConcreteValue( jsStringClassId, value.toString(), fuzzedOp @@ -51,7 +52,7 @@ class JsFuzzerAstVisitor : IAstVisitor { is Boolean -> { fuzzedConcreteValues.add( - FuzzedConcreteValue( + JsFuzzedConcreteValue( jsBooleanClassId, value, fuzzedOp @@ -60,7 +61,7 @@ class JsFuzzerAstVisitor : IAstVisitor { } is Double -> { - fuzzedConcreteValues.add(FuzzedConcreteValue(jsDoubleClassId, value, fuzzedOp)) + fuzzedConcreteValues.add(JsFuzzedConcreteValue(jsDoubleClassId, value, fuzzedOp)) } } } diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 2f769a33e9..b7522207ab 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -4,8 +4,8 @@ import com.google.javascript.jscomp.Compiler import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node +import fuzzer.JsFuzzedContext import java.lang.IllegalStateException - import parser.JsParserUtils.getMethodName // TODO: make methods more safe by checking the Node method is called on. @@ -82,14 +82,14 @@ object JsParserUtils { /** * Called upon node with any kind of binary comparison token. */ - fun Node.toFuzzedContextComparisonOrNull(): FuzzedContext.Comparison? = when { - this.isEQ -> FuzzedContext.Comparison.EQ - this.isNE -> FuzzedContext.Comparison.NE - this.token.name == "LT" -> FuzzedContext.Comparison.LT - this.token.name == "GT" -> FuzzedContext.Comparison.GT - this.token.name == "LE" -> FuzzedContext.Comparison.LE - this.token.name == "GE" -> FuzzedContext.Comparison.GE - this.token.name == "SHEQ" -> FuzzedContext.Comparison.EQ + fun Node.toFuzzedContextComparisonOrNull(): JsFuzzedContext? = when { + this.isEQ -> JsFuzzedContext.EQ + this.isNE -> JsFuzzedContext.NE + this.token.name == "LT" -> JsFuzzedContext.LT + this.token.name == "GT" -> JsFuzzedContext.GT + this.token.name == "LE" -> JsFuzzedContext.LE + this.token.name == "GE" -> JsFuzzedContext.GE + this.token.name == "SHEQ" -> JsFuzzedContext.EQ else -> null } diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index ff8813aac4..e2f0917b7e 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -5,6 +5,7 @@ import framework.api.js.JsMethodId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isExportable import framework.api.js.util.isUndefined +import fuzzer.JsFuzzedValue import fuzzer.JsMethodDescription import java.lang.StringBuilder import org.utbot.framework.plugin.api.UtAssembleModel @@ -63,7 +64,7 @@ function check_value(value, json) { } fun get( - fuzzedValues: List>, + fuzzedValues: List>, execId: JsMethodId, ): Pair, List> { return when (mode) { @@ -80,7 +81,7 @@ function check_value(value, json) { } private fun runBasicCoverageAnalysis( - fuzzedValues: List>, + fuzzedValues: List>, execId: JsMethodId, ): Pair, List> { val covFunName = instrumentationService.covFunName @@ -104,7 +105,7 @@ function check_value(value, json) { } private fun runFastCoverageAnalysis( - fuzzedValues: List>, + fuzzedValues: List>, execId: JsMethodId, ): Pair, List> { val covFunName = instrumentationService.covFunName @@ -139,7 +140,7 @@ fs.writeFileSync("$resFilePath", JSON.stringify(json)) } private fun makeStringForRunJs( - fuzzedValue: List, + fuzzedValue: List, method: JsMethodId, containingClass: String?, covFunName: String, @@ -171,7 +172,7 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) } private fun makeCallFunctionString( - fuzzedValue: List, + fuzzedValue: List, method: JsMethodId, containingClass: String?, index: Int @@ -192,7 +193,7 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) return paramsInit + callString } - private fun initParams(fuzzedValue: List): String { + private fun initParams(fuzzedValue: List): String { val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue return actualParams.mapIndexed { index, param -> val varName = "param$index" From 6a1c884fc8a868c029753953147fc7c604d73333 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 6 Mar 2023 09:30:06 +0300 Subject: [PATCH 54/74] Removed all "org.utbot.fuzzer" deps in utbot-js module --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 6 +++--- .../kotlin/framework/api/js/JsUtFuzzedExecution.kt | 11 +++++++++++ utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index a1b9a089fd..730035949b 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -4,6 +4,7 @@ import codegen.JsCodeGenerator import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMethodId +import framework.api.js.JsUtFuzzedExecution import framework.api.js.util.isExportable import framework.api.js.util.isJsBasic import framework.api.js.util.jsErrorClassId @@ -31,7 +32,6 @@ import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtTimeoutException -import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control import org.utbot.fuzzing.utils.Trie import parser.JsAstScrapper @@ -224,7 +224,7 @@ class JsTestGenerator( val jsDescription = JsMethodDescription( name = funcNode.getAbstractFunctionName(), parameters = execId.parameters, - execId.classId, + classId = execId.classId, concreteValues = fuzzerVisitor.fuzzedConcreteValues, tracer = Trie(JsStatement::number) ) @@ -274,7 +274,7 @@ class JsTestGenerator( EnvironmentModels(thisObject, modelList, mapOf()) emit( JsValidExecution( - UtFuzzedExecution( + JsUtFuzzedExecution( stateBefore = initEnv, stateAfter = initEnv, result = result, diff --git a/utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt b/utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt new file mode 100644 index 0000000000..e1540955dc --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt @@ -0,0 +1,11 @@ +package framework.api.js + +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionResult + +class JsUtFuzzedExecution( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult +) : UtExecution(stateBefore, stateAfter, result, null, null, null, null) diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt index 4a8bd41b96..1e34f6bd08 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -1,19 +1,19 @@ package fuzzer import framework.api.js.JsClassId +import framework.api.js.JsUtFuzzedExecution import framework.api.js.util.isClass import java.util.concurrent.atomic.AtomicInteger import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtTimeoutException -import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control import org.utbot.fuzzing.Description import org.utbot.fuzzing.Feedback import org.utbot.fuzzing.utils.Trie sealed interface JsFuzzingExecutionFeedback -class JsValidExecution(val utFuzzedExecution: UtFuzzedExecution) : JsFuzzingExecutionFeedback +class JsValidExecution(val utFuzzedExecution: JsUtFuzzedExecution) : JsFuzzingExecutionFeedback class JsTimeoutExecution(val utTimeout: UtTimeoutException) : JsFuzzingExecutionFeedback From 1f8a4b5ed798f0d820aa95d8dc72fe109798ea76 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 6 Mar 2023 12:17:04 +0300 Subject: [PATCH 55/74] Fixed test generation action for JavaScript --- .../plugin/language/js/JsDialogProcessor.kt | 69 ++++++++++++------- .../plugin/language/js/JsLanguageAssistant.kt | 14 ++-- 2 files changed, 53 insertions(+), 30 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 f80e550b78..e2eba20acb 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 @@ -46,7 +46,7 @@ object JsDialogProcessor { fileMethods: Set, focusedMethod: JSMemberInfo?, containingFilePath: String, - editor: Editor, + editor: Editor?, file: JSFile ) { val model = @@ -64,12 +64,19 @@ object JsDialogProcessor { createDialog(model)?.let { dialogWindow -> if (!dialogWindow.showAndGet()) return@invokeLater // Since Tern.js accesses containing file, sync with file system required before test generation. - runWriteAction { - with(FileDocumentManager.getInstance()) { - saveDocument(editor.document) + editor?.let { + runWriteAction { + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } } } - createTests(dialogWindow.model, containingFilePath, editor) + createTests( + dialogWindow.model, + containingFilePath, + editor, + dialogWindow.model.file.getContent() + ) } } } @@ -144,7 +151,7 @@ object JsDialogProcessor { } } - private fun createTests(model: JsTestsModel, containingFilePath: String, editor: Editor) { + private fun createTests(model: JsTestsModel, containingFilePath: String, editor: Editor?, contents: String) { val normalizedContainingFilePath = containingFilePath.replace(File.separator, "/") (object : Task.Backgroundable(model.project, "Generate tests") { override fun run(indicator: ProgressIndicator) { @@ -154,7 +161,8 @@ object JsDialogProcessor { model.testSourceRoot!! ) val testFileName = normalizedContainingFilePath.substringAfterLast("/").replace(Regex(".js"), "Test.js") - val testGenerator = JsTestGenerator(fileText = editor.document.text, + val testGenerator = JsTestGenerator( + fileText = contents, sourceFilePath = normalizedContainingFilePath, projectPath = model.project.basePath?.replace(File.separator, "/") ?: throw IllegalStateException("Can't access project path."), @@ -169,7 +177,7 @@ object JsDialogProcessor { }, outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), exportsManager = partialApplication( - JsDialogProcessor::manageExports, editor, project, editor.document.text + JsDialogProcessor::manageExports, editor, project, model ), settings = JsDynamicSettings( pathToNode = model.pathToNode, @@ -203,25 +211,32 @@ object JsDialogProcessor { return { d: D, e: E -> f(a, b, c, d, e) } } + private fun JSFile.getContent(): String = this.viewProvider.contents.toString() + private fun manageExports( - editor: Editor, project: Project, fileText: String, exports: List, swappedText: (String?) -> String + editor: Editor?, project: Project, model: JsTestsModel, exports: List, swappedText: (String?) -> String ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { + val fileText = model.file.getContent() when { fileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> val newText = swappedText(existingSection) - runWriteAction { - with(editor.document) { - unblockDocument(project, this) - setText(newText) - unblockDocument(project, this) - } - with(FileDocumentManager.getInstance()) { - saveDocument(editor.document) + editor?.let { + runWriteAction { + with(editor.document) { + unblockDocument(project, this) + setText(newText) + unblockDocument(project, this) + } + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } } + } ?: run { + File(model.containingFilePath).writeText(newText) } } } @@ -232,15 +247,19 @@ object JsDialogProcessor { append(swappedText(null)) append(endComment) } - runWriteAction { - with(editor.document) { - unblockDocument(project, this) - setText(fileText + line) - unblockDocument(project, this) - } - with(FileDocumentManager.getInstance()) { - saveDocument(editor.document) + editor?.let { + runWriteAction { + with(editor.document) { + unblockDocument(project, this) + setText(fileText + line) + unblockDocument(project, this) + } + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } } + } ?: run { + File(model.containingFilePath).writeText(fileText + line) } } } diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsLanguageAssistant.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsLanguageAssistant.kt index 102a339270..22ac365be2 100644 --- a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsLanguageAssistant.kt +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsLanguageAssistant.kt @@ -29,7 +29,7 @@ object JsLanguageAssistant : LanguageAssistant() { val focusedMethod: JSMemberInfo?, val module: Module, val containingFilePath: String, - val editor: Editor, + val editor: Editor?, val file: JSFile ) @@ -53,11 +53,15 @@ object JsLanguageAssistant : LanguageAssistant() { private fun getPsiTargets(e: AnActionEvent): PsiTargets? { e.project ?: return null - val virtualFile = (e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return null).path - val editor = e.getData(CommonDataKeys.EDITOR) ?: return null + val editor = e.getData(CommonDataKeys.EDITOR) val file = e.getData(CommonDataKeys.PSI_FILE) as? JSFile ?: return null - val element = findPsiElement(file, editor) ?: return null + val element = if (editor != null) { + findPsiElement(file, editor) ?: return null + } else { + e.getData(CommonDataKeys.PSI_ELEMENT) ?: return null + } val module = element.module ?: return null + val virtualFile = (e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return null).path val focusedMethod = getContainingMethod(element) containingClass(element)?.let { val methods = it.functions @@ -143,4 +147,4 @@ object JsLanguageAssistant : LanguageAssistant() { JSMemberInfo.extractClassMembers(clazz!!, res) { true } return res.toSet() } -} \ No newline at end of file +} From 1ea6965db35525544b1175153fa57ef41a6f225f Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Mon, 6 Mar 2023 12:17:31 +0300 Subject: [PATCH 56/74] Fixed temp files generation for multiple params --- .../src/main/kotlin/service/coverage/CoverageServiceProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index e2f0917b7e..f3ef95894b 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -201,7 +201,7 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) appendLine("let $varName = ${param.model.initModelAsString()}") (param.model as? UtAssembleModel)?.initModificationsAsString(this, varName) } - }.joinToString() + }.joinToString(separator = "\n") } private fun Any.quoteWrapIfNecessary(): String = From fff737e6d90e3c4329fc7d790e4d30e66e6cfaf7 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Mon, 6 Mar 2023 17:51:12 +0300 Subject: [PATCH 57/74] Fix imports --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 696e9b3069..cb955856af 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -17,8 +17,6 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing -import java.io.File -import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -35,9 +33,6 @@ import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.fuzzing.Control import org.utbot.fuzzing.utils.Trie import parser.JsAstScrapper -import org.utbot.fuzzing.utils.Trie -import parser.JsClassAstVisitor -import parser.JsFunctionAstVisitor import parser.JsFuzzerAstVisitor import parser.JsParserUtils import parser.JsParserUtils.getAbstractFunctionName @@ -47,7 +42,10 @@ import parser.JsParserUtils.getClassName import parser.JsParserUtils.getParamName import parser.JsParserUtils.runParser import parser.JsToplevelFunctionAstVisitor +import providers.exports.IExportsProvider import service.InstrumentationService +import service.PackageJson +import service.PackageJsonService import service.ServiceContext import service.TernService import service.coverage.CoverageMode @@ -59,6 +57,9 @@ import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny +import java.io.File +import java.util.concurrent.CancellationException + private val logger = KotlinLogging.logger {} class JsTestGenerator( From 60129b1e1ac24df5f58053b3a3927daca00e539f Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 7 Mar 2023 14:41:09 +0300 Subject: [PATCH 58/74] Implemented package.json based imports for CjJsRenderer --- .../src/main/kotlin/api/JsTestGenerator.kt | 20 ++++++++++++- .../main/kotlin/codegen/JsCodeGenerator.kt | 13 ++------- .../main/kotlin/framework/codegen/JsDomain.kt | 28 +++++++++++++++++++ .../model/constructor/visitor/CgJsRenderer.kt | 17 ++++++++--- .../src/main/kotlin/parser/JsAstScrapper.kt | 7 +++-- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index cb955856af..b4b54d76bb 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -9,6 +9,8 @@ import framework.api.js.util.isExportable import framework.api.js.util.isJsBasic import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId +import framework.codegen.JsImport +import framework.codegen.ModuleType import fuzzer.JsFeedback import fuzzer.JsFuzzedValue import fuzzer.JsFuzzingExecutionFeedback @@ -59,6 +61,7 @@ import utils.data.ResultData import utils.toJsAny import java.io.File import java.util.concurrent.CancellationException +import settings.JsTestGenerationSettings.fileUnderTestAliases private val logger = KotlinLogging.logger {} @@ -125,10 +128,25 @@ class JsTestGenerator( makeTestsForMethod(classId, funcNode, classNode, context, testSets, paramNames) } val importPrefix = makeImportPrefix() + val moduleType = ModuleType.fromPackageJson(context.packageJson) + val imports = listOf( + JsImport( + "*", + fileUnderTestAliases, + "./$importPrefix/${sourceFilePath.substringAfterLast("/")}", + moduleType + ), + JsImport( + "*", + "assert", + "assert", + moduleType + ) + ) val codeGen = JsCodeGenerator( classUnderTest = classId, paramNames = paramNames, - importPrefix = importPrefix + imports = imports ) return codeGen.generateAsStringWithTestReport(testSets).generatedCode } diff --git a/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt b/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt index 1087b56e29..ae67e0f929 100644 --- a/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt +++ b/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt @@ -2,12 +2,12 @@ package codegen import framework.api.js.JsClassId import framework.codegen.JsCgLanguageAssistant +import framework.codegen.JsImport import framework.codegen.Mocha import org.utbot.framework.codegen.CodeGeneratorResult import org.utbot.framework.codegen.domain.ForceStaticMocking import org.utbot.framework.codegen.domain.HangingTestsTimeout import org.utbot.framework.codegen.domain.ParametrizedTestSource -import org.utbot.framework.codegen.domain.RegularImport import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.StaticsMocking import org.utbot.framework.codegen.domain.TestFramework @@ -20,7 +20,6 @@ import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MockFramework -import settings.JsTestGenerationSettings.fileUnderTestAliases class JsCodeGenerator( private val classUnderTest: JsClassId, @@ -30,7 +29,7 @@ class JsCodeGenerator( hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), enableTestsTimeout: Boolean = true, testClassPackageName: String = classUnderTest.packageName, - importPrefix: String, + imports: List, ) { private var context: CgContext = CgContext( classUnderTest = classUnderTest, @@ -47,13 +46,7 @@ class JsCodeGenerator( hangingTestsTimeout = hangingTestsTimeout, enableTestsTimeout = enableTestsTimeout, testClassPackageName = testClassPackageName, - collectedImports = mutableSetOf( - RegularImport("assert", "assert"), - RegularImport( - fileUnderTestAliases, - "./$importPrefix/${classUnderTest.filePath.substringAfterLast("/")}" - ) - ) + collectedImports = imports.toMutableSet() ) fun generateAsStringWithTestReport( diff --git a/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt b/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt index 9e84e24f1d..8c59af2c34 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt @@ -6,7 +6,9 @@ import org.utbot.framework.plugin.api.ClassId import framework.api.js.JsClassId import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId +import org.utbot.framework.codegen.domain.Import import org.utbot.framework.codegen.domain.TestFramework +import service.PackageJson object Mocha : TestFramework(id = "Mocha", displayName = "Mocha") { @@ -74,3 +76,29 @@ internal val jsAssertThrows by lazy { ) ) } + +enum class ModuleType { + MODULE, + PLAIN; + // TODO: review commonjs + //COMMONJS + + companion object { + fun fromPackageJson(packageJson: PackageJson): ModuleType { + return when (packageJson.isModule) { + true -> MODULE + else -> PLAIN + } + } + } +} + +data class JsImport( + val name: String, + val aliases: String, + val path: String, + val type: ModuleType +): Import(2) { + + override val qualifiedName: String = "$name as $aliases from $path" +} diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index 28f02ffe18..9880a058a9 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -2,6 +2,8 @@ package framework.codegen.model.constructor.visitor import framework.api.js.JsClassId import framework.api.js.util.isExportable +import framework.codegen.JsImport +import framework.codegen.ModuleType import org.apache.commons.text.StringEscapeUtils import org.utbot.framework.codegen.domain.RegularImport import org.utbot.framework.codegen.domain.StaticImport @@ -111,6 +113,10 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP else -> "$this" } + override fun renderRegularImport(regularImport: RegularImport) { + println("const ${regularImport.packageName} = require(\"${regularImport.className}\")") + } + override fun visit(element: CgStaticsRegion) { if (element.content.isEmpty()) return @@ -204,8 +210,8 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP } override fun visit(element: CgClassFile) { - element.imports.filterIsInstance().forEach { - renderRegularImport(it) + element.imports.filterIsInstance().forEach { + renderImport(it) } println() element.declaredClass.accept(this) @@ -259,8 +265,11 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP print(")") } - override fun renderRegularImport(regularImport: RegularImport) { - println("const ${regularImport.packageName} = require(\"${regularImport.className}\")") + private fun renderImport(import: JsImport) = with(import) { + when (type) { + ModuleType.PLAIN -> println("const $aliases = require(\"$path\")") + ModuleType.MODULE -> println("import $name as $aliases from \"$path\"") + } } override fun renderStaticImport(staticImport: StaticImport) { diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt index f90de9a48d..a22b232cf2 100644 --- a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -139,10 +139,11 @@ class JsAstScrapper( val importNodes: List get() = _importNodes.toList() + // TODO: commented for release since features are incomplete override fun accept(rootNode: Node) { - NodeUtil.visitPreOrder(rootNode) { node -> - if (node.isImport || node.isRequireImport()) _importNodes += node - } +// NodeUtil.visitPreOrder(rootNode) { node -> +// if (node.isImport || node.isRequireImport()) _importNodes += node +// } } } } From ea5a3aee162855339fe03dc72868f1db17de46f1 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 17 Feb 2023 13:28:09 +0300 Subject: [PATCH 59/74] Reworked exports manager + fixed non-indexing test files --- .../utbot/cli/js/JsGenerateTestsCommand.kt | 24 ++++++------ .../plugin/language/js/JsDialogProcessor.kt | 37 ++++++++++++------- .../src/main/kotlin/api/JsTestGenerator.kt | 32 ++++++++-------- .../kotlin/framework/api/js/util/JsIdUtil.kt | 7 +++- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt index 3eec14facd..2a0e043216 100644 --- a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt @@ -82,13 +82,14 @@ class JsGenerateTestsCommand : val sourceFileAbsolutePath = makeAbsolutePath(sourceCodeFile) logger.info { "Generating tests for [$sourceFileAbsolutePath] - started" } val fileText = File(sourceCodeFile).readText() + currentFileText = fileText val outputAbsolutePath = output?.let { makeAbsolutePath(it) } val testGenerator = JsTestGenerator( fileText = fileText, sourceFilePath = sourceFileAbsolutePath, parentClassName = targetClass, outputFilePath = outputAbsolutePath, - exportsManager = partialApplication(::manageExports, fileText), + exportsManager = ::manageExports, settings = JsDynamicSettings( pathToNode = pathToNode, pathToNYC = pathToNYC, @@ -118,30 +119,31 @@ class JsGenerateTestsCommand : } } - private fun manageExports(fileText: String, exports: List, swappedText: (String?) -> String) { + // Needed for continuous exports managing + private var currentFileText = "" + + private fun manageExports(swappedText: (String?, String) -> String) { val file = File(sourceCodeFile) when { - fileText.contains(startComment) -> { + currentFileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") - regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> - val newText = swappedText(existingSection) + regex.find(currentFileText)?.groups?.get(1)?.value?.let { existingSection -> + val newText = swappedText(existingSection, currentFileText) file.writeText(newText) + currentFileText = newText } } else -> { val line = buildString { append("\n$startComment") - append(swappedText(null)) + append(swappedText(null, currentFileText)) append(endComment) } file.appendText(line) + currentFileText = file.readText() } } } - - private fun partialApplication(f: (A, B, C) -> Unit, a: A): (B, C) -> Unit { - return { b: B, c: C -> f(a, b, c) } - } -} \ No newline at end of file +} 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 5205301043..94c48594e3 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 @@ -161,6 +161,7 @@ object JsDialogProcessor { model.testSourceRoot!! ) val testFileName = normalizedContainingFilePath.substringAfterLast("/").replace(Regex(".js"), "Test.js") + currentFileText = model.file.getContent() val testGenerator = JsTestGenerator( fileText = contents, sourceFilePath = normalizedContainingFilePath, @@ -194,36 +195,44 @@ object JsDialogProcessor { val generatedCode = testGenerator.run() invokeLater { runWriteAction { - val testPsiFile = testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) - .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + val testPsiFile = testDir.findFile(testFileName) ?: run { + val temp = PsiFileFactory.getInstance(project) + .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + testDir.add(temp) + testDir.findFile(testFileName)!! + } val testFileEditor = CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) unblockDocument(project, testFileEditor.document) testFileEditor.document.setText(generatedCode) unblockDocument(project, testFileEditor.document) - testDir.findFile(testFileName) ?: testDir.add(testPsiFile) } } } }).queue() } - private fun partialApplication(f: (A, B, C, D, E) -> Unit, a: A, b: B, c: C): (D, E) -> Unit { - return { d: D, e: E -> f(a, b, c, d, e) } + private fun partialApplication(f: (A, B, C, D) -> Unit, a: A, b: B, c: C): (D) -> Unit { + return { d: D -> f(a, b, c, d) } } private fun JSFile.getContent(): String = this.viewProvider.contents.toString() + // Needed for continuous exports managing + private var currentFileText = "" + private fun manageExports( - editor: Editor?, project: Project, model: JsTestsModel, exports: List, swappedText: (String?) -> String + editor: Editor?, + project: Project, + model: JsTestsModel, + swappedText: (String?, String) -> String ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { - val fileText = model.file.getContent() when { - fileText.contains(startComment) -> { + currentFileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") - regex.find(fileText)?.groups?.get(1)?.value?.let { existingSection -> - val newText = swappedText(existingSection) + regex.find(currentFileText)?.groups?.get(1)?.value?.let { existingSection -> + val newText = swappedText(existingSection, currentFileText) editor?.let { runWriteAction { with(editor.document) { @@ -238,20 +247,21 @@ object JsDialogProcessor { } ?: run { File(model.containingFilePath).writeText(newText) } + currentFileText = newText } } else -> { val line = buildString { append("\n$startComment") - append(swappedText(null)) + append(swappedText(null, currentFileText)) append(endComment) } editor?.let { runWriteAction { with(editor.document) { unblockDocument(project, this) - setText(fileText + line) + setText(currentFileText + line) unblockDocument(project, this) } with(FileDocumentManager.getInstance()) { @@ -259,8 +269,9 @@ object JsDialogProcessor { } } } ?: run { - File(model.containingFilePath).writeText(fileText + line) + File(model.containingFilePath).writeText(currentFileText + line) } + currentFileText += line } } } diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index b4b54d76bb..03ba237e2c 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -5,7 +5,8 @@ import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.JsUtFuzzedExecution -import framework.api.js.util.isExportable +import framework.api.js.util.isClass +import framework.api.js.util.isJsArray import framework.api.js.util.isJsBasic import framework.api.js.util.jsErrorClassId import framework.api.js.util.jsUndefinedClassId @@ -19,6 +20,8 @@ import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing +import java.io.File +import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -53,15 +56,13 @@ import service.TernService import service.coverage.CoverageMode import service.coverage.CoverageServiceProvider import settings.JsDynamicSettings +import settings.JsTestGenerationSettings.fileUnderTestAliases import settings.JsTestGenerationSettings.fuzzingThreshold import settings.JsTestGenerationSettings.fuzzingTimeout import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny -import java.io.File -import java.util.concurrent.CancellationException -import settings.JsTestGenerationSettings.fileUnderTestAliases private val logger = KotlinLogging.logger {} @@ -72,7 +73,7 @@ class JsTestGenerator( private val selectedMethods: List? = null, private var parentClassName: String? = null, private var outputFilePath: String?, - private val exportsManager: (List, (String?) -> String) -> Unit, + private val exportsManager: ((String?, String) -> String) -> Unit, private val settings: JsDynamicSettings, private val isCancelled: () -> Boolean = { false } ) { @@ -333,7 +334,7 @@ class JsTestGenerator( val collectedExports = collectExports(execId) val exportsProvider = IExportsProvider.providerByPackageJson(packageJson) exports += (collectedExports + obligatoryExport) - exportsManager(exports.toList()) { existingSection -> + exportsManager { existingSection, currentFileText -> val existingExportsSet = existingSection?.let { section -> val trimmedSection = section.substringAfter(exportsProvider.exportsPrefix) .substringBeforeLast(exportsProvider.exportsPostfix) @@ -352,7 +353,7 @@ class JsTestGenerator( ) { exportsProvider.getExportsFrame(it) } - existingSection?.let { fileText.replace(existingSection, resSection) } ?: resSection + existingSection?.let { currentFileText.replace(existingSection, resSection) } ?: resSection } } @@ -384,15 +385,16 @@ class JsTestGenerator( } private fun collectExports(methodId: JsMethodId): List { - val res = mutableListOf() - methodId.parameters.forEach { - if (it.isExportable && !astScrapper.importsMap.contains(it.name)) { - res += it.name - } + return (listOf(methodId.returnType) + methodId.parameters).flatMap { it.collectExportsRecursively() } + } + + private fun JsClassId.collectExportsRecursively(): List { + return when { + this.isClass -> listOf(this.name) + (this.constructor?.parameters ?: emptyList()) + .flatMap { it.collectExportsRecursively() } + this.isJsArray -> (this.elementClassId as? JsClassId)?.collectExportsRecursively() ?: emptyList() + else -> emptyList() } - if (methodId.returnType.isExportable && !astScrapper.importsMap.contains(methodId.returnType.name)) - res += methodId.returnType.name - return res } private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt index deb0dbc2b6..6ac73a0048 100644 --- a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -47,10 +47,10 @@ val JsClassId.isJsBasic: Boolean get() = this in jsBasic || this is JsMultipleClassId val JsClassId.isExportable: Boolean - get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsArray || this.isJsMap || this.isJsSet) + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsStdStructure) val JsClassId.isClass: Boolean - get() = !(this.isJsBasic || this == jsErrorClassId) + get() = !(this.isJsBasic || this == jsErrorClassId || this.isJsStdStructure) val JsClassId.isUndefined: Boolean get() = this == jsUndefinedClassId @@ -63,3 +63,6 @@ val JsClassId.isJsMap: Boolean val JsClassId.isJsSet: Boolean get() = this.name == "Set" + +val JsClassId.isJsStdStructure: Boolean + get() = this.isJsArray || this.isJsSet || this.isJsMap From 512d622542d7f17d63bc01d39d3f792dea227b61 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 7 Mar 2023 17:27:01 +0300 Subject: [PATCH 60/74] Rollback fix of non-indexing test files --- .../intellij/plugin/language/js/JsDialogProcessor.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 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 94c48594e3..5625e3cd7a 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 @@ -195,16 +195,13 @@ object JsDialogProcessor { val generatedCode = testGenerator.run() invokeLater { runWriteAction { - val testPsiFile = testDir.findFile(testFileName) ?: run { - val temp = PsiFileFactory.getInstance(project) - .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) - testDir.add(temp) - testDir.findFile(testFileName)!! - } + val testPsiFile = testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) + .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) val testFileEditor = CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) unblockDocument(project, testFileEditor.document) testFileEditor.document.setText(generatedCode) unblockDocument(project, testFileEditor.document) + testDir.findFile(testFileName) ?: testDir.add(testPsiFile) } } } From 22f2f27c3bf97592085434662e86e43e2487b875 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 7 Mar 2023 17:27:16 +0300 Subject: [PATCH 61/74] Fixed non-indexing test files --- .../intellij/plugin/language/js/JsDialogProcessor.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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 5625e3cd7a..94c48594e3 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 @@ -195,13 +195,16 @@ object JsDialogProcessor { val generatedCode = testGenerator.run() invokeLater { runWriteAction { - val testPsiFile = testDir.findFile(testFileName) ?: PsiFileFactory.getInstance(project) - .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + val testPsiFile = testDir.findFile(testFileName) ?: run { + val temp = PsiFileFactory.getInstance(project) + .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + testDir.add(temp) + testDir.findFile(testFileName)!! + } val testFileEditor = CodeInsightUtil.positionCursor(project, testPsiFile, testPsiFile) unblockDocument(project, testFileEditor.document) testFileEditor.document.setText(generatedCode) unblockDocument(project, testFileEditor.document) - testDir.findFile(testFileName) ?: testDir.add(testPsiFile) } } } From 5f56feec9ee0d116c77eb5dae0853a6d56f3ba02 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 7 Mar 2023 18:07:03 +0300 Subject: [PATCH 62/74] After merge fix --- .../plugin/language/js/JsDialogProcessor.kt | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 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 640afd6376..35d59e7fb9 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 @@ -144,6 +144,13 @@ object JsDialogProcessor { private fun createDialog(jsTestsModel: JsTestsModel?) = jsTestsModel?.let { JsDialogWindow(it) } + private fun unblockDocument(project: Project, document: Document) { + PsiDocumentManager.getInstance(project).apply { + commitDocument(document) + doPostponedOperationsAndUnblockDocument(document) + } + } + private fun createTests(model: JsTestsModel, containingFilePath: String, editor: Editor?, contents: String) { val normalizedContainingFilePath = containingFilePath.replace(File.separator, "/") (object : Task.Backgroundable(model.project, "Generate tests") { @@ -204,12 +211,30 @@ object JsDialogProcessor { }).queue() } - private fun partialApplication(f: (A, B, C) -> Unit, a: A, b: B): (C) -> Unit { - return { c: C -> f(a, b, c) } + private fun partialApplication(f: (A, B, C, D) -> Unit, a: A, b: B, c: C): (D) -> Unit { + return { d: D -> f(a, b, c, d) } } private fun JSFile.getContent(): String = this.viewProvider.contents.toString() + private fun Project.setNewText(editor: Editor?, filePath: String, text: String) { + editor?.let { + runWriteAction { + with(editor.document) { + unblockDocument(this@setNewText, this@with) + setText(text) + unblockDocument(this@setNewText, this@with) + } + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } + } + } ?: run { + File(filePath).writeText(text) + } + currentFileText = text + } + // Needed for continuous exports managing private var currentFileText = "" @@ -221,27 +246,12 @@ object JsDialogProcessor { ) { AppExecutorUtil.getAppExecutorService().submit { invokeLater { - val exportSection = exports.joinToString("\n") { "exports.$it = $it" } when { currentFileText.contains(startComment) -> { val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") regex.find(currentFileText)?.groups?.get(1)?.value?.let { existingSection -> val newText = swappedText(existingSection, currentFileText) - editor?.let { - runWriteAction { - with(editor.document) { - unblockDocument(project, this) - setText(newText) - unblockDocument(project, this) - } - with(FileDocumentManager.getInstance()) { - saveDocument(editor.document) - } - } - } ?: run { - File(model.containingFilePath).writeText(newText) - } - currentFileText = newText + project.setNewText(editor, model.containingFilePath, newText) } } @@ -251,21 +261,7 @@ object JsDialogProcessor { append(swappedText(null, currentFileText)) append(endComment) } - editor?.let { - runWriteAction { - with(editor.document) { - unblockDocument(project, this) - setText(currentFileText + line) - unblockDocument(project, this) - } - with(FileDocumentManager.getInstance()) { - saveDocument(editor.document) - } - } - } ?: run { - File(model.containingFilePath).writeText(currentFileText + line) - } - currentFileText += line + project.setNewText(editor, model.containingFilePath, currentFileText + line) } } } From b9418e2ca82bec77b199dccc0e451c5ece4cb5a0 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 7 Mar 2023 18:14:38 +0300 Subject: [PATCH 63/74] Added new samples to utbot-js --- utbot-js/samples/arrays.js | 30 ++++++++++++++++++++++++++++++ utbot-js/samples/mapStructure.js | 11 +++++++++++ utbot-js/samples/setStructure.js | 12 ++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 utbot-js/samples/arrays.js create mode 100644 utbot-js/samples/mapStructure.js create mode 100644 utbot-js/samples/setStructure.js diff --git a/utbot-js/samples/arrays.js b/utbot-js/samples/arrays.js new file mode 100644 index 0000000000..0b062aa79f --- /dev/null +++ b/utbot-js/samples/arrays.js @@ -0,0 +1,30 @@ +function simpleArray(arr) { + if (arr[0] === 5) { + return 5 + } + return 1 +} + +simpleArray([0, 2]) + +class ObjectParameter { + + constructor(a) { + this.first = a + } + + performAction(value) { + return 2 * value + } +} + +function arrayOfObjects(arr) { + if (arr[0].first === 2) { + return 1 + } + return 10 +} + +let arr = [] +arr[0] = new ObjectParameter(10) +arrayOfObjects(arr) \ No newline at end of file diff --git a/utbot-js/samples/mapStructure.js b/utbot-js/samples/mapStructure.js new file mode 100644 index 0000000000..8007e13d19 --- /dev/null +++ b/utbot-js/samples/mapStructure.js @@ -0,0 +1,11 @@ +// Maps in JavaScript are untyped, so only maps with basic key/value types are feasible to support +function simpleMap(map, compareValue) { + if (map.get("a") === compareValue) { + return 5 + } + return 1 +} + +const map1 = new Map() +map1.set("b", 3.0) +simpleMap(map1, 5) \ No newline at end of file diff --git a/utbot-js/samples/setStructure.js b/utbot-js/samples/setStructure.js new file mode 100644 index 0000000000..92c4b68c3c --- /dev/null +++ b/utbot-js/samples/setStructure.js @@ -0,0 +1,12 @@ +// Sets in JavaScript are untyped, so only sets with basic value types are feasible to support +function setTest(set, checkValue) { + if (set.has(checkValue)) { + return 5 + } + return 1 +} + +let s = new Set() +s.add(5) +s.add(6) +setTest(s, 4) \ No newline at end of file From 2bea5ebfae871acb9bbc132668cf5b7279b468fc Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Tue, 7 Mar 2023 18:26:00 +0300 Subject: [PATCH 64/74] Minor refactoring --- utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt | 6 ++---- .../codegen/model/constructor/visitor/CgJsRenderer.kt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt b/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt index 8c59af2c34..6584241428 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt @@ -79,15 +79,13 @@ internal val jsAssertThrows by lazy { enum class ModuleType { MODULE, - PLAIN; - // TODO: review commonjs - //COMMONJS + COMMONJS; companion object { fun fromPackageJson(packageJson: PackageJson): ModuleType { return when (packageJson.isModule) { true -> MODULE - else -> PLAIN + else -> COMMONJS } } } diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index 9880a058a9..428c55b833 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -267,7 +267,7 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP private fun renderImport(import: JsImport) = with(import) { when (type) { - ModuleType.PLAIN -> println("const $aliases = require(\"$path\")") + ModuleType.COMMONJS -> println("const $aliases = require(\"$path\")") ModuleType.MODULE -> println("import $name as $aliases from \"$path\"") } } From 56d1518c0709206e04b65428dae90654c51a3cef Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Tue, 7 Mar 2023 18:52:56 +0300 Subject: [PATCH 65/74] Change ideType to IC --- gradle.properties | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/gradle.properties b/gradle.properties index 326f008335..277a08e1e5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,29 +1,22 @@ kotlin.code.style=official - # IU, IC, PC, PY # IC for AndroidStudio -ideType=IU +ideType=IC # ALL, NOJS buildType=NOJS ideVersion=222.4167.29 - pythonIde=IC,IU,PC,PY jsIde=IU,PY,WS jsBuild=ALL goIde=IU - # In order to run Android Studion instead of Intellij Community, specify the path to your Android Studio installation #androidStudioPath=your_path_to_android_studio - #Version numbers: https://plugins.jetbrains.com/plugin/631-python/versions pythonCommunityPluginVersion=222.4167.37 pythonUltimatePluginVersion=222.4167.37 - # Version numbers: https://plugins.jetbrains.com/plugin/9568-go/versions goPluginVersion=222.4167.21 - kotlinPluginVersion=222-1.7.20-release-201-IJ4167.29 - junit5Version=5.8.0-RC1 junit4Version=4.13.2 junit4PlatformVersion=1.9.0 @@ -63,8 +56,8 @@ kryoSerializersVersion=0.45 asmVersion=9.2 testNgVersion=7.6.0 mockitoInlineVersion=4.0.0 -kamlVersion = 0.51.0 -jacksonVersion = 2.12.3 +kamlVersion=0.51.0 +jacksonVersion=2.12.3 javasmtSolverZ3Version=4.8.9-sosy1 slf4jVersion=1.7.36 eclipseAetherVersion=1.1.0 @@ -81,7 +74,6 @@ pytorchNativeVersion=1.9.1 shadowJarVersion=7.1.2 openblasVersion=0.3.10-1.5.4 arpackNgVersion=3.7.0-1.5.4 - # configuration for build server # # the following options are passed to gradle command explicitly (see appropriate workflow): @@ -90,7 +82,6 @@ arpackNgVersion=3.7.0-1.5.4 # # read about options precedence at: https://docs.gradle.org/current/userguide/build_environment.html org.gradle.jvmargs="-Xmx6g" - # configuration for local compilation - much faster # overriden by some parameters in CI, read below about each option # From 8b769e0e184bf82e2a78f9fa45631900b54a6460 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Tue, 7 Mar 2023 19:17:40 +0300 Subject: [PATCH 66/74] Fix gradle.properties code style --- gradle.properties | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 277a08e1e5..0aa577543c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,22 +1,29 @@ kotlin.code.style=official + # IU, IC, PC, PY # IC for AndroidStudio ideType=IC # ALL, NOJS buildType=NOJS ideVersion=222.4167.29 + pythonIde=IC,IU,PC,PY jsIde=IU,PY,WS jsBuild=ALL goIde=IU + # In order to run Android Studion instead of Intellij Community, specify the path to your Android Studio installation #androidStudioPath=your_path_to_android_studio + #Version numbers: https://plugins.jetbrains.com/plugin/631-python/versions pythonCommunityPluginVersion=222.4167.37 pythonUltimatePluginVersion=222.4167.37 + # Version numbers: https://plugins.jetbrains.com/plugin/9568-go/versions goPluginVersion=222.4167.21 + kotlinPluginVersion=222-1.7.20-release-201-IJ4167.29 + junit5Version=5.8.0-RC1 junit4Version=4.13.2 junit4PlatformVersion=1.9.0 @@ -56,8 +63,8 @@ kryoSerializersVersion=0.45 asmVersion=9.2 testNgVersion=7.6.0 mockitoInlineVersion=4.0.0 -kamlVersion=0.51.0 -jacksonVersion=2.12.3 +kamlVersion = 0.51.0 +jacksonVersion = 2.12.3 javasmtSolverZ3Version=4.8.9-sosy1 slf4jVersion=1.7.36 eclipseAetherVersion=1.1.0 @@ -74,6 +81,7 @@ pytorchNativeVersion=1.9.1 shadowJarVersion=7.1.2 openblasVersion=0.3.10-1.5.4 arpackNgVersion=3.7.0-1.5.4 + # configuration for build server # # the following options are passed to gradle command explicitly (see appropriate workflow): @@ -82,6 +90,7 @@ arpackNgVersion=3.7.0-1.5.4 # # read about options precedence at: https://docs.gradle.org/current/userguide/build_environment.html org.gradle.jvmargs="-Xmx6g" + # configuration for local compilation - much faster # overriden by some parameters in CI, read below about each option # @@ -94,4 +103,4 @@ org.gradle.parallel=true # not overrided, we use cache in CI as well org.gradle.caching=true # there is no need to override the option below because parallel execution is disabled by --no-parallel -org.gradle.workers.max=8 +org.gradle.workers.max=8 \ No newline at end of file From ed08428d3b7c48ba36095241442aae57e9f9d9a4 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Tue, 7 Mar 2023 19:18:54 +0300 Subject: [PATCH 67/74] Add newline at end of file --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0aa577543c..56447555bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -103,4 +103,4 @@ org.gradle.parallel=true # not overrided, we use cache in CI as well org.gradle.caching=true # there is no need to override the option below because parallel execution is disabled by --no-parallel -org.gradle.workers.max=8 \ No newline at end of file +org.gradle.workers.max=8 From 831159ba026f673d0f397f32b13feec68e2aaf44 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Thu, 9 Mar 2023 14:59:13 +0300 Subject: [PATCH 68/74] After merge fix --- .../src/main/kotlin/api/JsTestGenerator.kt | 7 ++--- .../src/main/kotlin/fuzzer/JsFuzzerApi.kt | 3 -- utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt | 4 +-- .../fuzzer/providers/ArrayValueProvider.kt | 15 ++++------ .../fuzzer/providers/BoolValueProvider.kt | 1 - .../fuzzer/providers/MapValueProvider.kt | 28 ++++++++----------- .../fuzzer/providers/SetValueProvider.kt | 25 +++++++---------- .../fuzzer/providers/StringValueProvider.kt | 1 - .../main/kotlin/parser/JsFuzzerAstVisitor.kt | 6 ++-- .../src/main/kotlin/parser/JsParserUtils.kt | 6 ++-- .../coverage/CoverageServiceProvider.kt | 12 +++----- 11 files changed, 40 insertions(+), 68 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 243cc6d243..41da1214d0 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -5,7 +5,6 @@ import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.JsUtFuzzedExecution -import framework.api.js.JsUtFuzzedExecution import framework.api.js.util.isClass import framework.api.js.util.isJsArray import framework.api.js.util.isJsBasic @@ -14,15 +13,12 @@ import framework.api.js.util.jsUndefinedClassId import framework.codegen.JsImport import framework.codegen.ModuleType import fuzzer.JsFeedback -import fuzzer.JsFuzzedValue import fuzzer.JsFuzzingExecutionFeedback import fuzzer.JsMethodDescription import fuzzer.JsStatement import fuzzer.JsTimeoutExecution import fuzzer.JsValidExecution import fuzzer.runFuzzing -import java.io.File -import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging @@ -65,6 +61,8 @@ import utils.PathResolver import utils.constructClass import utils.data.ResultData import utils.toJsAny +import java.io.File +import java.util.concurrent.CancellationException private val logger = KotlinLogging.logger {} @@ -394,6 +392,7 @@ class JsTestGenerator( return when { this.isClass -> listOf(this.name) + (this.constructor?.parameters ?: emptyList()) .flatMap { it.collectExportsRecursively() } + this.isJsArray -> (this.elementClassId as? JsClassId)?.collectExportsRecursively() ?: emptyList() else -> emptyList() } diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt index e74d43b526..cbef94b64c 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -5,9 +5,6 @@ import framework.api.js.JsUtFuzzedExecution import framework.api.js.util.isClass import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtModel -import java.util.concurrent.atomic.AtomicInteger -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.fuzzing.Control import org.utbot.fuzzing.Description diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt index b7c0de2855..c9953083d8 100644 --- a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -1,17 +1,17 @@ package fuzzer import framework.api.js.JsClassId +import fuzzer.providers.ArrayValueProvider import fuzzer.providers.BoolValueProvider import fuzzer.providers.MapValueProvider import fuzzer.providers.NumberValueProvider +import fuzzer.providers.ObjectValueProvider import fuzzer.providers.SetValueProvider import fuzzer.providers.StringValueProvider import org.utbot.framework.plugin.api.UtModel import org.utbot.fuzzing.Fuzzing import org.utbot.fuzzing.Seed import org.utbot.fuzzing.fuzz -import fuzzer.providers.ArrayValueProvider -import fuzzer.providers.ObjectValueProvider fun defaultValueProviders() = listOf( BoolValueProvider, diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt index 631de09d51..ab966c29d2 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -3,38 +3,35 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.util.defaultJsValueModel import framework.api.js.util.isJsArray -import fuzzer.JsFuzzedValue import fuzzer.JsIdProvider import fuzzer.JsMethodDescription -import fuzzer.fuzzed import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtModel import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -class ArrayValueProvider : ValueProvider { +class ArrayValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean = type.isJsArray override fun generate( description: JsMethodDescription, type: JsClassId - ) = sequence> { + ) = sequence> { yield( Seed.Collection( construct = Routine.Collection { UtArrayModel( - id = JsIdProvider.get(), + id = JsIdProvider.createId(), classId = type, length = it, constModel = (type.elementClassId!! as JsClassId).defaultJsValueModel(), stores = hashMapOf(), - ).fuzzed { - summary = "%var% = ${type.elementClassId!!.simpleName}[$it]" - } + ) }, modify = Routine.ForEach(listOf(type.elementClassId!! as JsClassId)) { self, i, values -> - (self.model as UtArrayModel).stores[i] = values.first().model + (self as UtArrayModel).stores[i] = values.first() } )) } diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt index 2b664c242d..87031b6559 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt @@ -3,7 +3,6 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.JsPrimitiveModel import framework.api.js.util.isJsBasic -import fuzzer.JsFuzzedValue import fuzzer.JsMethodDescription import org.utbot.framework.plugin.api.UtModel import org.utbot.fuzzing.Seed diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt index 11d04e85d8..159435fe66 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt @@ -1,35 +1,33 @@ package fuzzer.providers + import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.util.isJsMap import framework.api.js.util.jsBasic import framework.api.js.util.jsUndefinedClassId -import fuzzer.JsFuzzedValue import fuzzer.JsIdProvider import fuzzer.JsMethodDescription -import fuzzer.fuzzed import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel - - +import org.utbot.framework.plugin.api.UtModel import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider -object MapValueProvider : ValueProvider { +object MapValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean = type.isJsMap override fun generate( description: JsMethodDescription, type: JsClassId - ) = sequence> { - val modifications = mutableListOf>() + ) = sequence> { + val modifications = mutableListOf>() jsBasic.zip(jsBasic).map { (a, b) -> listOf(a, b) }.forEach { typeParameters -> modifications += Routine.Call(typeParameters) { instance, arguments -> - val model = instance.model as UtAssembleModel + val model = instance as UtAssembleModel (model).modificationsChain as MutableList += UtExecutableCallModel( model, @@ -39,7 +37,7 @@ object MapValueProvider : ValueProvider { +object SetValueProvider : ValueProvider { override fun accept(type: JsClassId): Boolean = type.isJsSet override fun generate( description: JsMethodDescription, type: JsClassId - ) = sequence> { - val modifications = mutableListOf>() + ) = sequence> { + val modifications = mutableListOf>() jsBasic.forEach { typeParameter -> modifications += Routine.Call(listOf(typeParameter)) { instance, arguments -> - val model = instance.model as UtAssembleModel + val model = instance as UtAssembleModel (model).modificationsChain as MutableList += UtExecutableCallModel( model, @@ -37,7 +36,7 @@ object SetValueProvider : ValueProvider { validateNode(node.getAnyValue(), JsFuzzedContext.NE) } + currentFuzzedOp != null -> { lastFuzzedOpGlobal = currentFuzzedOp validateNode(node.getBinaryExprLeftOperand().getAnyValue(), lastFuzzedOpGlobal) diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt index 583e481d8e..e1d1df2385 100644 --- a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -5,9 +5,6 @@ import com.google.javascript.jscomp.NodeUtil import com.google.javascript.jscomp.SourceFile import com.google.javascript.rhino.Node import fuzzer.JsFuzzedContext -import java.lang.IllegalStateException -import parser.JsParserUtils.getMethodName -import fuzzer.JsFuzzedContext import parser.JsParserUtils.getMethodName // TODO: make methods more safe by checking the Node method is called on. @@ -76,6 +73,7 @@ object JsParserUtils { this.firstChild?.next?.getAnyValue() } else null } + else -> null } @@ -183,7 +181,7 @@ object JsParserUtils { * Returns imported objects as [List]. */ fun Node.getModuleImportSpecsAsList(): List { - val importSpecsNode = NodeUtil.findPreorder(this, {it.isImportSpecs}, {true}) + val importSpecsNode = NodeUtil.findPreorder(this, { it.isImportSpecs }, { true }) ?: throw UnsupportedOperationException("Module import doesn't contain \"import_specs\" token as an AST child") var currNode: Node? = importSpecsNode.firstChild!! val importSpecsList = mutableListOf() diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 857537a628..19aac6082e 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -6,11 +6,13 @@ import framework.api.js.JsPrimitiveModel import framework.api.js.util.isExportable import framework.api.js.util.isUndefined import fuzzer.JsMethodDescription -import java.lang.StringBuilder +import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.util.isStatic - +import providers.imports.IImportsProvider import service.ContextOwner import service.InstrumentationService import service.ServiceContext @@ -19,10 +21,6 @@ import settings.JsTestGenerationSettings.tempFileName import utils.data.CoverageData import utils.data.ResultData import java.util.regex.Pattern -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtNullModel -import providers.imports.IImportsProvider class CoverageServiceProvider( private val context: ServiceContext, @@ -241,8 +239,6 @@ fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) return paramsString } - - private fun UtModel.initModelAsString(): String = when (this) { is UtAssembleModel -> this.toParamString() From 5f5d593992143097b22b10b5e0e006484ed0ab8e Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Thu, 9 Mar 2023 15:23:14 +0300 Subject: [PATCH 69/74] Attempt to fix active memory usage --- .../service/coverage/BasicCoverageService.kt | 3 +- .../service/coverage/CoverageService.kt | 29 +++++++------------ .../coverage/CoverageServiceProvider.kt | 2 -- .../service/coverage/FastCoverageService.kt | 3 +- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index c777b799ce..da802f614f 100644 --- a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -14,8 +14,7 @@ private val logger = KotlinLogging.logger {} class BasicCoverageService( context: ServiceContext, private val scriptTexts: List, - baseCoverage: List, -) : CoverageService(context, scriptTexts, baseCoverage) { +) : CoverageService(context, scriptTexts) { override fun generateCoverageReport() { scriptTexts.indices.forEach { index -> diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt index aca8f8ae03..a916762238 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt @@ -14,7 +14,6 @@ import utils.data.ResultData abstract class CoverageService( context: ServiceContext, private val scriptTexts: List, - private val baseCoverage: List, ): ContextOwner by context { private val _utbotDirPath = lazy { "${projectPath}/${utbotDir}" } @@ -50,11 +49,11 @@ abstract class CoverageService( timeout = settings.timeout, ) return JSONObject(File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.json").readText()) - .getJSONObject("s").let { - it.keySet().flatMap { key -> - val count = it.getInt(key) - Collections.nCopies(count, key.toInt()) - } + .getJSONObject("s").let { obj -> + obj.keys().asSequence().mapNotNull { + val count = obj.getInt(it) + if (count == 0) null else it.toInt() + }.toList() } } } @@ -79,17 +78,11 @@ abstract class CoverageService( // TODO: sort by coverage size desc return coverageList .map { (_, obj) -> - val dirtyCoverage = obj - .let { - it.keySet().flatMap { key -> - val count = it.getInt(key) - Collections.nCopies(count, key.toInt()) - }.toMutableList() - } - baseCoverage.forEach { - dirtyCoverage.remove(it) - } - CoverageData(dirtyCoverage.toSet()) + val res = obj.keys().asSequence().mapNotNull { + val count = obj.getInt(it) + if (count == 0) null else it.toInt() + }.toSet() + CoverageData(res) } } catch (e: JSONException) { throw Exception("Could not get coverage of test cases!") @@ -114,4 +107,4 @@ abstract class CoverageService( File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}$index.js").delete() } } -} \ No newline at end of file +} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index 19aac6082e..da789bc5da 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -95,7 +95,6 @@ function check_value(value, json) { val coverageService = BasicCoverageService( context = context, scriptTexts = tempScriptTexts, - baseCoverage = baseCoverage ) coverageService.generateCoverageReport() return coverageService.getCoveredLines() to coverageService.resultList @@ -120,7 +119,6 @@ function check_value(value, json) { context = context, scriptTexts = listOf(tempScriptTexts), testCaseIndices = fuzzedValues.indices, - baseCoverage = baseCoverage, ) coverageService.generateCoverageReport() return coverageService.getCoveredLines() to coverageService.resultList diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index 94bb56b8a6..e5df83a9d4 100644 --- a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -15,8 +15,7 @@ class FastCoverageService( context: ServiceContext, scriptTexts: List, private val testCaseIndices: IntRange, - baseCoverage: List, -) : CoverageService(context, scriptTexts, baseCoverage) { +) : CoverageService(context, scriptTexts) { override fun generateCoverageReport() { val (_, errorText) = JsCmdExec.runCommand( From 2535a2e4560bca765b4332a76d61dbc5328fb987 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 10 Mar 2023 17:39:00 +0300 Subject: [PATCH 70/74] Fixed base coverage analysis --- .../service/coverage/BasicCoverageService.kt | 3 ++- .../service/coverage/CoverageService.kt | 26 +++++++++---------- .../coverage/CoverageServiceProvider.kt | 4 ++- .../service/coverage/FastCoverageService.kt | 3 ++- .../main/kotlin/utils/data/CoverageData.kt | 2 +- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt index da802f614f..5ba1facadd 100644 --- a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -13,8 +13,9 @@ private val logger = KotlinLogging.logger {} class BasicCoverageService( context: ServiceContext, + baseCoverage: Map, private val scriptTexts: List, -) : CoverageService(context, scriptTexts) { +) : CoverageService(context, baseCoverage, scriptTexts) { override fun generateCoverageReport() { scriptTexts.indices.forEach { index -> diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt index a916762238..b51106e8cf 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt @@ -1,7 +1,6 @@ package service.coverage import java.io.File -import java.util.Collections import org.json.JSONException import org.json.JSONObject import service.ContextOwner @@ -13,8 +12,9 @@ import utils.data.ResultData abstract class CoverageService( context: ServiceContext, + private val baseCoverage: Map, private val scriptTexts: List, -): ContextOwner by context { +) : ContextOwner by context { private val _utbotDirPath = lazy { "${projectPath}/${utbotDir}" } protected val utbotDirPath: String @@ -32,7 +32,7 @@ abstract class CoverageService( file.createNewFile() } - fun getBaseCoverage(context: ServiceContext, baseCoverageScriptText: String): List { + fun getBaseCoverage(context: ServiceContext, baseCoverageScriptText: String): Map { with(context) { val utbotDirPath = "${projectPath}/${utbotDir}" createTempScript( @@ -50,10 +50,9 @@ abstract class CoverageService( ) return JSONObject(File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.json").readText()) .getJSONObject("s").let { obj -> - obj.keys().asSequence().mapNotNull { - val count = obj.getInt(it) - if (count == 0) null else it.toInt() - }.toList() + obj.keySet().associate { key -> + key.toInt() to obj.getInt(key) + } } } } @@ -78,11 +77,13 @@ abstract class CoverageService( // TODO: sort by coverage size desc return coverageList .map { (_, obj) -> - val res = obj.keys().asSequence().mapNotNull { - val count = obj.getInt(it) - if (count == 0) null else it.toInt() - }.toSet() - CoverageData(res) + val map = obj.keySet().associate { key -> + val intKey = key.toInt() + intKey to (obj.getInt(key) - baseCoverage.getOrDefault(intKey, 0)) + } + CoverageData(map.mapNotNull { entry -> + entry.key.takeIf { entry.value > 0 } + }.toSet()) } } catch (e: JSONException) { throw Exception("Could not get coverage of test cases!") @@ -93,7 +94,6 @@ abstract class CoverageService( abstract fun generateCoverageReport() - private fun createTempScript(path: String, scriptText: String) { val file = File(path) file.writeText(scriptText) diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt index da789bc5da..7d027d1d91 100644 --- a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -47,7 +47,7 @@ function check_value(value, json) { } """ - private val baseCoverage: List + private val baseCoverage: Map init { val temp = makeScriptForBaseCoverage( @@ -94,6 +94,7 @@ function check_value(value, json) { } val coverageService = BasicCoverageService( context = context, + baseCoverage = baseCoverage, scriptTexts = tempScriptTexts, ) coverageService.generateCoverageReport() @@ -117,6 +118,7 @@ function check_value(value, json) { } val coverageService = FastCoverageService( context = context, + baseCoverage = baseCoverage, scriptTexts = listOf(tempScriptTexts), testCaseIndices = fuzzedValues.indices, ) diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt index e5df83a9d4..32a427cdd9 100644 --- a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -13,9 +13,10 @@ private val logger = KotlinLogging.logger {} class FastCoverageService( context: ServiceContext, + baseCoverage: Map, scriptTexts: List, private val testCaseIndices: IntRange, -) : CoverageService(context, scriptTexts) { +) : CoverageService(context, baseCoverage, scriptTexts) { override fun generateCoverageReport() { val (_, errorText) = JsCmdExec.runCommand( diff --git a/utbot-js/src/main/kotlin/utils/data/CoverageData.kt b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt index 2237243745..b64fdfdaf7 100644 --- a/utbot-js/src/main/kotlin/utils/data/CoverageData.kt +++ b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt @@ -2,4 +2,4 @@ package utils.data data class CoverageData( val additionalCoverage: Set -) \ No newline at end of file +) From 77635cafeb8851735d0cf18e9e095899d5ee2653 Mon Sep 17 00:00:00 2001 From: Vladislav Kasimov Date: Fri, 10 Mar 2023 17:46:54 +0300 Subject: [PATCH 71/74] Refactor some tern regexps --- utbot-js/src/main/kotlin/service/TernService.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt index 3ed4d7b780..faf526d0d1 100644 --- a/utbot-js/src/main/kotlin/service/TernService.kt +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -4,7 +4,6 @@ import com.google.javascript.rhino.Node import framework.api.js.JsClassId import framework.api.js.JsMultipleClassId import framework.api.js.util.jsUndefinedClassId -import java.io.File import org.json.JSONException import org.json.JSONObject import parser.JsParserUtils @@ -16,6 +15,7 @@ import providers.imports.IImportsProvider import utils.JsCmdExec import utils.constructClass import utils.data.MethodTypes +import java.io.File /** * Installs and sets up scripts for running Tern.js type guesser. @@ -104,10 +104,11 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) val parametersRegex = Regex("fn[(](.+)[)]") return parametersRegex.find(line)?.groups?.get(1)?.let { matchResult -> val value = matchResult.value - val paramList = value.split(',') - paramList.map { param -> - val paramReg = Regex(":(.*)") + val paramGroupList = Regex("(\\w+:\\[\\w+(,\\w+)*]|\\w+:\\w+)|\\w+:\\?").findAll(value).toList() + paramGroupList.map { paramGroup -> + val paramReg = Regex("\\w*:(.*)") try { + val param = paramGroup.groups[0]!!.value makeClassId( paramReg.find(param)?.groups?.get(1)?.value ?: throw IllegalStateException() @@ -161,7 +162,8 @@ test(["${filePathToInference.joinToString(separator = "\", \"")}"]) val classId = when { name == "?" || name.toIntOrNull() != null || name.contains('!') -> jsUndefinedClassId Regex("\\[(.*)]").matches(name) -> { - val arrType = Regex("\\[(.*)]").find(name)?.groups?.get(1)?.value ?: throw IllegalStateException() + val arrType = Regex("\\[(.*)]").find(name)?.groups?.get(1)?.value?.substringBefore(",") + ?: throw IllegalStateException() JsClassId( jsName = "array", elementClassId = makeClassId(arrType) From 8f331776df985d9f98691a89a190dc521338ef03 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 10 Mar 2023 17:55:28 +0300 Subject: [PATCH 72/74] Refactored SetValueProvider and MapValueProvider --- .../fuzzer/providers/MapValueProvider.kt | 47 ++++++------------- .../fuzzer/providers/SetValueProvider.kt | 47 ++++++------------- 2 files changed, 30 insertions(+), 64 deletions(-) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt index 159435fe66..79122fa6b5 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt @@ -4,7 +4,6 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.util.isJsMap -import framework.api.js.util.jsBasic import framework.api.js.util.jsUndefinedClassId import fuzzer.JsIdProvider import fuzzer.JsMethodDescription @@ -24,26 +23,9 @@ object MapValueProvider : ValueProvider description: JsMethodDescription, type: JsClassId ) = sequence> { - val modifications = mutableListOf>() - jsBasic.zip(jsBasic).map { (a, b) -> listOf(a, b) }.forEach { typeParameters -> - modifications += Routine.Call(typeParameters) { instance, arguments -> - val model = instance as UtAssembleModel - (model).modificationsChain as MutableList += - UtExecutableCallModel( - model, - JsMethodId( - classId = type, - name = "set", - returnTypeNotLazy = jsUndefinedClassId, - parametersNotLazy = listOf(jsUndefinedClassId, jsUndefinedClassId) - ), - arguments - ) - } - } yield( - Seed.Recursive( - construct = Routine.Create(listOf(jsUndefinedClassId, jsUndefinedClassId)) { + Seed.Collection( + construct = Routine.Collection { UtAssembleModel( id = JsIdProvider.createId(), classId = type, @@ -56,19 +38,20 @@ object MapValueProvider : ValueProvider modificationsChainProvider = { mutableListOf() } ) }, - empty = Routine.Empty { - UtAssembleModel( - id = JsIdProvider.createId(), - classId = type, - modelName = "", - instantiationCall = UtExecutableCallModel( - null, - ConstructorId(type, emptyList()), - emptyList() + modify = Routine.ForEach(listOf(jsUndefinedClassId, jsUndefinedClassId)) { self, _, values -> + val model = self as UtAssembleModel + model.modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "set", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId, jsUndefinedClassId) + ), + values ) - ) - }, - modify = modifications.asSequence() + } ) ) } diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt index fa531edf7d..6991518c9f 100644 --- a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt +++ b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt @@ -3,7 +3,6 @@ package fuzzer.providers import framework.api.js.JsClassId import framework.api.js.JsMethodId import framework.api.js.util.isJsSet -import framework.api.js.util.jsBasic import framework.api.js.util.jsUndefinedClassId import fuzzer.JsIdProvider import fuzzer.JsMethodDescription @@ -23,26 +22,9 @@ object SetValueProvider : ValueProvider description: JsMethodDescription, type: JsClassId ) = sequence> { - val modifications = mutableListOf>() - jsBasic.forEach { typeParameter -> - modifications += Routine.Call(listOf(typeParameter)) { instance, arguments -> - val model = instance as UtAssembleModel - (model).modificationsChain as MutableList += - UtExecutableCallModel( - model, - JsMethodId( - classId = type, - name = "add", - returnTypeNotLazy = jsUndefinedClassId, - parametersNotLazy = listOf(jsUndefinedClassId) - ), - arguments - ) - } - } yield( - Seed.Recursive( - construct = Routine.Create(listOf(jsUndefinedClassId)) { + Seed.Collection( + construct = Routine.Collection { UtAssembleModel( id = JsIdProvider.createId(), classId = type, @@ -55,19 +37,20 @@ object SetValueProvider : ValueProvider modificationsChainProvider = { mutableListOf() } ) }, - empty = Routine.Empty { - UtAssembleModel( - id = JsIdProvider.createId(), - classId = type, - modelName = "", - instantiationCall = UtExecutableCallModel( - null, - ConstructorId(type, emptyList()), - emptyList() + modify = Routine.ForEach(listOf(jsUndefinedClassId)) { self, _, values -> + val model = self as UtAssembleModel + model.modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "add", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId) + ), + values ) - ) - }, - modify = modifications.asSequence() + } ) ) } From e2c489414978edeb85098da73b47c67c9be37238 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 10 Mar 2023 19:10:52 +0300 Subject: [PATCH 73/74] Fixed exports management for corrupted utbot exports section --- utbot-js/src/main/kotlin/api/JsTestGenerator.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt index 41da1214d0..a72adaaa82 100644 --- a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -63,6 +63,8 @@ import utils.data.ResultData import utils.toJsAny import java.io.File import java.util.concurrent.CancellationException +import settings.JsExportsSettings.endComment +import settings.JsExportsSettings.startComment private val logger = KotlinLogging.logger {} @@ -348,12 +350,12 @@ class JsTestGenerator( val resultSet = existingExportsSet + exports.toSet() val resSection = resultSet.joinToString( separator = exportsProvider.exportsDelimiter, - prefix = exportsProvider.exportsPrefix, - postfix = exportsProvider.exportsPostfix, + prefix = startComment + exportsProvider.exportsPrefix, + postfix = exportsProvider.exportsPostfix + endComment, ) { exportsProvider.getExportsFrame(it) } - existingSection?.let { currentFileText.replace(existingSection, resSection) } ?: resSection + existingSection?.let { currentFileText.replace(startComment + existingSection + endComment, resSection) } ?: resSection } } From 4f44211cf52c9b4bec43339656d7ce205b6d6f35 Mon Sep 17 00:00:00 2001 From: Sergey Loktev Date: Fri, 10 Mar 2023 19:20:19 +0300 Subject: [PATCH 74/74] Hotfix --- .../utbot/intellij/plugin/language/js/JsDialogProcessor.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 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 258517fc4c..e695e347a0 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 @@ -257,9 +257,8 @@ object JsDialogProcessor { else -> { val line = buildString { - append("\n$startComment") - append(swappedText(null, currentFileText)) - append(endComment) + append("\n") + appendLine(swappedText(null, currentFileText)) } project.setNewText(editor, model.containingFilePath, currentFileText + line) }