From 1ca87cf6ae74890813fedd3be6dadb025ca9c497 Mon Sep 17 00:00:00 2001 From: Denis Fokin Date: Sat, 28 May 2022 19:47:01 +0300 Subject: [PATCH 001/120] Text for JetBrains marketplace closes #53 (#54) * Text for JetBrains marketplace closes #53 Authored-by: Denis Fokin --- .../src/main/resources/META-INF/plugin.xml | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/utbot-intellij/src/main/resources/META-INF/plugin.xml b/utbot-intellij/src/main/resources/META-INF/plugin.xml index e91b7f849d..78f5829834 100644 --- a/utbot-intellij/src/main/resources/META-INF/plugin.xml +++ b/utbot-intellij/src/main/resources/META-INF/plugin.xml @@ -2,7 +2,7 @@ org.utbot.intellij.plugin.id - UnitTestBot + UTBot utbot.org UnitTestBot plugin for IntelliJ IDEA for Java @@ -37,7 +37,35 @@ unit tests with a single action! +
+
+ The UTBot engine goes through your code instructions and generates regression tests. +
+
+ The engine finds potential problems in your code: +
+
+
    +
  • exceptions
  • +
  • hangs
  • +
  • overflows
  • +
  • and even native crashes
  • +
+
+ They are not a surprise for you anymore. The engine will find the problems and generate tests for them. +
+
+ The engine carefully selects tests to maximize statement and branch coverage. Our credo is to maximize test coverage and minimize tests number. +
+
+ You can try the engine online without installation. +
+
+ Got ideas? Give us know or become a contributor on our GitHub page +
+
+ Found an issue? Please, submit it here. ]]>
From 44f36d4f00a897c3eeb602cfe81030680b8af746 Mon Sep 17 00:00:00 2001 From: Victoria <32179813+victoriafomina@users.noreply.github.com> Date: Sun, 29 May 2022 08:54:10 +0300 Subject: [PATCH 002/120] Add link on releases to README.md closes #60 (#61) --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3da38dc837..84707a0c39 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,25 @@ UTBotJava generates test cases by code, trying to cover maximum statements and execution paths. We treat source code as source of truth assuming that behavior is correct and corresponds to initial user demand. Generated tests are placed in so-called regression suite. Thus, we fixate current behavior by generated test cases. Using UTBotJava developers obtain full control of their code. No future change can break the code without being noticed once it's covered with tests generated by UTBot. This way, modifications made by developers to an existing code are much safer. Hence, with the help of generated unit tests, UTBot provides dramatic code quality improvement. -# How to install UTBot Java IntelliJ IDEA plugin +# UTBot Java IntelliJ IDEA plugin -For now, you can use UTBot under: -- Ubuntu 20.04 -- Windows 10 +UTBot Java provides users with **IntelliJ IDEA** plugin. -See [step-by-step guide](https://github.com/UnitTestBot/UTBotJava/wiki/intellij-idea-plugin) explaining how to install UTBot Java IntelliJ IDEA plugin. +_The plugin was tested on **Win64** and **Linux64**._ -# How to use UTBot Java IntelliJ IDEA plugin +> ⚠ Not supported on **MAC OS** for now (will be fixed soon). -See [step-by-step guide](https://github.com/UnitTestBot/UTBotJava/wiki/Generate-tests-with-UTBot-IntelliJ-IDEA-plugin) explaining how to use UTBot Java IntelliJ IDEA plugin. +# How to download IntelliJ IDEA plugin + +You can download the plugin from [GitHub Releases](https://github.com/UnitTestBot/UTBotJava/releases). + +# How to install IntelliJ IDEA plugin + +See [step-by-step guide](https://github.com/UnitTestBot/UTBotJava/wiki/intellij-idea-plugin) explaining how to install the plugin. + +# How to use IntelliJ IDEA plugin + +See [step-by-step guide](https://github.com/UnitTestBot/UTBotJava/wiki/generate-tests-with-plugin) explaining how to use the plugin. # How to contribute to UTBot Java From 365b36e34360c825de26b525c752ce15a5171964 Mon Sep 17 00:00:00 2001 From: Denis Fokin Date: Sun, 29 May 2022 18:59:28 +0300 Subject: [PATCH 003/120] Checkboxes are substituted with a text suggestion closes #48 (#49) Checkboxes are substituted with a text suggestion --- .github/ISSUE_TEMPLATE/bug_report.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dfd48feacc..082931e9ac 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -43,19 +43,7 @@ public void testFail_errors() { **Environment** -Test framework: - -Java: - -Mockito: - - - [ ] Other packages - - [ ] Other classes - - [ ] Static mocking - -Test: - -- [ ] Parametrized +_Substitute this text with an information that can help us to recreate the environment. For instance, specify Java version, test framework with a version (JUnit, TestNG...), Mockito configuration (packages, classes, statics), were the test parametrized or not and so on._ **Additional context** From e6bb7f7cf723ebd510868d974ccc066520c80b1b Mon Sep 17 00:00:00 2001 From: Yury Kamenev <39625503+Damtev@users.noreply.github.com> Date: Sun, 29 May 2022 21:32:10 +0300 Subject: [PATCH 004/120] =?UTF-8?q?=D0=A1oncrete=20values=20for=20enums=20?= =?UTF-8?q?(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/org/utbot/common/KClassUtil.kt | 2 +- .../kotlin/org/utbot/framework/UtSettings.kt | 2 +- .../main/kotlin/org/utbot/engine/Resolver.kt | 28 +- .../kotlin/org/utbot/engine/TypeResolver.kt | 1 - .../org/utbot/engine/UtBotSymbolicEngine.kt | 137 +++++++- .../statics/concrete/EnumConcreteUtils.kt | 245 +++++++++++++ .../org/utbot/framework/codegen/Domain.kt | 2 +- .../codegen/model/visitor/UtilMethods.kt | 116 ++++--- .../examples/AbstractTestCaseGeneratorTest.kt | 106 ++++++ .../examples/CodeTestCaseGeneratorTest.kt | 13 +- ...lassWithEnumInsideDifficultBranchesTest.kt | 19 -- .../utbot/examples/enums/ClassWithEnumTest.kt | 183 +++++++++- .../examples/math/OverflowAsErrorTest.kt | 323 +++++++++--------- .../org/utbot/examples/objects/EnumsTest.kt | 59 ---- .../utbot/examples/enums/ClassWithEnum.java | 230 ++++++++++++- .../ClassWithEnumInsideDifficultBranches.java | 24 -- .../ExceptionClusteringExamples.java | 2 +- .../utbot/examples/objects/SimpleEnum.java | 33 -- 18 files changed, 1157 insertions(+), 368 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt delete mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranchesTest.kt delete mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/objects/EnumsTest.kt delete mode 100644 utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranches.java delete mode 100644 utbot-sample/src/main/java/org/utbot/examples/objects/SimpleEnum.java diff --git a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt index 7c8572d0d5..1f6bb848a5 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt @@ -28,4 +28,4 @@ fun Method.invokeCatching(obj: Any?, args: List) = try { Result.success(invoke(obj, *args.toTypedArray())) } catch (e: InvocationTargetException) { Result.failure(e.targetException) -} \ No newline at end of file +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index b97d534627..38ed305156 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -31,7 +31,7 @@ internal class SettingDelegate(val initializer: () -> T) { /** * Default concrete execution timeout (in milliseconds). */ -const val DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS = 100L +const val DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS = 1000L object UtSettings { private val properties = Properties().also { props -> diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index a67d675cb0..4fca4b478d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -25,6 +25,7 @@ import org.utbot.engine.pc.mkLong import org.utbot.engine.pc.mkShort import org.utbot.engine.pc.select import org.utbot.engine.pc.store +import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.z3.intValue import org.utbot.engine.z3.value @@ -82,6 +83,7 @@ import soot.PrimType import soot.RefType import soot.Scene import soot.ShortType +import soot.SootClass import soot.SootField import soot.Type import soot.VoidType @@ -1068,7 +1070,7 @@ fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResul arrayToMethodResult(value.size, elementType) { if (value[it] == null) return@arrayToMethodResult nullObjectAddr - val addr = UtAddrExpression(mkBVConst("staticVariable${value.hashCode()}", UtInt32Sort)) + val addr = UtAddrExpression(mkBVConst("staticVariable${value.hashCode()}_$it", UtInt32Sort)) val createdElement = if (elementType is RefType) { val className = value[it]!!.javaClass.id.name @@ -1141,6 +1143,30 @@ private fun UtBotSymbolicEngine.arrayToMethodResult( ) } +fun UtBotSymbolicEngine.constructEnumStaticFieldResult( + fieldName: String, + fieldType: Type, + declaringClass: SootClass, + enumConstantSymbolicResultsByName: Map, + staticFieldConcreteValue: Any?, + enumConstantSymbolicValues: List +): MethodResult = + if (isEnumValuesFieldName(fieldName)) { + // special case to fill $VALUES array with already created symbolic values for enum constants runtime values + arrayToMethodResult(enumConstantSymbolicValues.size, declaringClass.type) { i -> + enumConstantSymbolicValues[i].addr + } + } else { + if (fieldName in enumConstantSymbolicResultsByName) { + // it is field to store enum constant so we use already created symbolic value from runtime enum constant + enumConstantSymbolicResultsByName.getValue(fieldName) + } else { + // otherwise, it is a common static field, + // and we have to create new symbolic value for it using its concrete value + toMethodResult(staticFieldConcreteValue, fieldType) + } + } + private val Type.unsigned: Boolean get() = this is CharType diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt index 054bf26cde..486499a5bc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt @@ -18,7 +18,6 @@ import soot.Scene import soot.SootField import soot.Type import soot.VoidType -import sun.reflect.Reflection class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 12c4ef9bd5..bfb5f5de90 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -75,6 +75,12 @@ import org.utbot.engine.symbolic.SymbolicStateUpdate import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.symbolic.asSoftConstraint import org.utbot.engine.symbolic.asUpdate +import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues +import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics +import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName +import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates +import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates +import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues import org.utbot.framework.PathSelectorType import org.utbot.framework.UtSettings import org.utbot.framework.UtSettings.checkSolverTimeoutMillis @@ -152,6 +158,7 @@ import kotlinx.collections.immutable.persistentHashMapOf import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentMap import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job @@ -196,7 +203,6 @@ import soot.jimple.Expr import soot.jimple.FieldRef import soot.jimple.FloatConstant import soot.jimple.IdentityRef -import soot.jimple.InstanceFieldRef import soot.jimple.IntConstant import soot.jimple.InvokeExpr import soot.jimple.LongConstant @@ -876,7 +882,7 @@ class UtBotSymbolicEngine( stmt: Stmt ): Boolean { if (shouldProcessStaticFieldConcretely(fieldRef)) { - return processStaticFieldConcretely(fieldRef) + return processStaticFieldConcretely(fieldRef, stmt) } val field = fieldRef.field @@ -922,7 +928,9 @@ class UtBotSymbolicEngine( // Note that this list is not exhaustive, so it may be supplemented in the future. val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely - return packagesToProcessConcretely.any { className.startsWith(it) } + val declaringClass = fieldRef.field.declaringClass + + val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) } // it is required to remove classes we override, since // we could accidentally initialize their final fields // with values that will later affect our overridden classes @@ -933,6 +941,13 @@ class UtBotSymbolicEngine( //hardcoded string for class name is used cause class is not public //this is a hack to avoid crashing on code with Math.random() && !className.endsWith("RandomNumberGeneratorHolder") + + // we can process concretely only enums that does not affect the external system + val isEnumNotAffectingExternalStatics = declaringClass.let { + it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver) + } + + return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely } } @@ -954,7 +969,7 @@ class UtBotSymbolicEngine( * * Returns true if processing takes place and Engine should end traversal of current statement. */ - private fun processStaticFieldConcretely(fieldRef: StaticFieldRef): Boolean { + private fun processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean { val field = fieldRef.field val fieldId = field.fieldId if (memory.isInitialized(fieldId)) { @@ -963,6 +978,107 @@ class UtBotSymbolicEngine( // Gets concrete value, converts to symbolic value val declaringClass = field.declaringClass + + val (edge, updates) = if (declaringClass.isEnum) { + makeConcreteUpdatesForEnums(fieldId, declaringClass, stmt) + } else { + makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass) + } + + val newState = environment.state.updateQueued(edge, updates) + pathSelector.offer(newState) + + return true + } + + private fun makeConcreteUpdatesForEnums( + fieldId: FieldId, + declaringClass: SootClass, + stmt: Stmt + ): Pair { + val type = declaringClass.type + val jClass = type.id.jClass + + // symbolic value for enum class itself + val enumClassValue = findOrCreateStaticObject(type) + + // values for enum constants + val enumConstantConcreteValues = jClass.enumConstants.filterIsInstance>() + + val (enumConstantSymbolicValues, enumConstantSymbolicResultsByName) = + makeSymbolicValuesFromEnumConcreteValues(type, enumConstantConcreteValues) + + val enumFields = typeResolver.findFields(type) + + val sootFieldsWithRuntimeValues = + associateEnumSootFieldsWithConcreteValues(enumFields, enumConstantConcreteValues) + + val (staticFields, nonStaticFields) = sootFieldsWithRuntimeValues.partition { it.first.isStatic } + + val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates( + staticFields, + declaringClass, + enumConstantSymbolicResultsByName, + enumConstantSymbolicValues, + enumClassValue, + fieldId + ) + + val nonStaticFieldsUpdates = makeEnumNonStaticFieldsUpdates(enumConstantSymbolicValues, nonStaticFields) + + // we do not mark static fields for enum constants and $VALUES as meaningful + // because we should not set them in generated code + val meaningfulStaticFields = staticFields.filterNot { + val name = it.first.name + + name in enumConstantSymbolicResultsByName.keys || isEnumValuesFieldName(name) + } + + val initializedStaticFieldsMemoryUpdate = MemoryUpdate( + initializedStaticFields = staticFields.associate { it.first.fieldId to it.second.single() }.toPersistentMap(), + meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet() + ) + + var allUpdates = staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate + + // we need to make locals update if it is an assignment statement + // for enums we have only two types for assignment with enums — enum constant or $VALUES field + // for example, a jimple body for Enum::values method starts with the following lines: + // public static ClassWithEnum$StatusEnum[] values() + // { + // ClassWithEnum$StatusEnum[] $r0, $r2; + // java.lang.Object $r1; + // $r0 = ; + // $r1 = virtualinvoke $r0.(); + + // so, we have to make an update for the local $r0 + if (stmt is JAssignStmt) { + val local = stmt.leftOp as JimpleLocal + val localUpdate = localMemoryUpdate( + local.variable to curFieldSymbolicValueForLocalVariable + ) + + allUpdates += localUpdate + } + + // enum static initializer can be the first statement in method so there will be no last edge + // for example, as it is during Enum::values method analysis: + // public static ClassWithEnum$StatusEnum[] values() + // { + // ClassWithEnum$StatusEnum[] $r0, $r2; + // java.lang.Object $r1; + + // $r0 = ; + val edge = environment.state.lastEdge ?: globalGraph.succ(stmt) + + return edge to allUpdates + } + + private fun makeConcreteUpdatesForNonEnumStaticField( + field: SootField, + fieldId: FieldId, + declaringClass: SootClass + ): Pair { val concreteValue = extractConcreteValue(field, declaringClass) val (symbolicResult, symbolicStateUpdate) = toMethodResult(concreteValue, field.type) val symbolicValue = (symbolicResult as SymbolicSuccess).value @@ -970,19 +1086,15 @@ class UtBotSymbolicEngine( // Collects memory updates val initializedFieldUpdate = MemoryUpdate(initializedStaticFields = persistentHashMapOf(fieldId to concreteValue)) + val objectUpdate = objectUpdate( instance = findOrCreateStaticObject(declaringClass.type), field = field, value = valueToExpression(symbolicValue, field.type) ) val allUpdates = symbolicStateUpdate + initializedFieldUpdate + objectUpdate - pathSelector.offer( - environment.state.updateQueued( - edge = environment.state.lastEdge!!, - allUpdates - ) - ) - return true + + return environment.state.lastEdge!! to allUpdates } // Some fields are inaccessible with reflection, so we have to instantiate it by ourselves. @@ -1200,7 +1312,7 @@ class UtBotSymbolicEngine( /** * Converts value to expression with cast to target type for primitives. */ - private fun valueToExpression(value: SymbolicValue, type: Type): UtExpression = when (value) { + fun valueToExpression(value: SymbolicValue, type: Type): UtExpression = when (value) { is ReferenceValue -> value.addr // TODO: shall we add additional constraint that aligned expression still equals original? // BitVector can lose valuable bites during extraction @@ -2028,6 +2140,7 @@ class UtBotSymbolicEngine( val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate) queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields) + // TODO filter enum constant static fields JIRA:1681 if (!environment.method.isStaticInitializer && !fieldId.isSynthetic) { queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId)) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt new file mode 100644 index 0000000000..84deca8af5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt @@ -0,0 +1,245 @@ +package org.utbot.engine.util.statics.concrete + +import org.utbot.common.withAccessibility +import org.utbot.engine.* +import org.utbot.engine.nullObjectAddr +import org.utbot.engine.pc.addrEq +import org.utbot.engine.pc.mkEq +import org.utbot.engine.pc.mkNot +import org.utbot.engine.pc.select +import org.utbot.engine.symbolic.SymbolicStateUpdate +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.field +import soot.SootClass +import soot.SootField +import soot.SootMethod +import soot.Type +import soot.Value +import soot.jimple.StaticFieldRef +import soot.jimple.Stmt +import soot.jimple.internal.JAssignStmt + +fun UtBotSymbolicEngine.makeSymbolicValuesFromEnumConcreteValues( + type: Type, + enumConstantRuntimeValues: List> +): Pair, Map> { + val enumConstantResultsWithNames = enumConstantRuntimeValues.map { + it.name to toMethodResult(it, type) + } + val enumConstantSymbolicValuesWithNames = enumConstantResultsWithNames.map { + it.first to (it.second.symbolicResult as SymbolicSuccess).value as ObjectValue + } + + val enumConstantSymbolicValues = enumConstantSymbolicValuesWithNames.map { it.second } + + val enumConstantSymbolicResultsByName = enumConstantResultsWithNames.toMap() + + return enumConstantSymbolicValues to enumConstantSymbolicResultsByName +} + +fun associateEnumSootFieldsWithConcreteValues( + enumFields: List, + enumConstants: List> +): List>> = + enumFields.map { enumSootField -> + val enumField = enumSootField.fieldId.field + + val fieldValues = if (enumSootField.isStatic) { + val staticFieldValue = enumField.withAccessibility { enumField.get(null) } + + listOf(staticFieldValue) + } else { + // extract ordinal, name and other non static fields for every enum constant + enumConstants.map { concreteValue -> + enumField.withAccessibility { enumField.get(concreteValue) } + } + } + + enumSootField to fieldValues + } + +/** + * Construct symbolic updates for enum static fields and a symbolic value for a local in the left part of the assignment. + */ +fun UtBotSymbolicEngine.makeEnumStaticFieldsUpdates( + staticFields: List>>, + declaringClass: SootClass, + enumConstantSymbolicResultsByName: Map, + enumConstantSymbolicValues: List, + enumClassValue: ObjectValue, + fieldId: FieldId +): Pair { + var staticFieldsUpdates = SymbolicStateUpdate() + var symbolicValueForLocal: SymbolicValue? = null + + staticFields.forEach { (sootStaticField, staticFieldRuntimeValue) -> + val fieldName = sootStaticField.name + val fieldType = sootStaticField.type + val (fieldSymbolicResult, fieldSymbolicStateUpdate) = constructEnumStaticFieldResult( + fieldName, + fieldType, + declaringClass, + enumConstantSymbolicResultsByName, + staticFieldRuntimeValue.single(), + enumConstantSymbolicValues + ) + + val fieldSymbolicValue = (fieldSymbolicResult as SymbolicSuccess).value + val fieldValue = valueToExpression(fieldSymbolicValue, fieldType) + + val fieldUpdate = objectUpdate(enumClassValue, sootStaticField, fieldValue) + + staticFieldsUpdates += fieldSymbolicStateUpdate + fieldUpdate + + // enum constant could not be null + if (fieldName in enumConstantSymbolicResultsByName) { + val canBeNull = addrEq(fieldSymbolicValue.addr, nullObjectAddr) + val canNotBeNull = mkNot(canBeNull) + + staticFieldsUpdates += canNotBeNull.asHardConstraint() + } + + // save value to associate it with local if required + if (sootStaticField.name == fieldId.name) { + symbolicValueForLocal = fieldSymbolicValue + } + } + + return staticFieldsUpdates to symbolicValueForLocal +} + +fun UtBotSymbolicEngine.makeEnumNonStaticFieldsUpdates( + enumConstantSymbolicValues: List, + nonStaticFields: List>> +): SymbolicStateUpdate { + var nonStaticFieldsUpdates = SymbolicStateUpdate() + + for ((i, enumConstantSymbolicValue) in enumConstantSymbolicValues.withIndex()) { + nonStaticFields.forEach { (sootNonStaticField, nonStaticFieldRuntimeValues) -> + val nonStaticFieldRuntimeValue = nonStaticFieldRuntimeValues[i] + + val fieldType = sootNonStaticField.type + val (fieldSymbolicResult, fieldSymbolicStateUpdate) = toMethodResult( + nonStaticFieldRuntimeValue, + fieldType + ) + + nonStaticFieldsUpdates += fieldSymbolicStateUpdate + + val fieldSymbolicValue = (fieldSymbolicResult as SymbolicSuccess).value + val fieldValue = valueToExpression(fieldSymbolicValue, fieldType) + + val chunkId = hierarchy.chunkIdForField(enumConstantSymbolicValue.type, sootNonStaticField) + val descriptor = MemoryChunkDescriptor(chunkId, enumConstantSymbolicValue.type, fieldType) + val array = memory.findArray(descriptor) + val arraySelectEqualsValue = mkEq(array.select(enumConstantSymbolicValue.addr), fieldValue) + + nonStaticFieldsUpdates += arraySelectEqualsValue.asHardConstraint() + } + } + + return nonStaticFieldsUpdates +} + +fun isEnumValuesFieldName(fieldName: String): Boolean = fieldName == "\$VALUES" + +/** + * Checks that [this] is enum which affects any external static fields in its / sections. + */ +fun SootClass.isEnumAffectingExternalStatics(typeResolver: TypeResolver): Boolean { + if (!isEnum) { + return false + } + + // enum active body contains invocations so we can check only + val staticInitializer = staticInitializerOrNull() ?: return false + + val implementedInterfaces = typeResolver + .findOrConstructAncestorsIncludingTypes(type) + .filter { type -> type.sootClass.isInterface } + + return staticInitializer.isTouchingExternalStatics(this, mutableSetOf(), implementedInterfaces) +} + +/** + * Returns whether [this] method touches any statics from any types + * except [currentClass] and its interfaces in [currentClassImplementedInterfaces]. + * + * NOTE: see org.utbot.examples.enums.ClassWithEnum.{EnumWithStaticAffectingInit, OuterStaticUsageEnum} for examples. + */ +fun SootMethod.isTouchingExternalStatics( + currentClass: SootClass, + alreadyProcessed: MutableSet, + currentClassImplementedInterfaces: List +): Boolean { + if (this in alreadyProcessed) { + return false + } + + alreadyProcessed += this + + // active body could be missing ((java.lang.String,int)>, for example) + // so consider it as not affecting external statics + if (!canRetrieveBody()) { + return false + } + + return jimpleBody().units.any { + if (it !is Stmt) { + return@any false + } + + when (it) { + is JAssignStmt -> { + val leftOp = it.leftOp + val rightOp = it.rightOp + + val assigningOuterStatics = isExternalStaticField( + leftOp, + currentClassImplementedInterfaces, + currentClass + ) + + val assignmentFromOuterStatics = isExternalStaticField( + rightOp, + currentClassImplementedInterfaces, + currentClass + ) + + assigningOuterStatics || assignmentFromOuterStatics + } + else -> { + if (it.containsInvokeExpr()) { + it.invokeExpr.method.isTouchingExternalStatics( + currentClass, + alreadyProcessed, + currentClassImplementedInterfaces + ) + } else { + false + } + } + } + } +} + +/** + * Determines whether [fieldRef] is static field not from [currentClass] + * (except static fields from all interfaces implemented by [currentClass], stored in [currentClassImplementedInterfaces]). + */ +private fun isExternalStaticField( + fieldRef: Value, + currentClassImplementedInterfaces: List, + currentClass: SootClass +): Boolean { + if (fieldRef !is StaticFieldRef) { + return false + } + + val declaringClass = fieldRef.field.declaringClass + + val classInImplementedInterfaces = declaringClass.type in currentClassImplementedInterfaces + + return !classInImplementedInterfaces && declaringClass != currentClass +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt index 40e751b6c0..af566de628 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt @@ -505,7 +505,7 @@ data class HangingTestsTimeout(val timeoutMs: Long) { companion object { const val DEFAULT_TIMEOUT_MS = DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS - const val MIN_TIMEOUT_MS = DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS + const val MIN_TIMEOUT_MS = 100L const val MAX_TIMEOUT_MS = 1_000_000L } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 0a877aefff..3c28c799f0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -70,8 +70,8 @@ fun getEnumConstantByName(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getEnumConstantByName(enumClass: Class<*>, name: String): Any? { - val fields: Array = enumClass.declaredFields + private fun getEnumConstantByName(enumClass: Class<*>, name: String): kotlin.Any? { + val fields: kotlin.Array = enumClass.declaredFields for (field in fields) { val fieldName = field.name if (field.isEnumConstant && fieldName == name) { @@ -114,7 +114,7 @@ fun getStaticFieldValue(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getStaticFieldValue(clazz: Class<*>, fieldName: String): Any? { + private fun getStaticFieldValue(clazz: Class<*>, fieldName: String): kotlin.Any? { var currentClass: Class<*>? = clazz var field: java.lang.reflect.Field do { @@ -164,7 +164,7 @@ fun getFieldValue(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getFieldValue(any: Any, fieldName: String): Any? { + private fun getFieldValue(any: kotlin.Any, fieldName: String): kotlin.Any? { var clazz: Class<*>? = any.javaClass var field: java.lang.reflect.Field do { @@ -214,7 +214,7 @@ fun setStaticField(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun setStaticField(defaultClass: Class<*>, fieldName: String, fieldValue: Any?) { + private fun setStaticField(defaultClass: Class<*>, fieldName: String, fieldValue: kotlin.Any?) { var field: java.lang.reflect.Field? var clazz = defaultClass @@ -266,7 +266,7 @@ fun setField(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun setField(any: Any, fieldName: String, fieldValue: Any?) { + private fun setField(any: kotlin.Any, fieldName: String, fieldValue: kotlin.Any?) { var clazz: Class<*> = any.javaClass var field: java.lang.reflect.Field? do { @@ -306,14 +306,18 @@ fun createArray(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun createArray(className: String, length: Int, vararg values: Any): Array { - val array: Any = java.lang.reflect.Array.newInstance(Class.forName(className), length) + private fun createArray( + className: String, + length: Int, + vararg values: kotlin.Any + ): kotlin.Array { + val array: kotlin.Any = java.lang.reflect.Array.newInstance(Class.forName(className), length) for (i in values.indices) { java.lang.reflect.Array.set(array, i, values[i]) } - return array as Array + return array as kotlin.Array } """ } @@ -332,7 +336,7 @@ fun createInstance(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun createInstance(className: String): Any? { + private fun createInstance(className: String): kotlin.Any? { val clazz: Class<*> = Class.forName(className) return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class::class.java) .invoke(getUnsafeInstance(), clazz) @@ -354,7 +358,7 @@ fun getUnsafeInstance(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getUnsafeInstance(): Any? { + private fun getUnsafeInstance(): kotlin.Any? { val f: java.lang.reflect.Field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe") f.isAccessible = true return f[null] @@ -403,10 +407,10 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } private boolean deepEquals(Object o1, Object o2) { - return deepEquals(o1, o2, new HashSet<>()); + return deepEquals(o1, o2, new java.util.HashSet<>()); } - private boolean deepEquals(Object o1, Object o2, Set visited) { + private boolean deepEquals(Object o1, Object o2, java.util.Set visited) { visited.add(new FieldsPair(o1, o2)); if (o1 == o2) { @@ -429,15 +433,15 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew return false; } - if (o1 instanceof Map) { - if (!(o2 instanceof Map)) { + if (o1 instanceof java.util.Map) { + if (!(o2 instanceof java.util.Map)) { return false; } - return mapsDeepEquals((Map) o1, (Map) o2, visited); + return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); } - if (o2 instanceof Map) { + if (o2 instanceof java.util.Map) { return false; } @@ -461,9 +465,9 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } // common classes without custom equals, use comparison by fields - final List fields = new ArrayList<>(); + final java.util.List fields = new java.util.ArrayList<>(); while (firstClass != Object.class) { - fields.addAll(Arrays.asList(firstClass.getDeclaredFields())); + fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); // Interface should not appear here firstClass = firstClass.getSuperclass(); } @@ -490,26 +494,30 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } CodegenLanguage.KOTLIN -> { """ - private fun deepEquals(o1: Any?, o2: Any?): Boolean = deepEquals(o1, o2, hashSetOf()) + private fun deepEquals(o1: kotlin.Any?, o2: kotlin.Any?): Boolean = deepEquals(o1, o2, hashSetOf()) - private fun deepEquals(o1: Any?, o2: Any?, visited: MutableSet>): Boolean { + private fun deepEquals( + o1: kotlin.Any?, + o2: kotlin.Any?, + visited: kotlin.collections.MutableSet> + ): Boolean { visited += o1 to o2 if (o1 === o2) return true if (o1 == null || o2 == null) return false - if (o1 is Iterable<*>) { - return if (o2 !is Iterable<*>) false else iterablesDeepEquals(o1, o2, visited) + if (o1 is kotlin.collections.Iterable<*>) { + return if (o2 !is kotlin.collections.Iterable<*>) false else iterablesDeepEquals(o1, o2, visited) } - if (o2 is Iterable<*>) return false + if (o2 is kotlin.collections.Iterable<*>) return false - if (o1 is Map<*, *>) { - return if (o2 !is Map<*, *>) false else mapsDeepEquals(o1, o2, visited) + if (o1 is kotlin.collections.Map<*, *>) { + return if (o2 !is kotlin.collections.Map<*, *>) false else mapsDeepEquals(o1, o2, visited) } - if (o2 is Map<*, *>) return false + if (o2 is kotlin.collections.Map<*, *>) return false var firstClass: Class<*> = o1.javaClass if (firstClass.isArray) { @@ -528,8 +536,8 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } // common classes without custom equals, use comparison by fields - val fields: MutableList = mutableListOf() - while (firstClass != Any::class.java) { + val fields: kotlin.collections.MutableList = mutableListOf() + while (firstClass != kotlin.Any::class.java) { fields += listOf(*firstClass.declaredFields) // Interface should not appear here firstClass = firstClass.superclass @@ -559,14 +567,14 @@ fun arraysDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean arraysDeepEquals(Object arr1, Object arr2, Set visited) { - final int length = Array.getLength(arr1); - if (length != Array.getLength(arr2)) { + private boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { + final int length = java.lang.reflect.Array.getLength(arr1); + if (length != java.lang.reflect.Array.getLength(arr2)) { return false; } for (int i = 0; i < length; i++) { - if (!deepEquals(Array.get(arr1, i), Array.get(arr2, i), visited)) { + if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { return false; } } @@ -577,7 +585,11 @@ fun arraysDeepEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun arraysDeepEquals(arr1: Any?, arr2: Any?, visited: MutableSet>): Boolean { + private fun arraysDeepEquals( + arr1: kotlin.Any?, + arr2: kotlin.Any?, + visited: kotlin.collections.MutableSet> + ): Boolean { val size = java.lang.reflect.Array.getLength(arr1) if (size != java.lang.reflect.Array.getLength(arr2)) return false @@ -597,9 +609,9 @@ fun iterablesDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean iterablesDeepEquals(Iterable i1, Iterable i2, Set visited) { - final Iterator firstIterator = i1.iterator(); - final Iterator secondIterator = i2.iterator(); + private boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { + final java.util.Iterator firstIterator = i1.iterator(); + final java.util.Iterator secondIterator = i2.iterator(); while (firstIterator.hasNext() && secondIterator.hasNext()) { if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { return false; @@ -616,7 +628,11 @@ fun iterablesDeepEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun iterablesDeepEquals(i1: Iterable<*>, i2: Iterable<*>, visited: MutableSet>): Boolean { + private fun iterablesDeepEquals( + i1: Iterable<*>, + i2: Iterable<*>, + visited: kotlin.collections.MutableSet> + ): Boolean { val firstIterator = i1.iterator() val secondIterator = i2.iterator() while (firstIterator.hasNext() && secondIterator.hasNext()) { @@ -633,12 +649,16 @@ fun mapsDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean mapsDeepEquals(Map m1, Map m2, Set visited) { - final Iterator> firstIterator = m1.entrySet().iterator(); - final Iterator> secondIterator = m2.entrySet().iterator(); + private boolean mapsDeepEquals( + java.util.Map m1, + java.util.Map m2, + java.util.Set visited + ) { + final java.util.Iterator> firstIterator = m1.entrySet().iterator(); + final java.util.Iterator> secondIterator = m2.entrySet().iterator(); while (firstIterator.hasNext() && secondIterator.hasNext()) { - final Map.Entry firstEntry = firstIterator.next(); - final Map.Entry secondEntry = secondIterator.next(); + final java.util.Map.Entry firstEntry = firstIterator.next(); + final java.util.Map.Entry secondEntry = secondIterator.next(); if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { return false; @@ -662,7 +682,7 @@ fun mapsDeepEquals(language: CodegenLanguage): String = private fun mapsDeepEquals( m1: kotlin.collections.Map<*, *>, m2: kotlin.collections.Map<*, *>, - visited: MutableSet> + visited: kotlin.collections.MutableSet> ): Boolean { val firstIterator = m1.entries.iterator() val secondIterator = m2.entries.iterator() @@ -704,9 +724,9 @@ fun hasCustomEquals(language: CodegenLanguage): String = """ private fun hasCustomEquals(clazz: Class<*>): Boolean { var c = clazz - while (Any::class.java != c) { + while (kotlin.Any::class.java != c) { try { - c.getDeclaredMethod("equals", Any::class.java) + c.getDeclaredMethod("equals", kotlin.Any::class.java) return true } catch (e: Exception) { // Interface should not appear here @@ -724,12 +744,12 @@ fun getArrayLength(codegenLanguage: CodegenLanguage) = CodegenLanguage.JAVA -> """ private static int getArrayLength(Object arr) { - return Array.getLength(arr); + return java.lang.reflect.Array.getLength(arr); } """.trimIndent() CodegenLanguage.KOTLIN -> """ - private fun getArrayLength(arr: Any?): Int = java.lang.reflect.Array.getLength(arr) + private fun getArrayLength(arr: kotlin.Any?): Int = java.lang.reflect.Array.getLength(arr) """.trimIndent() } diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt index 61ce16c4c2..1587b5661e 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt @@ -1233,6 +1233,101 @@ abstract class AbstractTestCaseGeneratorTest( summaryDisplayNameChecks = summaryDisplayNameChecks ) + protected inline fun checkThisAndStaticsAfter( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + summaryTextChecks: List<(List?) -> Boolean> = listOf(), + summaryNameChecks: List<(String?) -> Boolean> = listOf(), + summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + summaryTextChecks = summaryTextChecks, + summaryNameChecks = summaryNameChecks, + summaryDisplayNameChecks = summaryDisplayNameChecks + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + summaryTextChecks: List<(List?) -> Boolean> = listOf(), + summaryNameChecks: List<(String?) -> Boolean> = listOf(), + summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + summaryTextChecks = summaryTextChecks, + summaryNameChecks = summaryNameChecks, + summaryDisplayNameChecks = summaryDisplayNameChecks + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + summaryTextChecks: List<(List?) -> Boolean> = listOf(), + summaryNameChecks: List<(String?) -> Boolean> = listOf(), + summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + summaryTextChecks = summaryTextChecks, + summaryNameChecks = summaryNameChecks, + summaryDisplayNameChecks = summaryDisplayNameChecks + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + summaryTextChecks: List<(List?) -> Boolean> = listOf(), + summaryNameChecks: List<(String?) -> Boolean> = listOf(), + summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + summaryTextChecks = summaryTextChecks, + summaryNameChecks = summaryNameChecks, + summaryDisplayNameChecks = summaryDisplayNameChecks + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + summaryTextChecks: List<(List?) -> Boolean> = listOf(), + summaryNameChecks: List<(String?) -> Boolean> = listOf(), + summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + summaryTextChecks = summaryTextChecks, + summaryNameChecks = summaryNameChecks, + summaryDisplayNameChecks = summaryDisplayNameChecks + ) + // checks paramsBefore, staticsBefore and return value for static methods protected inline fun checkStaticsInStaticMethod( method: KFunction0, @@ -2528,6 +2623,7 @@ fun withResult(ex: UtValueExecution<*>) = ex.paramsBefore + ex.evaluatedResult fun withException(ex: UtValueExecution<*>) = ex.paramsBefore + ex.returnValue fun withStaticsBefore(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsBefore + ex.evaluatedResult fun withStaticsAfter(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsAfter + ex.evaluatedResult +fun withThisAndStaticsAfter(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.staticsAfter + ex.evaluatedResult fun withThisAndResult(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.evaluatedResult fun withThisStaticsBeforeAndResult(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.staticsBefore + ex.evaluatedResult fun withThisAndException(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.returnValue @@ -2669,3 +2765,13 @@ inline fun withPushingStateFromPathSelectorForConcrete(block: () -> UtSettings.saveRemainingStatesForConcreteExecution = prev } } + +inline fun withTreatingOverflowAsError(block: () -> T): T { + val prev = UtSettings.treatOverflowAsError + UtSettings.treatOverflowAsError = true + try { + return block() + } finally { + UtSettings.treatOverflowAsError = prev + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt index 60a9d55d34..900aa0d896 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt @@ -35,7 +35,7 @@ import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor @ExtendWith(CodeTestCaseGeneratorTest.Companion.ReadRunningTestsNumberBeforeAllTestsCallback::class) abstract class CodeTestCaseGeneratorTest( private val testClass: KClass<*>, - private val testCodeGeneration: Boolean = true, + private var testCodeGeneration: Boolean = true, private val languagesLastStages: List = listOf( CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN) @@ -49,6 +49,17 @@ abstract class CodeTestCaseGeneratorTest( if (testCodeGeneration) testCases += testCase } + protected fun withEnabledTestingCodeGeneration(testCodeGeneration: Boolean, block: () -> Unit) { + val prev = this.testCodeGeneration + + try { + this.testCodeGeneration = testCodeGeneration + block() + } finally { + this.testCodeGeneration = prev + } + } + // save all generated test cases from current class to test code generation private fun addTestCase(pkg: Package) { if (testCodeGeneration) { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranchesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranchesTest.kt deleted file mode 100644 index ec12cd5440..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranchesTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.utbot.examples.enums - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -class ClassWithEnumInsideDifficultBranchesTest : AbstractTestCaseGeneratorTest(testClass = ClassWithEnumInsideDifficultBranches::class) { - @Test - @Disabled("TODO JIRA:1612") - fun testDifficultIfBranch() { - check( - ClassWithEnumInsideDifficultBranches::useEnumInDifficultIf, - eq(2), - { s, r -> s.equals("TRYIF", ignoreCase = true) && r == 1 }, - { s, r -> !s.equals("TRYIF", ignoreCase = true) && r == 2 } - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt index c264da89ea..079bf1d01c 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt @@ -1,16 +1,197 @@ package org.utbot.examples.enums +import org.utbot.common.findField import org.utbot.examples.AbstractTestCaseGeneratorTest +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.enums.ClassWithEnum.StatusEnum.ERROR +import org.utbot.examples.enums.ClassWithEnum.StatusEnum.READY +import org.utbot.examples.eq +import org.utbot.examples.isException +import org.utbot.examples.withPushingStateFromPathSelectorForConcrete import org.utbot.examples.withoutConcrete +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.id import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class ClassWithEnumTest : AbstractTestCaseGeneratorTest(testClass = ClassWithEnum::class) { @Test - @Disabled("TODO JIRA:1611") fun testOrdinal() { withoutConcrete { checkAllCombinations(ClassWithEnum::useOrdinal) } } + + @Test + fun testGetter() { + check( + ClassWithEnum::useGetter, + eq(2), + { s, r -> s == null && r == -1 }, + { s, r -> s != null && r == 0 }, + ) + } + + @Test + fun testDifficultIfBranch() { + check( + ClassWithEnum::useEnumInDifficultIf, + eq(2), + { s, r -> s.equals("TRYIF", ignoreCase = true) && r == 1 }, + { s, r -> !s.equals("TRYIF", ignoreCase = true) && r == 2 }, + ) + } + + @Test + @Disabled("TODO JIRA:1686") + fun testNullParameter() { + check( + ClassWithEnum::nullEnumAsParameter, + eq(3), + { e, _ -> e == null }, + { e, r -> e == READY && r == 0 }, + { e, r -> e == ERROR && r == -1 }, + ) + } + + @Test + @Disabled("TODO JIRA:1686") + fun testNullField() { + checkWithException( + ClassWithEnum::nullField, + eq(3), + { e, r -> e == null && r.isException() }, + { e, r -> e == ERROR && r.isException() }, + { e, r -> e == READY && r.getOrNull()!! == 3 && READY.s.length == 3 }, + ) + } + + @Test + @Disabled("TODO JIRA:1686") + fun testChangeEnum() { + checkWithException( + ClassWithEnum::changeEnum, + eq(3), + { e, r -> e == null && r.isException() }, + { e, r -> e == READY && r.getOrNull()!! == ERROR.ordinal }, + { e, r -> e == ERROR && r.getOrNull()!! == READY.ordinal }, + ) + } + + @Test + fun testChangeMutableField() { + // TODO testing code generation for this method is disabled because we need to restore original field state + // should be enabled after solving JIRA:1648 + withEnabledTestingCodeGeneration(testCodeGeneration = false) { + checkWithException( + ClassWithEnum::changeMutableField, + eq(2), + { e, r -> e == READY && r.getOrNull()!! == 2 }, + { e, r -> (e == null || e == ERROR) && r.getOrNull()!! == -2 }, + ) + } + } + + @Test + @Disabled("TODO JIRA:1686") + fun testCheckName() { + check( + ClassWithEnum::checkName, + eq(3), + { s, _ -> s == null }, + { s, r -> s == READY.name && r == ERROR.name }, + { s, r -> s != READY.name && r == READY.name }, + ) + } + + @Test + fun testChangingStaticWithEnumInit() { + checkThisAndStaticsAfter( + ClassWithEnum::changingStaticWithEnumInit, + eq(1), + { t, staticsAfter, r -> + // for some reasons x is inaccessible + val x = t.javaClass.findField("x").get(t) as Int + + val y = staticsAfter[FieldId(ClassWithEnum.ClassWithStaticField::class.id, "y")]!!.value as Int + + val areStaticsCorrect = x == 1 && y == 11 + areStaticsCorrect && r == true + } + ) + } + + @Test + fun testEnumValues() { + checkStaticMethod( + ClassWithEnum.StatusEnum::values, + eq(1), + { r -> r.contentEquals(arrayOf(READY, ERROR)) }, + ) + } + + @Test + fun testFromCode() { + checkStaticMethod( + ClassWithEnum.StatusEnum::fromCode, + eq(3), + { code, r -> code == 10 && r == READY }, + { code, r -> code == -10 && r == ERROR }, + { code, r -> code !in setOf(10, -10) && r == null }, // IllegalArgumentException + ) + } + + @Test + fun testFromIsReady() { + checkStaticMethod( + ClassWithEnum.StatusEnum::fromIsReady, + eq(2), + { isFirst, r -> isFirst && r == READY }, + { isFirst, r -> !isFirst && r == ERROR }, + ) + } + + @Test + @Disabled("TODO JIRA:1450") + fun testPublicGetCodeMethod() { + checkWithThis( + ClassWithEnum.StatusEnum::publicGetCode, + eq(2), + { enumInstance, r -> enumInstance == READY && r == 10 }, + { enumInstance, r -> enumInstance == ERROR && r == -10 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testImplementingInterfaceEnumInDifficultBranch() { + withPushingStateFromPathSelectorForConcrete { + check( + ClassWithEnum::implementingInterfaceEnumInDifficultBranch, + eq(2), + { s, r -> s.equals("SUCCESS", ignoreCase = true) && r == 0 }, + { s, r -> !s.equals("SUCCESS", ignoreCase = true) && r == 2 }, + ) + } + } + + @Test + fun testAffectSystemStaticAndUseInitEnumFromIt() { + check( + ClassWithEnum::affectSystemStaticAndInitEnumFromItAndReturnField, + eq(1), + { r -> r == true }, + coverage = DoNotCalculate + ) + } + + @Test + fun testAffectSystemStaticAndInitEnumFromItAndGetItFromEnumFun() { + check( + ClassWithEnum::affectSystemStaticAndInitEnumFromItAndGetItFromEnumFun, + eq(1), + { r -> r == true }, + coverage = DoNotCalculate + ) + } } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt index ba76a60f40..a14c592c1f 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt @@ -1,5 +1,7 @@ package org.utbot.examples.math +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test import org.utbot.examples.AbstractTestCaseGeneratorTest import org.utbot.examples.AtLeast import org.utbot.examples.algorithms.Sort @@ -7,15 +9,11 @@ import org.utbot.examples.eq import org.utbot.examples.ignoreExecutionsNumber import org.utbot.examples.isException import org.utbot.examples.withSolverTimeoutInMillis -import org.utbot.framework.UtSettings +import org.utbot.examples.withTreatingOverflowAsError import org.utbot.framework.codegen.Compilation import org.utbot.framework.plugin.api.CodegenLanguage import kotlin.math.floor import kotlin.math.sqrt -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test internal class OverflowAsErrorTest : AbstractTestCaseGeneratorTest( testClass = OverflowExamples::class, @@ -27,133 +25,140 @@ internal class OverflowAsErrorTest : AbstractTestCaseGeneratorTest( CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, Compilation), ) ) { - private val treatOverflowAsError = UtSettings.treatOverflowAsError - - @BeforeAll - fun beforeAll() { - UtSettings.treatOverflowAsError = true - } - - @AfterAll - fun afterAll() { - UtSettings.treatOverflowAsError = treatOverflowAsError - } - @Test fun testIntOverflow() { - checkWithException( - OverflowExamples::intOverflow, - eq(5), - { x, _, r -> x * x * x <= 0 && x > 0 && r.isException() }, // through overflow - { x, _, r -> x * x * x <= 0 && x > 0 && r.isException() }, // through overflow (2nd '*') - { x, _, r -> x * x * x >= 0 && x >= 0 && r.getOrNull() == 0 }, - { x, y, r -> x * x * x > 0 && x > 0 && y == 10 && r.getOrNull() == 1 }, - { x, y, r -> x * x * x > 0 && x > 0 && y != 10 && r.getOrNull() == 0 }, - coverage = AtLeast(90), - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intOverflow, + eq(5), + { x, _, r -> x * x * x <= 0 && x > 0 && r.isException() }, // through overflow + { x, _, r -> x * x * x <= 0 && x > 0 && r.isException() }, // through overflow (2nd '*') + { x, _, r -> x * x * x >= 0 && x >= 0 && r.getOrNull() == 0 }, + { x, y, r -> x * x * x > 0 && x > 0 && y == 10 && r.getOrNull() == 1 }, + { x, y, r -> x * x * x > 0 && x > 0 && y != 10 && r.getOrNull() == 0 }, + coverage = AtLeast(90), + ) + } } @Test fun testByteAddOverflow() { - checkWithException( - OverflowExamples::byteAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y).toByte() >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y).toByte() <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y).toByte() >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y).toByte() <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test fun testByteSubOverflow() { - checkWithException( - OverflowExamples::byteSubOverflow, - eq(2), - { _, _, r -> !r.isException()}, - { x, y, r -> - val negOverflow = ((x - y).toByte() >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y).toByte() <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException()}, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y).toByte() >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y).toByte() <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test fun testByteMulOverflow() { - checkWithException( - OverflowExamples::byteMulOverflow, - eq(2), - { _, _, r -> !r.isException()}, - { _, _, r -> r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } } @Test fun testShortAddOverflow() { - checkWithException( - OverflowExamples::shortAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y).toShort() >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y).toShort() <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::shortAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y).toShort() >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y).toShort() <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test fun testShortSubOverflow() { - checkWithException( - OverflowExamples::shortSubOverflow, - eq(2), - { _, _, r -> !r.isException()}, - { x, y, r -> - val negOverflow = ((x - y).toShort() >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y).toShort() <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::shortSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y).toShort() >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y).toShort() <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test fun testShortMulOverflow() { - checkWithException( - OverflowExamples::shortMulOverflow, - eq(2), - { _, _, r -> !r.isException()}, - { _, _, r -> r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::shortMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } } @Test fun testIntAddOverflow() { - checkWithException( - OverflowExamples::intAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test fun testIntSubOverflow() { - checkWithException( - OverflowExamples::intSubOverflow, - eq(2), - { _, _, r -> !r.isException()}, - { x, y, r -> - val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test @@ -162,41 +167,47 @@ internal class OverflowAsErrorTest : AbstractTestCaseGeneratorTest( // Reason: softConstraints, containing limits for Int values, hang solver. // With solver timeout softConstraints are dropped and hard constraints are SAT for overflow. withSolverTimeoutInMillis(timeoutInMillis = 1000) { - checkWithException( - OverflowExamples::intMulOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { _, _, r -> r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } } } @Test fun testLongAddOverflow() { - checkWithException( - OverflowExamples::longAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::longAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test fun testLongSubOverflow() { - checkWithException( - OverflowExamples::longSubOverflow, - eq(2), - { _, _, r -> !r.isException()}, - { x, y, r -> - val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::longSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } } @Test @@ -205,49 +216,57 @@ internal class OverflowAsErrorTest : AbstractTestCaseGeneratorTest( // Reason: softConstraints, containing limits for Int values, hang solver. // With solver timeout softConstraints are dropped and hard constraints are SAT for overflow. withSolverTimeoutInMillis(timeoutInMillis = 2000) { - checkWithException( - OverflowExamples::longMulOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { _, _, r -> r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::longMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } } } @Test fun testIncOverflow() { - checkWithException( - OverflowExamples::incOverflow, - eq(2), - { _, r -> !r.isException()}, - { _, r -> r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::incOverflow, + eq(2), + { _, r -> !r.isException() }, + { _, r -> r.isException() }, // through overflow + ) + } } @Test fun testIntCubeOverflow() { val sqrtIntMax = floor(sqrt(Int.MAX_VALUE.toDouble())).toInt() - checkWithException( - OverflowExamples::intCubeOverflow, - eq(3), - { _, r -> !r.isException()}, - // Can't use abs(x) below, because abs(Int.MIN_VALUE) == Int.MIN_VALUE. - // (Int.MAX_VALUE shr 16) is the border of square overflow and cube overflow. - // Int.MAX_VALUE.toDouble().pow(1/3.toDouble()) - { x, r -> (x > -sqrtIntMax && x < sqrtIntMax ) && r.isException() }, // through overflow - { x, r -> (x <= -sqrtIntMax || x >= sqrtIntMax) && r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intCubeOverflow, + eq(3), + { _, r -> !r.isException() }, + // Can't use abs(x) below, because abs(Int.MIN_VALUE) == Int.MIN_VALUE. + // (Int.MAX_VALUE shr 16) is the border of square overflow and cube overflow. + // Int.MAX_VALUE.toDouble().pow(1/3.toDouble()) + { x, r -> (x > -sqrtIntMax && x < sqrtIntMax) && r.isException() }, // through overflow + { x, r -> (x <= -sqrtIntMax || x >= sqrtIntMax) && r.isException() }, // through overflow + ) + } } // Generated Kotlin code does not compile, so disabled for now @Test @Disabled fun testQuickSort() { - checkWithException( - Sort::quickSort, - ignoreExecutionsNumber, - { _, _, _, r -> !r.isException()}, - { _, _, _, r -> r.isException() }, // through overflow - ) + withTreatingOverflowAsError { + checkWithException( + Sort::quickSort, + ignoreExecutionsNumber, + { _, _, _, r -> !r.isException() }, + { _, _, _, r -> r.isException() }, // through overflow + ) + } } } diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/EnumsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/EnumsTest.kt deleted file mode 100644 index 09f10a080a..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/EnumsTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.objects.SimpleEnum.FIRST -import org.utbot.examples.objects.SimpleEnum.SECOND -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -internal class EnumsTest : AbstractTestCaseGeneratorTest(testClass = SimpleEnum::class) { - @Test - fun testEnumValues() { - checkStaticMethod( - SimpleEnum::values, - eq(1), - { r -> r.contentEquals(arrayOf(FIRST, SECOND)) }, - // TODO: loader MemoryClassLoader attempted duplicate class definition - coverage = DoNotCalculate - ) - } - - @Test - fun testFromCode() { - checkStaticMethod( - SimpleEnum::fromCode, - eq(3), - { code, r -> code == 1 && r == FIRST }, - { code, r -> code == 2 && r == SECOND }, - { code, r -> code !in 1..2 && r == null }, // IllegalArgumentException - // TODO: CallerImpl$Method cannot access a member of class SimpleEnum with modifiers "static" - coverage = DoNotCalculate - ) - } - - @Test - fun testFromIsFirst() { - checkStaticMethod( - SimpleEnum::fromIsFirst, - eq(2), - { isFirst, r -> isFirst && r == FIRST }, - { isFirst, r -> !isFirst && r == SECOND }, - // TODO: CallerImpl$Method cannot access a member of class SimpleEnum with modifiers "static" - coverage = DoNotCalculate - ) - } - - @Test - @Disabled("JIRA:1450") - fun testPublicGetCodeMethod() { - checkWithThis( - SimpleEnum::publicGetCode, - eq(2), - { enumInstance, r -> enumInstance == FIRST && r == 1 }, - { enumInstance, r -> enumInstance == SECOND && r == 2 }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java index 4c3fe80aa5..14a0ded3fc 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java @@ -1,37 +1,241 @@ package org.utbot.examples.enums; -import static org.utbot.examples.enums.ClassWithEnum.StatusEnum.*; +import static org.utbot.examples.enums.ClassWithEnum.ManyConstantsEnum.A; +import static org.utbot.examples.enums.ClassWithEnum.ManyConstantsEnum.B; +import static org.utbot.examples.enums.ClassWithEnum.StatusEnum.ERROR; +import static org.utbot.examples.enums.ClassWithEnum.StatusEnum.READY; +@SuppressWarnings({"UnnecessaryLocalVariable", "IfStatementWithIdenticalBranches"}) public class ClassWithEnum { public int useOrdinal(String s) { if (s != null) { - return READY.ordinal(); + final int ordinal = READY.ordinal(); + return ordinal; } else { - return ERROR.ordinal(); + final int ordinal = ERROR.ordinal(); + return ordinal; } } - @SuppressWarnings("unused") public int useGetter(String s) { if (s != null) { - return READY.getX(); + final int mutableInt = READY.getMutableInt(); + return mutableInt; + } else { + final int mutableInt = ERROR.getMutableInt(); + return mutableInt; + } + } + + @SuppressWarnings("UnnecessaryLocalVariable") + public int useEnumInDifficultIf(String s) { + if ("TRYIF".equalsIgnoreCase(s)) { + final ManyConstantsEnum[] values = ManyConstantsEnum.values(); + return foo(values[0]); } else { - return ERROR.getX(); + final ManyConstantsEnum b = B; + return foo(b); + } + } + + private int foo(ManyConstantsEnum e) { + if (e.equals(A)) { + return 1; + } else { + return 2; + } + } + + public int nullEnumAsParameter(StatusEnum statusEnum) { + final int ordinal = statusEnum.ordinal(); + return ordinal; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public int nullField(StatusEnum statusEnum) { + // catch NPE + statusEnum.s.length(); + + statusEnum.s = "404"; + return statusEnum.s.length(); + } + + public int changeEnum(StatusEnum statusEnum) { + if (statusEnum == READY) { + statusEnum = ERROR; + } else { + statusEnum = READY; + } + + return statusEnum.ordinal(); + } + + public String checkName(String s) { + final String name = READY.name(); + if (s.equals(name)) { + return ERROR.name(); + } + + return READY.name(); + } + + public int changeMutableField(StatusEnum statusEnum) { + if (statusEnum == READY) { + READY.mutableInt = 2; + + return READY.mutableInt; } + + ERROR.mutableInt = -2; + return ERROR.mutableInt; + } + + @SuppressWarnings("unused") + public boolean changingStaticWithEnumInit() { + // run and sections + final EnumWithStaticAffectingInit[] values = EnumWithStaticAffectingInit.values(); + + return true; } enum StatusEnum { - READY(0), - ERROR(-1); + READY(0, 10, "200"), + ERROR(-1, -10, null); + + int mutableInt; + final int code; + String s; + + StatusEnum(int mutableInt, final int code, String s) { + this.mutableInt = mutableInt; + this.code = code; + this.s = s; + } + + public int getMutableInt() { + return mutableInt; + } + + public int getCode() { + return code; + } + + static StatusEnum fromCode(int code) { + for (StatusEnum value : values()) { + if (value.getCode() == code) { + return value; + } + } + + throw new IllegalArgumentException("No enum corresponding to given code: " + code); + } + + static StatusEnum fromIsReady(boolean isReady) { + return isReady ? READY : ERROR; + } + + int publicGetCode() { + return this == READY ? 10 : -10; + } + } + + enum ManyConstantsEnum { + A, B, C, D, E, F, G, H, I, J, K + } + + static int x = 0; + + static class ClassWithStaticField { + static int y = 0; + + static void increment() { + y++; + } + } + + enum EnumWithStaticAffectingInit { + A, B; + + EnumWithStaticAffectingInit() { + ClassWithStaticField.y++; + ClassWithStaticField.increment(); + invokeIncrement(); + invokeIncrementStatic(); + + // changes after all init sections: + // y = y + 4 * 2 = y + 8 + } + + static { + x++; + ClassWithStaticField.y++; + ClassWithStaticField.increment(); + invokeIncrementStatic(); + + // changes after clinit section: + // y = y + 3 + // x = x + 1 + } + + void invokeIncrement() { + ClassWithStaticField.increment(); + } + + static void invokeIncrementStatic() { + ClassWithStaticField.increment(); + } + } + + public int implementingInterfaceEnumInDifficultBranch(String s) { + if ("SUCCESS".equalsIgnoreCase(s)) { + return EnumImplementingInterface.x + EnumImplementingInterface.A_INHERITOR.ordinal(); + } else { + return EnumImplementingInterface.y + EnumImplementingInterface.B_INHERITOR.ordinal(); + } + } + + interface AncestorInterface { + int y = 1; + } + + interface InterfaceWithField extends AncestorInterface { + int x = 0; + } + + enum EnumImplementingInterface implements InterfaceWithField { + A_INHERITOR, B_INHERITOR, C_INHERITOR, D_INHERITOR, + E_INHERITOR, F_INHERITOR, G_INHERITOR, H_INHERITOR, + I_INHERITOR, J_INHERITOR, K_INHERITOR, L_INHERITOR, + M_INHERITOR, N_INHERITOR, O_INHERITOR, P_INHERITOR, + } + + boolean affectSystemStaticAndInitEnumFromItAndReturnField() { + int prevStaticValue = ClassWithEnum.staticInt; + staticInt++; + + return OuterStaticUsageEnum.A.y != prevStaticValue; + } + + boolean affectSystemStaticAndInitEnumFromItAndGetItFromEnumFun() { + int prevStaticValue = ClassWithEnum.staticInt; + staticInt++; + + return OuterStaticUsageEnum.A.getOuterStatic() != prevStaticValue; + } + + static int staticInt = 0; + + enum OuterStaticUsageEnum { + A; - int x; + int y; - StatusEnum(int x) { - this.x = x; + OuterStaticUsageEnum() { + y = staticInt; } - public int getX() { - return x; + int getOuterStatic() { + return staticInt; } } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranches.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranches.java deleted file mode 100644 index 76bfaeb37e..0000000000 --- a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnumInsideDifficultBranches.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.utbot.examples.enums; - -public class ClassWithEnumInsideDifficultBranches { - public int useEnumInDifficultIf(String s) { - if ("TRYIF".equalsIgnoreCase(s)) { - return foo(ManyConstantsEnum.A); - } else { - return foo(ManyConstantsEnum.B); - } - } - - private int foo(ManyConstantsEnum e) { - if (e.equals(ManyConstantsEnum.A)) { - return 1; - } else { - return 2; - } - } - - @SuppressWarnings("unused") - enum ManyConstantsEnum { - A, B, C, D, E, F, G, H, I, J, K - } -} diff --git a/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionClusteringExamples.java b/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionClusteringExamples.java index 0f8dea29c4..1142a9a685 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionClusteringExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionClusteringExamples.java @@ -20,7 +20,7 @@ public int differentExceptionsInNestedCall(int i) throws MyCheckedException { } public int sleepingMoreThanDefaultTimeout(int i) throws InterruptedException { - Thread.sleep(500L); + Thread.sleep(1500L); if (i < 0) { throw new RuntimeException(); diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/SimpleEnum.java b/utbot-sample/src/main/java/org/utbot/examples/objects/SimpleEnum.java deleted file mode 100644 index f8b3ca4163..0000000000 --- a/utbot-sample/src/main/java/org/utbot/examples/objects/SimpleEnum.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.utbot.examples.objects; - -public enum SimpleEnum { - FIRST(1), - SECOND(2); - - private final int code; - - SimpleEnum(int code) { - this.code = code; - } - - static SimpleEnum fromCode(int code) { - for (SimpleEnum value : values()) { - if (value.getCode() == code) { - return value; - } - } - throw new IllegalArgumentException("No enum corresponding to given code: " + code); - } - - private int getCode() { - return code; - } - - static SimpleEnum fromIsFirst(boolean isFirst) { - return isFirst ? FIRST : SECOND; - } - - int publicGetCode() { - return this == FIRST ? 1 : 2; - } -} \ No newline at end of file From b0be384b7d5d100f39783f75e0c30bdbade8b78b Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 26 May 2022 12:44:20 +0300 Subject: [PATCH 005/120] Fix stack overflow error in AssembleModelGenerator.kt for arrays --- .../org/utbot/framework/plugin/api/Api.kt | 2 +- .../assemble/AssembleModelGenerator.kt | 25 +++++++++++-------- .../examples/arrays/ArrayOfArraysTest.kt | 10 ++++++++ .../utbot/examples/arrays/ArrayOfArrays.java | 10 ++++++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index d67044b753..72e11211ba 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -420,7 +420,7 @@ data class UtArrayModel( override val id: Int, override val classId: ClassId, val length: Int = 0, - val constModel: UtModel, + var constModel: UtModel, val stores: MutableMap ) : UtReferenceModel(id, classId) { override fun toString() = withToStringThreadLocalReentrancyGuard { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt index fad4158d4d..ce2f389c39 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt @@ -195,19 +195,24 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { /** * Assembles internal structure of [UtArrayModel]. */ - private fun assembleArrayModel(arrayModel: UtArrayModel): UtModel { - instantiatedModels[arrayModel]?.let { return it } + private fun assembleArrayModel(arrayModel: UtArrayModel): UtModel = + with(arrayModel) { + instantiatedModels[this]?.let { return it } - val constModel = assembleModel(arrayModel.constModel) - val stores = arrayModel.stores - .mapValues { assembleModel(it.value) } - .toMutableMap() + // Note that we use constModel from the source model as is here to avoid + // possible stack overflow error in case when const model has the same + // id as the source one. Later we will try to transform it. + val assembleModel = UtArrayModel(id, classId, length, constModel, stores = mutableMapOf()) - val assembleModel = UtArrayModel(arrayModel.id, arrayModel.classId, arrayModel.length, constModel, stores) + instantiatedModels[this] = assembleModel - instantiatedModels[arrayModel] = assembleModel - return assembleModel - } + assembleModel.constModel = assembleModel(constModel) + assembleModel.stores += stores + .mapValues { assembleModel(it.value) } + .toMutableMap() + + assembleModel + } /** * Assembles internal structure of [UtCompositeModel] if possible and handles assembling exceptions. diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt index 864a41da30..e7c660e426 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt @@ -266,4 +266,14 @@ internal class ArrayOfArraysTest : AbstractTestCaseGeneratorTest(testClass = Arr { valueBefore, valueAfter -> valueAfter.withIndex().all { it.value == valueBefore[it.index] + it.index } } ) } + + @Test + fun testArrayWithItselfAnAsElement() { + check( + ArrayOfArrays::arrayWithItselfAnAsElement, + eq(2), + coverage = atLeast(percents = 94) + // because of the assumption + ) + } } \ No newline at end of file diff --git a/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayOfArrays.java b/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayOfArrays.java index 4615410654..c4ec038c30 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayOfArrays.java +++ b/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayOfArrays.java @@ -152,4 +152,14 @@ public int[][] fillMultiArrayWithArray(int[] value) { return array; } + + public Object[] arrayWithItselfAnAsElement(Object[] array) { + UtMock.assume(array != null && array.length > 0); + + if (array[0] == array) { + return array; + } + + return null; + } } From 0d6142f96eb96ab3c3228938ee4d4c74b60f6855 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Tue, 31 May 2022 12:29:16 +0300 Subject: [PATCH 006/120] Fuzzer improvements (#50) * Randomize fuzzer inputs * Limit fuzzing attempts * Mutate primitive values according to comparison operations * Mutate string constants * Move fuzz-relative code out of UtBotSymbolicEngine.kt * Test added --- .../kotlin/org/utbot/framework/UtSettings.kt | 7 +- .../org/utbot/engine/UtBotSymbolicEngine.kt | 247 ++++------------- .../org/utbot/fuzzer/FallbackModelProvider.kt | 106 +++++++ .../org/utbot/fuzzer/FuzzerFunctions.kt | 237 ++++++++++++++++ .../org/utbot/fuzzer/CartesianProduct.kt | 25 +- .../utbot/fuzzer/ConstantsModelProvider.kt | 25 -- .../utbot/fuzzer/FuzzedMethodDescription.kt | 22 +- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 10 +- .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 41 ++- .../AbstractModelProvider.kt} | 16 +- .../providers/ConstantsModelProvider.kt | 47 ++++ .../{ => providers}/NullModelProvider.kt | 4 +- .../{ => providers}/ObjectModelProvider.kt | 17 +- .../PrimitivesModelProvider.kt | 34 +-- .../providers/StringConstantModelProvider.kt | 48 ++++ .../framework/plugin/api/ModelProviderTest.kt | 259 ++++++++++++++++++ 16 files changed, 863 insertions(+), 282 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt delete mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{DefaultModelProvider.kt => providers/AbstractModelProvider.kt} (55%) create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{ => providers}/NullModelProvider.kt (85%) rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{ => providers}/ObjectModelProvider.kt (87%) rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{ => providers}/PrimitivesModelProvider.kt (88%) create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt create mode 100644 utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 38ed305156..39da879aa6 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -2,10 +2,8 @@ package org.utbot.framework import org.utbot.common.PathUtil.toPath import java.io.FileInputStream -import java.io.FileNotFoundException import java.io.IOException import java.util.Properties -import kotlin.io.path.exists import kotlin.properties.PropertyDelegateProvider import kotlin.reflect.KProperty import mu.KotlinLogging @@ -239,6 +237,11 @@ object UtSettings { */ var useFuzzing: Boolean by getBooleanProperty(false) + /** + * Set the total attempts to improve coverage by fuzzer. + */ + var fuzzingMaxAttemps: Int by getIntProperty(Int.MAX_VALUE) + /** * Generate tests that treat possible overflows in arithmetic operations as errors * that throw Arithmetic Exception. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index bfb5f5de90..18bc138d74 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -1,5 +1,22 @@ package org.utbot.engine +import kotlinx.collections.immutable.persistentHashMapOf +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentMap +import kotlinx.collections.immutable.toPersistentSet +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.isActive +import kotlinx.coroutines.yield +import mu.KotlinLogging import org.utbot.analytics.Predictors import org.utbot.common.WorkaroundReason.HACK import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES @@ -13,6 +30,7 @@ import org.utbot.engine.MockStrategy.NO_MOCKS import org.utbot.engine.overrides.UtArrayMock import org.utbot.engine.overrides.UtLogicMock import org.utbot.engine.overrides.UtOverrideMock +import org.utbot.engine.pc.NotBoolExpression import org.utbot.engine.pc.UtAddNoOverflowExpression import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtAndBoolExpression @@ -38,7 +56,6 @@ import org.utbot.engine.pc.UtIteExpression import org.utbot.engine.pc.UtLongSort import org.utbot.engine.pc.UtMkTermArrayExpression import org.utbot.engine.pc.UtNegExpression -import org.utbot.engine.pc.NotBoolExpression import org.utbot.engine.pc.UtOrBoolExpression import org.utbot.engine.pc.UtPrimitiveSort import org.utbot.engine.pc.UtShortSort @@ -92,94 +109,47 @@ import org.utbot.framework.UtSettings.useDebugVisualization import org.utbot.framework.concrete.UtConcreteExecutionData import org.utbot.framework.concrete.UtConcreteExecutionResult import org.utbot.framework.concrete.UtExecutionInstrumentation -import org.utbot.framework.concrete.UtModelConstructor import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteExecutionFailureException import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.Instruction import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.MissingState import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtConcreteExecutionFailure import org.utbot.framework.plugin.api.UtError -import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtInstrumentation import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtOverflowFailure import org.utbot.framework.plugin.api.UtResult -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.graph import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.onSuccess -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.floatClassId import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isIterable -import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.kClass -import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.signature import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.util.description import org.utbot.framework.util.executableId -import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.FallbackModelProvider +import org.utbot.fuzzer.collectConstantsForFuzzer import org.utbot.fuzzer.defaultModelProviders import org.utbot.fuzzer.fuzz import org.utbot.instrumentation.ConcreteExecutor -import java.lang.reflect.Method -import java.lang.reflect.ParameterizedType -import java.util.BitSet -import java.util.IdentityHashMap -import java.util.TreeSet -import kotlin.collections.plus -import kotlin.collections.plusAssign -import kotlin.math.max -import kotlin.math.min -import kotlin.reflect.KClass -import kotlin.reflect.full.instanceParameter -import kotlin.reflect.full.valueParameters -import kotlin.reflect.jvm.javaType -import kotlinx.collections.immutable.persistentHashMapOf -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.collections.immutable.toPersistentMap -import kotlinx.collections.immutable.toPersistentSet -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.isActive -import kotlinx.coroutines.yield -import mu.KotlinLogging import soot.ArrayType -import soot.Body import soot.BooleanType import soot.ByteType import soot.CharType import soot.DoubleType import soot.FloatType import soot.IntType -import soot.Local import soot.LongType import soot.PrimType import soot.RefLikeType @@ -262,6 +232,15 @@ import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl import sun.reflect.generics.reflectiveObjects.TypeVariableImpl import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl +import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType +import kotlin.collections.plus +import kotlin.collections.plusAssign +import kotlin.math.max +import kotlin.math.min +import kotlin.reflect.full.instanceParameter +import kotlin.reflect.full.valueParameters +import kotlin.reflect.jvm.javaType private val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") @@ -534,7 +513,7 @@ class UtBotSymbolicEngine( //Simple fuzzing - fun fuzzing() = flow { + fun fuzzing(modelProvider: ModelProvider = defaultModelProviders { nextDefaultModelId++ }) = flow { if (!UtSettings.useFuzzing) return@flow @@ -552,6 +531,8 @@ class UtBotSymbolicEngine( return@flow } + val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } + val thisInstance = when { methodUnderTest.isStatic -> null methodUnderTest.isConstructor -> if ( @@ -563,7 +544,7 @@ class UtBotSymbolicEngine( null } else -> { - createSimpleModelByKClass(methodUnderTest.clazz).apply { + fallbackModelProvider.toModel(methodUnderTest.clazz).apply { if (this is UtNullModel) { // it will definitely fail because of NPE, return@flow } @@ -571,9 +552,11 @@ class UtBotSymbolicEngine( } } - val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph.body)) - val modelProvider = defaultModelProviders { nextDefaultModelId++ }.withFallback { createModelByClassId(it) } - fuzz(methodUnderTestDescription, modelProvider).forEachIndexed { index, parameters -> + val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)) + val modelProviderWithFallback = modelProvider.withFallback(fallbackModelProvider::toModel) + val coveredInstructionTracker = mutableSetOf() + var attempts = UtSettings.fuzzingMaxAttemps + fuzz(methodUnderTestDescription, modelProviderWithFallback).forEachIndexed { index, parameters -> val initialEnvironmentModels = EnvironmentModels(thisInstance, parameters, mapOf()) try { @@ -589,6 +572,12 @@ class UtBotSymbolicEngine( } } + if (!coveredInstructionTracker.addAll(concreteExecutionResult.coverage.coveredInstructions)) { + if (--attempts < 0) { + return@flow + } + } + emit( UtExecution( stateBefore = initialEnvironmentModels, @@ -611,86 +600,6 @@ class UtBotSymbolicEngine( } } - /** - * Finds constant values in method body. - * - * todo move to utbot-fuzzer module [module should have access to some API to traverse through control-flow graph] - */ - private fun collectConstantsForFuzzer(body: Body): MutableList { - val constants = mutableListOf() - - body.units.forEach { unit -> - unit.useBoxes.asSequence().map { it.value }.filter { it is Constant || it is JCastExpr }.forEach { value -> - try { - var constant: FuzzedConcreteValue? = null - if (unit is JIfStmt && value is Constant) { - /* - * It is acceptable to check different types in if statement like ```2 == 2L```. - * - * Because of that fuzzer tries to find out the correct type between local and constant. - * Constant should be converted into type of local var in such way that if-statement can be true. - */ - val exactValue = value.javaClass.getField("value")[value] - val local = unit.conditionBox.value.useBoxes.map { it.value } - .filterIsInstance() - .takeIf { it.size == 1 } - ?.first() - if (local?.type != value.type && value.type is IntType) { - // Soot loads any integer type as an Int, - // therefore we try to guess target type using second value - // in the if statement - constant = when (local?.type) { - is CharType -> FuzzedConcreteValue(charClassId, (exactValue as Int).toChar()) - is BooleanType -> FuzzedConcreteValue(booleanClassId, (exactValue == 1)) - is ByteType -> FuzzedConcreteValue(byteClassId, (exactValue as Int).toByte()) - is ShortType -> FuzzedConcreteValue(shortClassId, (exactValue as Int).toShort()) - else -> null - } - } - } - /* - * The if-statement with 8-byte types like long and double doesn't use simple comparison, - * but cast and `cmp` method instead. This block tries to recognize this situation - * and cast wider type to more narrow one. - */ - if (value is JCastExpr) { - val next = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first() - if (next is JAssignStmt) { - val const = next.useBoxes.map { it.value } - .filterIsInstance() - .takeIf { it.size == 1 } - ?.first() - if (const != null) { - val exactValue = const.javaClass.getField("value")[const] as Number - constant = when (value.op.type) { - is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte()) - is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort()) - is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt()) - is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat()) - else -> null - } - } - } - } - /* - * Load constant as is. - */ - if (constant == null && value is Constant) { - val exactValue = value.javaClass.getField("value")[value] - constant = FuzzedConcreteValue(value.type.classId, exactValue) - } - - if (constant != null) { - constants += constant - } - } catch (e: Exception) { - logger.warn(e) { "Cannot process constant value of type '${value.type}}'" } - } - } - } - return constants - } - private suspend fun FlowCollector.emitFailedConcreteExecutionResult( stateBefore: EnvironmentModels, e: ConcreteExecutionFailureException @@ -708,70 +617,6 @@ class UtBotSymbolicEngine( } - private fun createModelByClassId(classId: ClassId): UtModel { - val modelConstructor = UtModelConstructor(IdentityHashMap()) - val defaultConstructor = classId.jClass.constructors.firstOrNull { - it.parameters.isEmpty() && it.isPublic - } - return when { - classId.isPrimitive -> - classId.defaultValueModel() - classId.isArray -> - UtArrayModel( - id = nextDefaultModelId++, - classId, - length = 0, - classId.elementClassId!!.defaultValueModel(), - mutableMapOf() - ) - classId.isIterable -> { - val defaultInstance = when { - defaultConstructor != null -> defaultConstructor.newInstance() - classId.jClass.isAssignableFrom(java.util.ArrayList::class.java) -> ArrayList() - classId.jClass.isAssignableFrom(java.util.TreeSet::class.java) -> TreeSet() - classId.jClass.isAssignableFrom(java.util.HashMap::class.java) -> HashMap() - classId.jClass.isAssignableFrom(java.util.ArrayDeque::class.java) -> ArrayDeque() - classId.jClass.isAssignableFrom(java.util.BitSet::class.java) -> BitSet() - else -> null - } - if (defaultInstance != null) - modelConstructor.construct(defaultInstance, classId) - else - createSimpleModelByKClass(classId.kClass) - } - else -> - createSimpleModelByKClass(classId.kClass) - } - } - - private fun createSimpleModelByKClass(kclass: KClass<*>): UtModel { - val defaultConstructor = kclass.java.constructors.firstOrNull { - it.parameters.isEmpty() && it.isPublic // check constructor is public - } - return if (kclass.isAbstract) { // sealed class is abstract by itself - UtNullModel(kclass.java.id) - } else if (defaultConstructor != null) { - val chain = mutableListOf() - val model = UtAssembleModel( - id = nextDefaultModelId++, - kclass.id, - kclass.id.toString(), - chain - ) - chain.add( - UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) - ) - model - } else { - UtCompositeModel( - id = nextDefaultModelId++, - kclass.id, - isMock = false - ) - } - } - - private suspend fun FlowCollector.traverseStmt(current: Stmt) { if (doPreparatoryWorkIfRequired(current)) return diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt new file mode 100644 index 0000000000..a7302603fd --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt @@ -0,0 +1,106 @@ +package org.utbot.fuzzer + +import org.utbot.engine.isPublic +import org.utbot.framework.concrete.UtModelConstructor +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.UtCompositeModel +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.UtStatementModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isIterable +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.kClass +import org.utbot.fuzzer.providers.AbstractModelProvider +import java.util.* +import java.util.function.IntSupplier +import kotlin.collections.ArrayDeque +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.reflect.KClass + +/** + * Provides some simple default models of any class. + * + * Used as a fallback implementation until other providers cover every type. + */ +open class FallbackModelProvider( + private val idGenerator: IntSupplier +): AbstractModelProvider() { + + override fun toModel(classId: ClassId): UtModel { + return createModelByClassId(classId) + } + + fun toModel(klazz: KClass<*>): UtModel = createSimpleModelByKClass(klazz) + + private fun createModelByClassId(classId: ClassId): UtModel { + val modelConstructor = UtModelConstructor(IdentityHashMap()) + val defaultConstructor = classId.jClass.constructors.firstOrNull { + it.parameters.isEmpty() && it.isPublic + } + return when { + classId.isPrimitive -> + classId.defaultValueModel() + classId.isArray -> + UtArrayModel( + id = idGenerator.asInt, + classId, + length = 0, + classId.elementClassId!!.defaultValueModel(), + mutableMapOf() + ) + classId.isIterable -> { + val defaultInstance = when { + defaultConstructor != null -> defaultConstructor.newInstance() + classId.jClass.isAssignableFrom(java.util.ArrayList::class.java) -> ArrayList() + classId.jClass.isAssignableFrom(java.util.TreeSet::class.java) -> TreeSet() + classId.jClass.isAssignableFrom(java.util.HashMap::class.java) -> HashMap() + classId.jClass.isAssignableFrom(java.util.ArrayDeque::class.java) -> ArrayDeque() + classId.jClass.isAssignableFrom(java.util.BitSet::class.java) -> BitSet() + else -> null + } + if (defaultInstance != null) + modelConstructor.construct(defaultInstance, classId) + else + createSimpleModelByKClass(classId.kClass) + } + else -> + createSimpleModelByKClass(classId.kClass) + } + } + + private fun createSimpleModelByKClass(kclass: KClass<*>): UtModel { + val defaultConstructor = kclass.java.constructors.firstOrNull { + it.parameters.isEmpty() && it.isPublic // check constructor is public + } + return if (kclass.isAbstract) { // sealed class is abstract by itself + UtNullModel(kclass.java.id) + } else if (defaultConstructor != null) { + val chain = mutableListOf() + val model = UtAssembleModel( + id = idGenerator.asInt, + kclass.id, + kclass.id.toString(), + chain + ) + chain.add( + UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) + ) + model + } else { + UtCompositeModel( + id = idGenerator.asInt, + kclass.id, + isMock = false + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt new file mode 100644 index 0000000000..b4db7b094c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -0,0 +1,237 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId +import mu.KotlinLogging +import soot.BooleanType +import soot.ByteType +import soot.CharType +import soot.DoubleType +import soot.FloatType +import soot.IntType +import soot.LongType +import soot.ShortType +import soot.Unit +import soot.Value +import soot.ValueBox +import soot.jimple.Constant +import soot.jimple.InvokeExpr +import soot.jimple.NullConstant +import soot.jimple.internal.ImmediateBox +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JCastExpr +import soot.jimple.internal.JEqExpr +import soot.jimple.internal.JGeExpr +import soot.jimple.internal.JGtExpr +import soot.jimple.internal.JIfStmt +import soot.jimple.internal.JLeExpr +import soot.jimple.internal.JLtExpr +import soot.jimple.internal.JNeExpr +import soot.jimple.internal.JVirtualInvokeExpr +import soot.toolkits.graph.ExceptionalUnitGraph + +private val logger = KotlinLogging.logger {} + +/** + * Finds constant values in method body. + */ +fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set { + return graph.body.units.reversed().asSequence() + .filter { it is JIfStmt || it is JAssignStmt } + .flatMap { unit -> + unit.useBoxes.map { unit to it.value } + } + .filter { (_, value) -> + value is Constant || value is JCastExpr || value is InvokeExpr + } + .flatMap { (unit, value) -> + sequenceOf( + ConstantsFromIfStatement, + ConstantsFromCast, + BoundValuesForDoubleChecks, + StringConstant, + ).flatMap { finder -> + try { + finder.find(graph, unit, value) + } catch (e: Exception) { + logger.warn(e) { "Cannot process constant value of type '${value.type}}'" } + emptyList() + } + }.let { result -> + if (result.any()) result else { + ConstantsAsIs.find(graph, unit, value).asSequence() + } + } + }.toSet() +} + +private interface ConstantsFinder { + fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List +} + +private object ConstantsFromIfStatement: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is Constant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList() + + var useBoxes: List = emptyList() + var ifStatement: JIfStmt? = null + // simple if statement + if (unit is JIfStmt) { + useBoxes = unit.conditionBox.value.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } + ifStatement = unit + } + // statement with double and long that consists of 2 units: + // 1. compare (result = local compare constant) + // 2. if result + if (unit is JAssignStmt) { + useBoxes = unit.rightOp.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } + ifStatement = nextDirectUnit(graph, unit) as? JIfStmt + } + + /* + * It is acceptable to check different types in if statement like ```2 == 2L```. + * + * Because of that fuzzer tries to find out the correct type between local and constant. + * Constant should be converted into type of local var in such way that if-statement can be true. + */ + val valueIndex = useBoxes.indexOf(value) + if (useBoxes.size == 2 && valueIndex >= 0 && ifStatement != null) { + val exactValue = value.plainValue + val local = useBoxes[(valueIndex + 1) % 2] + var op = sootIfToFuzzedOp(ifStatement) + if (valueIndex == 0) { + op = reverse(op) + } + // Soot loads any integer type as an Int, + // therefore we try to guess target type using second value + // in the if statement + return listOfNotNull( + when (local.type) { + is CharType -> FuzzedConcreteValue(charClassId, (exactValue as Int).toChar(), op) + is BooleanType -> FuzzedConcreteValue(booleanClassId, (exactValue == 1), op) + is ByteType -> FuzzedConcreteValue(byteClassId, (exactValue as Int).toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, (exactValue as Int).toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue, op) + is LongType -> FuzzedConcreteValue(longClassId, exactValue, op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue, op) + is DoubleType -> FuzzedConcreteValue(doubleClassId, exactValue, op) + else -> null + } + ) + } + return emptyList() + } + +} + +private object ConstantsFromCast: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is JCastExpr) return emptyList() + + val next = nextDirectUnit(graph, unit) + if (next is JAssignStmt) { + val const = next.useBoxes.findFirstInstanceOf() + if (const != null) { + val op = (nextDirectUnit(graph, next) as? JIfStmt)?.let(::sootIfToFuzzedOp) ?: FuzzedOp.NONE + val exactValue = const.plainValue as Number + return listOfNotNull( + when (value.op.type) { + is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt(), op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat(), op) + else -> null + } + ) + } + } + return emptyList() + } + +} + +private object BoundValuesForDoubleChecks: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is InvokeExpr) return emptyList() + if (value.method.declaringClass.name != "java.lang.Double") return emptyList() + return when (value.method.name) { + "isNaN", "isInfinite", "isFinite" -> listOf( + FuzzedConcreteValue(doubleClassId, Double.POSITIVE_INFINITY), + FuzzedConcreteValue(doubleClassId, Double.NaN), + FuzzedConcreteValue(doubleClassId, 0.0), + ) + else -> emptyList() + } + } + +} + +private object StringConstant: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (unit !is JAssignStmt || value !is JVirtualInvokeExpr) return emptyList() + // if string constant is called from String class let's pass it as modification + if (value.method.declaringClass.name == "java.lang.String") { + val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue + if (stringConstantWasPassedAsArg != null) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedOp.CH)) + } + val stringConstantWasPassedAsThis = graph.getPredsOf(unit) + ?.filterIsInstance() + ?.firstOrNull() + ?.useBoxes + ?.findFirstInstanceOf() + ?.plainValue + if (stringConstantWasPassedAsThis != null) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedOp.CH)) + } + } + return emptyList() + } + +} + +private object ConstantsAsIs: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is Constant || value is NullConstant) return emptyList() + return listOf(FuzzedConcreteValue(value.type.classId, value.plainValue)) + + } + +} + +private inline fun List.findFirstInstanceOf(): T? { + return map { it.value } + .filterIsInstance() + .firstOrNull() +} + +private val Constant.plainValue + get() = javaClass.getField("value")[this] + +private fun sootIfToFuzzedOp(unit: JIfStmt) = when (unit.condition) { + is JEqExpr -> FuzzedOp.NE + is JNeExpr -> FuzzedOp.EQ + is JGtExpr -> FuzzedOp.LE + is JGeExpr -> FuzzedOp.LT + is JLtExpr -> FuzzedOp.GE + is JLeExpr -> FuzzedOp.GT + else -> FuzzedOp.NONE +} + +private fun reverse(op: FuzzedOp) = when(op) { + FuzzedOp.GT -> FuzzedOp.LT + FuzzedOp.LT -> FuzzedOp.GT + FuzzedOp.LE -> FuzzedOp.GE + FuzzedOp.GE -> FuzzedOp.LE + else -> op +} + +private fun nextDirectUnit(graph: ExceptionalUnitGraph, unit: Unit): Unit? = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first() \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt index 83f1edbb23..734ae8a34b 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt @@ -1,18 +1,29 @@ package org.utbot.fuzzer +import kotlin.random.Random + /** * Creates iterable for all values of cartesian product of `lists`. */ -class CartesianProduct(private val lists: List>): Iterable> { +class CartesianProduct( + private val lists: List>, + private val random: Random? = null +): Iterable> { fun asSequence(): Sequence> = iterator().asSequence() override fun iterator(): Iterator> { - return Combinations(*lists.map { it.size }.toIntArray()) - .asSequence() - .map { combination -> - combination.mapIndexedTo(mutableListOf()) { element, value -> lists[element][value] } - } - .iterator() + val combinations = Combinations(*lists.map { it.size }.toIntArray()) + val sequence = if (random != null) { + // todo create lazy random algo for this because this method can cause OOME even if we take only one value + val permutation = IntArray(combinations.size) { it } + permutation.shuffle(random) + permutation.asSequence().map(combinations::get) + } else { + combinations.asSequence() + } + return sequence.map { combination -> + combination.mapIndexedTo(mutableListOf()) { element, value -> lists[element][value] } + }.iterator() } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt deleted file mode 100644 index 5b0106a748..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.stringClassId -import java.util.function.BiConsumer - -/** - * Traverses through method constants and creates appropriate models for them. - */ -object ConstantsModelProvider : ModelProvider { - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.concreteValues - .asSequence() - .filter { (classId, _) -> classId.isPrimitive || classId == stringClassId } - .forEach { (_, value) -> - val model = UtPrimitiveModel(value) - description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> - consumer.accept(index, model) - } - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt index c7f106b4f6..70535c1e06 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -17,7 +17,7 @@ class FuzzedMethodDescription( val name: String, val returnType: ClassId, val parameters: List, - val concreteValues: List = emptyList() + val concreteValues: Collection = emptyList() ) { /** @@ -31,7 +31,7 @@ class FuzzedMethodDescription( result } - constructor(executableId: ExecutableId, concreteValues: List = emptyList()) : this( + constructor(executableId: ExecutableId, concreteValues: Collection = emptyList()) : this( executableId.name, executableId.returnType, executableId.parameters, @@ -44,5 +44,19 @@ class FuzzedMethodDescription( */ data class FuzzedConcreteValue( val classId: ClassId, - val value: Any -) \ No newline at end of file + val value: Any, + val relativeOp: FuzzedOp = FuzzedOp.NONE, +) +enum class FuzzedOp { + NONE, + EQ, + NE, + GT, + GE, + LT, + LE, + CH, // changed or called + ; + + fun isComparisonOp() = this == EQ || this == NE || this == GT || this == GE || this == LT || this == LE +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 8878725bd4..75a57d160d 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -3,9 +3,14 @@ package org.utbot.fuzzer import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.fuzzer.providers.ConstantsModelProvider +import org.utbot.fuzzer.providers.ObjectModelProvider +import org.utbot.fuzzer.providers.PrimitivesModelProvider +import org.utbot.fuzzer.providers.StringConstantModelProvider import mu.KotlinLogging import java.util.concurrent.atomic.AtomicInteger import java.util.function.ToIntFunction +import kotlin.random.Random private val logger = KotlinLogging.logger {} @@ -23,13 +28,14 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi models.add(classId.defaultValueModel()) } } - return CartesianProduct(values).asSequence() + return CartesianProduct(values, Random(0L)).asSequence() } fun defaultModelProviders(idGenerator: ToIntFunction = SimpleIdGenerator()): ModelProvider { return ObjectModelProvider(idGenerator) - .with(PrimitivesModelProvider) .with(ConstantsModelProvider) + .with(StringConstantModelProvider) + .with(PrimitivesModelProvider) } private class SimpleIdGenerator : ToIntFunction { diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt index d7dec7d870..f5b6a62640 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -19,10 +19,26 @@ fun interface ModelProvider { * * This model provider is called before `anotherModelProvider`. */ - fun with(anotherModelProvider: ModelProvider) : ModelProvider { - return ModelProvider { description, consumer -> - this@ModelProvider.generate(description, consumer) - anotherModelProvider.generate(description, consumer) + fun with(anotherModelProvider: ModelProvider): ModelProvider { + fun toList(m: ModelProvider) = if (m is Combined) m.providers else listOf(m) + return Combined(toList(this) + toList(anotherModelProvider)) + } + + /** + * Removes `anotherModelProvider` from current one. + */ + fun except(anotherModelProvider: ModelProvider): ModelProvider { + return except { it == anotherModelProvider } + } + + /** + * Removes `anotherModelProvider` from current one. + */ + fun except(filter: (ModelProvider) -> Boolean): ModelProvider { + return if (this is Combined) { + Combined(providers.filterNot(filter)) + } else { + Combined(if (filter(this)) emptyList() else listOf(this)) } } @@ -54,4 +70,21 @@ fun interface ModelProvider { } } + companion object { + @JvmStatic + fun of(vararg providers: ModelProvider): ModelProvider { + return Combined(providers.toList()) + } + } + + /** + * Wrapper class that delegates implementation to the [providers]. + */ + private class Combined(val providers: List): ModelProvider { + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + providers.forEach { provider -> + provider.generate(description, consumer) + } + } + } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/DefaultModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt similarity index 55% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/DefaultModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt index 625d1dfacb..7203060696 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/DefaultModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt @@ -1,26 +1,24 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.* +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer -import java.util.function.Function /** * Simple model implementation. - * - * @param classToModel creates a list of [UtModel] for a particular class. */ @Suppress("unused") -class DefaultModelProvider( - private val classToModel: Function> -) : ModelProvider { +abstract class AbstractModelProvider: ModelProvider { override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { description.parametersMap.forEach { (classId, indices) -> - val defaultModels = classToModel.apply(classId) - defaultModels.forEach { defaultModel -> + toModel(classId)?.let { defaultModel -> indices.forEach { index -> consumer.accept(index, defaultModel) } } } } + + abstract fun toModel(classId: ClassId): UtModel? } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt new file mode 100644 index 0000000000..1f4d1af4ae --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt @@ -0,0 +1,47 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedOp +import org.utbot.fuzzer.ModelProvider +import java.util.function.BiConsumer + +/** + * Traverses through method constants and creates appropriate models for them. + */ +object ConstantsModelProvider : ModelProvider { + + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + description.concreteValues + .asSequence() + .filter { (classId, _) -> classId.isPrimitive } + .forEach { (_, value, op) -> + sequenceOf(value, modifyValue(value, op)) + .filterNotNull() + .map(::UtPrimitiveModel) + .forEach { model -> + description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> + consumer.accept(index, model) + } + } + } + } + + private fun modifyValue(value: Any, op: FuzzedOp): Any? { + if (!op.isComparisonOp()) return null + val multiplier = if (op == FuzzedOp.LT || op == FuzzedOp.GE) -1 else 1 + return when(value) { + is Boolean -> value.not() + is Byte -> value + multiplier.toByte() + is Char -> (value.toInt() + multiplier).toChar() + is Short -> value + multiplier.toShort() + is Int -> value + multiplier + is Long -> value + multiplier.toLong() + is Float -> value + multiplier.toDouble() + is Double -> value + multiplier.toDouble() + else -> null + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/NullModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt similarity index 85% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/NullModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt index 18d0135ad6..d2a1691ca1 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/NullModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt @@ -1,8 +1,10 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer /** diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt similarity index 87% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObjectModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 5952091a25..04b361f28f 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -1,4 +1,4 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId @@ -8,6 +8,9 @@ import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.fuzz import java.lang.reflect.Constructor import java.lang.reflect.Modifier import java.lang.reflect.Parameter @@ -21,16 +24,18 @@ class ObjectModelProvider( private val idGenerator: ToIntFunction ) : ModelProvider { + var modelProvider: ModelProvider = ModelProvider.of(ConstantsModelProvider, StringConstantModelProvider, PrimitivesModelProvider) + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { val assembleModels = with(description) { parameters.asSequence() .flatMap { classId -> collectConstructors(classId) { javaConstructor -> - isPublic(javaConstructor) && javaConstructor.parameters.all(::isPrimitiveOrString) + isPublic(javaConstructor) && javaConstructor.parameters.all(Companion::isPrimitiveOrString) } } .associateWith { - fuzzParameters(it) + fuzzParameters(it, modelProvider) } .flatMap { (constructorId, fuzzedParameters) -> fuzzedParameters.map { params -> @@ -64,15 +69,11 @@ class ObjectModelProvider( return parameterType.isPrimitive || String::class.java == parameterType } - private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId): Sequence> { + private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence> { val fuzzedMethod = FuzzedMethodDescription( executableId = constructorId, concreteValues = this.concreteValues ) - val modelProviders = arrayOf( - PrimitivesModelProvider, - ConstantsModelProvider - ) return fuzz(fuzzedMethod, *modelProviders) } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PrimitivesModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt similarity index 88% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PrimitivesModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt index 5098b7ff83..e5c57ea27a 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PrimitivesModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt @@ -1,8 +1,10 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.* +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer /** @@ -13,25 +15,24 @@ object PrimitivesModelProvider : ModelProvider { description.parametersMap.forEach { (classId, parameterIndices) -> val primitives = when (classId) { byteClassId -> listOf( + UtPrimitiveModel(0.toByte()), UtPrimitiveModel(1.toByte()), UtPrimitiveModel((-1).toByte()), - UtPrimitiveModel(Byte.MAX_VALUE), UtPrimitiveModel(Byte.MIN_VALUE), - UtPrimitiveModel(0.toByte()), + UtPrimitiveModel(Byte.MAX_VALUE), ) booleanClassId -> listOf(UtPrimitiveModel(false), UtPrimitiveModel(true)) charClassId -> listOf( UtPrimitiveModel('\\'), - UtPrimitiveModel('\t'), - UtPrimitiveModel('\n'), - UtPrimitiveModel('\u0000'), + UtPrimitiveModel(Char.MIN_VALUE), + UtPrimitiveModel(Char.MAX_VALUE), ) shortClassId -> listOf( + UtPrimitiveModel(0.toShort()), UtPrimitiveModel(1.toShort()), UtPrimitiveModel((-1).toShort()), UtPrimitiveModel(Short.MIN_VALUE), UtPrimitiveModel(Short.MAX_VALUE), - UtPrimitiveModel(0.toShort()), ) intClassId -> listOf( UtPrimitiveModel(1), @@ -41,39 +42,34 @@ object PrimitivesModelProvider : ModelProvider { UtPrimitiveModel(0), ) longClassId -> listOf( + UtPrimitiveModel(0L), UtPrimitiveModel(1L), UtPrimitiveModel(-1L), UtPrimitiveModel(Long.MIN_VALUE), UtPrimitiveModel(Long.MAX_VALUE), - UtPrimitiveModel(0L), ) floatClassId -> listOf( + UtPrimitiveModel(0.0f), UtPrimitiveModel(1.1f), UtPrimitiveModel(-1.1f), - UtPrimitiveModel(Float.POSITIVE_INFINITY), - UtPrimitiveModel(Float.NEGATIVE_INFINITY), UtPrimitiveModel(Float.MIN_VALUE), UtPrimitiveModel(Float.MAX_VALUE), + UtPrimitiveModel(Float.NEGATIVE_INFINITY), + UtPrimitiveModel(Float.POSITIVE_INFINITY), UtPrimitiveModel(Float.NaN), - UtPrimitiveModel(0.0f), ) doubleClassId -> listOf( + UtPrimitiveModel(0.0), UtPrimitiveModel(1.1), UtPrimitiveModel(-1.1), - UtPrimitiveModel(Double.POSITIVE_INFINITY), - UtPrimitiveModel(Double.NEGATIVE_INFINITY), UtPrimitiveModel(Double.MIN_VALUE), UtPrimitiveModel(Double.MAX_VALUE), + UtPrimitiveModel(Double.NEGATIVE_INFINITY), + UtPrimitiveModel(Double.POSITIVE_INFINITY), UtPrimitiveModel(Double.NaN), - UtPrimitiveModel(0.0), ) stringClassId -> listOf( UtPrimitiveModel(""), - UtPrimitiveModel(" "), - UtPrimitiveModel("nonemptystring"), - UtPrimitiveModel("multiline\n\rstring"), - UtPrimitiveModel("1"), - UtPrimitiveModel("False"), ) else -> listOf() } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt new file mode 100644 index 0000000000..74cfad6494 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt @@ -0,0 +1,48 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedOp +import org.utbot.fuzzer.ModelProvider +import java.util.function.BiConsumer +import kotlin.random.Random + +object StringConstantModelProvider : ModelProvider { + + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + val random = Random(72923L) + description.concreteValues + .asSequence() + .filter { (classId, _) -> classId == stringClassId } + .forEach { (_, value, op) -> + listOf(value, mutate(random, value as? String, op)) + .asSequence() + .filterNotNull() + .map { UtPrimitiveModel(it) }.forEach { model -> + description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> + consumer.accept(index, model) + } + } + } + } + + private fun mutate(random: Random, value: String?, op: FuzzedOp): String? { + if (value == null || value.isEmpty() || op != FuzzedOp.CH) return null + val indexOfMutation = random.nextInt(value.length) + return value.replaceRange(indexOfMutation, indexOfMutation + 1, SingleCharacterSequence(value[indexOfMutation] - random.nextInt(1, 128))) + } + + private class SingleCharacterSequence(private val character: Char) : CharSequence { + override val length: Int + get() = 1 + + override fun get(index: Int): Char = character + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + throw UnsupportedOperationException() + } + + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt new file mode 100644 index 0000000000..f129643eb8 --- /dev/null +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -0,0 +1,259 @@ +package org.utbot.framework.plugin.api + +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.fuzzer.FuzzedConcreteValue +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedOp +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.providers.ConstantsModelProvider +import org.utbot.fuzzer.providers.ObjectModelProvider +import org.utbot.fuzzer.providers.PrimitivesModelProvider +import org.utbot.fuzzer.providers.StringConstantModelProvider +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.Date + +class ModelProviderTest { + + @Test + fun `test generate primitive models for boolean`() { + val models = collect(PrimitivesModelProvider, + parameters = listOf(booleanClassId) + ) + + assertEquals(1, models.size) + assertEquals(2, models[0]!!.size) + assertTrue(models[0]!!.contains(UtPrimitiveModel(true))) + assertTrue(models[0]!!.contains(UtPrimitiveModel(false))) + } + + @Test + fun `test all known primitive types are generate at least one value`() { + val primitiveTypes = listOf( + byteClassId, + booleanClassId, + charClassId, + shortClassId, + intClassId, + longClassId, + floatClassId, + doubleClassId, + stringClassId, + ) + val models = collect(PrimitivesModelProvider, + parameters = primitiveTypes + ) + + assertEquals(primitiveTypes.size, models.size) + primitiveTypes.indices.forEach { + assertTrue(models[it]!!.isNotEmpty()) + } + } + + @Test + fun `test that empty constants don't generate any models`() { + val models = collect(ConstantsModelProvider, + parameters = listOf(intClassId), + constants = emptyList() + ) + + assertEquals(0, models.size) + } + + @Test + fun `test that one constant generate corresponding value`() { + val models = collect(ConstantsModelProvider, + parameters = listOf(intClassId), + constants = listOf( + FuzzedConcreteValue(intClassId, 123) + ) + ) + + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) + assertEquals(UtPrimitiveModel(123), models[0]!![0]) + assertEquals(intClassId, models[0]!![0].classId) + } + + @Test + fun `test that constants are mutated if comparison operation is set`() { + val models = collect(ConstantsModelProvider, + parameters = listOf(intClassId), + constants = listOf( + FuzzedConcreteValue(intClassId, 10, FuzzedOp.EQ), + FuzzedConcreteValue(intClassId, 20, FuzzedOp.NE), + FuzzedConcreteValue(intClassId, 30, FuzzedOp.LT), + FuzzedConcreteValue(intClassId, 40, FuzzedOp.LE), + FuzzedConcreteValue(intClassId, 50, FuzzedOp.GT), + FuzzedConcreteValue(intClassId, 60, FuzzedOp.GE), + ) + ) + + assertEquals(1, models.size) + val expectedValues = listOf(10, 11, 20, 21, 29, 30, 40, 41, 50, 51, 59, 60) + assertEquals(expectedValues.size, models[0]!!.size) + expectedValues.forEach { + assertTrue(models[0]!!.contains(UtPrimitiveModel(it))) + } + } + + @Test + fun `test constant empty string generates only corresponding model`() { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "", FuzzedOp.CH), + ) + ) + + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) + assertEquals(UtPrimitiveModel(""), models[0]!![0]) + } + + @Test + fun `test non-empty string is not mutated if operation is not set`() { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "nonemptystring", FuzzedOp.NONE), + ) + ) + + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) + assertEquals(UtPrimitiveModel("nonemptystring"), models[0]!![0]) + } + + @Test + fun `test non-empty string is mutated if modification operation is set`() { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "nonemptystring", FuzzedOp.CH), + ) + ) + + assertEquals(1, models.size) + assertEquals(2, models[0]!!.size) + listOf("nonemptystring", "nonemptystr`ng").forEach { + assertTrue( models[0]!!.contains(UtPrimitiveModel(it))) { "Failed to find string $it in list ${models[0]}" } + } + } + + @Test + fun `test mutation creates the same values between different runs`() { + repeat(10) { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "anotherstring", FuzzedOp.CH), + ) + ) + listOf("anotherstring", "anotherskring").forEach { + assertTrue( models[0]!!.contains(UtPrimitiveModel(it))) { "Failed to find string $it in list ${models[0]}" } + } + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER", "RemoveEmptySecondaryConstructorBody") + fun `test default object model creation for simple constructors`() { + withUtContext(UtContext(this::class.java.classLoader)) { + class A { + constructor(a: Int) {} + constructor(a: Int, b: String) {} + constructor(a: Int, b: String, c: Boolean) + } + + val classId = A::class.java.id + val models = collect( + ObjectModelProvider { 0 }.apply { + modelProvider = ModelProvider.of(ConstantsModelProvider, StringConstantModelProvider) + }, + parameters = listOf(classId) + ) + + assertEquals(1, models.size) + assertEquals(3, models[0]!!.size) + assertTrue(models[0]!!.all { it is UtAssembleModel && it.classId == classId }) + + models[0]!!.filterIsInstance().forEachIndexed { index, model -> + assertEquals(1, model.instantiationChain.size) + val stm = model.instantiationChain[0] + assertTrue(stm is UtExecutableCallModel) + stm as UtExecutableCallModel + val paramCountInConstructorAsTheyListed = index + 1 + assertEquals(paramCountInConstructorAsTheyListed, stm.params.size) + } + } + } + + @Test + fun `test no object model is created for empty constructor`() { + withUtContext(UtContext(this::class.java.classLoader)) { + class A + + val classId = A::class.java.id + val models = collect( + ObjectModelProvider { 0 }, + parameters = listOf(classId) + ) + + assertEquals(0, models.size) + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER") + fun `test that constructors with not primitive parameters are ignored`() { + withUtContext(UtContext(this::class.java.classLoader)) { + class A { + constructor(a: Int, b: Int) + constructor(a: Int, b: Date) + } + + val classId = A::class.java.id + val models = collect( + ObjectModelProvider { 0 }, + parameters = listOf(classId) + ) + + assertEquals(1, models.size) + assertTrue(models[0]!!.isNotEmpty()) + val chain = (models[0]!![0] as UtAssembleModel).instantiationChain + assertEquals(1, chain.size) + assertTrue(chain[0] is UtExecutableCallModel) + (chain[0] as UtExecutableCallModel).params.forEach { + assertEquals(intClassId, it.classId) + } + } + } + + private fun collect( + modelProvider: ModelProvider, + name: String = "testMethod", + returnType: ClassId = voidClassId, + parameters: List, + constants: List = emptyList() + ): Map> { + return mutableMapOf>().apply { + modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants)) { i, m -> + computeIfAbsent(i) { mutableListOf() }.add(m) + } + } + } + +} \ No newline at end of file From 96aef0c71b069d79969bad839f31bf929090c110 Mon Sep 17 00:00:00 2001 From: Victoria <32179813+victoriafomina@users.noreply.github.com> Date: Tue, 31 May 2022 13:12:13 +0300 Subject: [PATCH 007/120] Create issue-to-project.yml closes #45 (#71) --- .github/workflows/issue-to-project.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/issue-to-project.yml diff --git a/.github/workflows/issue-to-project.yml b/.github/workflows/issue-to-project.yml new file mode 100644 index 0000000000..b16ce37cca --- /dev/null +++ b/.github/workflows/issue-to-project.yml @@ -0,0 +1,17 @@ +name: Add issues to UTBot Java project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/UnitTestBot/projects/2 + github-token: ${{ secrets.COPY_ISSUE_TO_PROJECT }} + From eafd65a3d27e05724a30b12b8e809e537e81019c Mon Sep 17 00:00:00 2001 From: Nikita Stroganov <54814796+IdeaSeeker@users.noreply.github.com> Date: Tue, 31 May 2022 14:19:35 +0300 Subject: [PATCH 008/120] Rename utbot-gradle package (#83) --- build.gradle | 10 ++++++++ utbot-gradle/build.gradle | 4 +-- utbot-gradle/docs/utbot-gradle.md | 24 +++++------------- .../plugin}/CreateSarifReportTask.kt | 21 ++++++++-------- .../plugin}/SarifGradlePlugin.kt | 10 ++++---- .../plugin}/extension/SarifGradleExtension.kt | 2 +- .../extension/SarifGradleExtensionProvider.kt | 13 +++------- .../plugin}/wrappers/GradleProjectWrapper.kt | 10 ++++---- .../wrappers/SourceFindingStrategyGradle.kt | 2 +- .../plugin}/wrappers/SourceSetWrapper.kt | 4 +-- .../plugin}/wrappers/TargetClassWrapper.kt | 2 +- .../plugin}/SarifGradlePluginTest.kt | 2 +- .../plugin}/SarifGradleTestUtil.kt | 4 +-- .../SarifGradleExtensionProviderTest.kt | 25 +++++++------------ .../wrappers/GradleProjectWrapperTest.kt | 12 ++++----- .../SourceFindingStrategyGradleTest.kt | 6 ++--- .../plugin}/wrappers/SourceSetWrapperTest.kt | 21 ++++++++-------- 17 files changed, 77 insertions(+), 95 deletions(-) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/CreateSarifReportTask.kt (94%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/SarifGradlePlugin.kt (97%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/extension/SarifGradleExtension.kt (98%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/extension/SarifGradleExtensionProvider.kt (93%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/GradleProjectWrapper.kt (94%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/SourceFindingStrategyGradle.kt (97%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/SourceSetWrapper.kt (99%) rename utbot-gradle/src/main/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/TargetClassWrapper.kt (94%) rename utbot-gradle/src/test/kotlin/org/utbot/{sarif => gradle/plugin}/SarifGradlePluginTest.kt (96%) rename utbot-gradle/src/test/kotlin/org/utbot/{sarif => gradle/plugin}/SarifGradleTestUtil.kt (88%) rename utbot-gradle/src/test/kotlin/org/utbot/{sarif => gradle/plugin}/extension/SarifGradleExtensionProviderTest.kt (96%) rename utbot-gradle/src/test/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/GradleProjectWrapperTest.kt (94%) rename utbot-gradle/src/test/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/SourceFindingStrategyGradleTest.kt (98%) rename utbot-gradle/src/test/kotlin/org/utbot/{sarif => gradle/plugin}/wrappers/SourceSetWrapperTest.kt (93%) diff --git a/build.gradle b/build.gradle index f18cfd3051..c4da1c528d 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,16 @@ subprojects { apply plugin: 'java' apply plugin: 'maven-publish' + publishing { + publications { + jar(MavenPublication) { + from components.java + groupId 'org.utbot' + artifactId project.name + } + } + } + repositories { mavenCentral() maven { url 'https://jitpack.io' } diff --git a/utbot-gradle/build.gradle b/utbot-gradle/build.gradle index 2b9cb3b008..a23c2f34cd 100644 --- a/utbot-gradle/build.gradle +++ b/utbot-gradle/build.gradle @@ -18,8 +18,8 @@ configurations.all { gradlePlugin { plugins { sarifReportPlugin { - id = 'org.utbot.sarif' - implementationClass = 'org.utbot.sarif.SarifGradlePlugin' + id = 'org.utbot.gradle.plugin' + implementationClass = 'org.utbot.gradle.plugin.SarifGradlePlugin' } } } diff --git a/utbot-gradle/docs/utbot-gradle.md b/utbot-gradle/docs/utbot-gradle.md index 31116201e8..33c8d9b89e 100644 --- a/utbot-gradle/docs/utbot-gradle.md +++ b/utbot-gradle/docs/utbot-gradle.md @@ -43,14 +43,14 @@ In addition, it creates one big SARIF-report containing the results from all the
Groovy
-  apply plugin: 'org.utbot.sarif'
+  apply plugin: 'org.utbot.gradle.plugin'
   
Kotlin DSL
-  apply(plugin = "org.utbot.sarif")
+  apply(plugin = "org.utbot.gradle.plugin")
   
@@ -195,11 +195,8 @@ There are two ways to do it. buildscript { repositories { mavenLocal() - maven { - url "http://[your-ip]:[your-port]/repository/utbot-uber/" - allowInsecureProtocol true - } mavenCentral() + maven { url 'https://jitpack.io' } }   dependencies { @@ -215,11 +212,8 @@ There are two ways to do it. buildscript { repositories { mavenLocal() - maven { - url = uri("http://[your-ip]:[your-port]/repository/utbot-uber/") - isAllowInsecureProtocol = true - } mavenCentral() + maven { url 'https://jitpack.io' } }   dependencies { @@ -239,11 +233,8 @@ There are two ways to do it. buildscript { repositories { mavenLocal() - maven { - url "http://[your-ip]:[your-port]/repository/utbot-uber/" - allowInsecureProtocol true - } mavenCentral() + maven { url 'https://jitpack.io' } }   dependencies { @@ -262,11 +253,8 @@ There are two ways to do it. buildscript { repositories { mavenLocal() - maven { - url = uri("http://[your-ip]:[your-port]/repository/utbot-uber/") - isAllowInsecureProtocol = true - } mavenCentral() + maven { url 'https://jitpack.io' } }   dependencies { diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/CreateSarifReportTask.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/CreateSarifReportTask.kt similarity index 94% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/CreateSarifReportTask.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/CreateSarifReportTask.kt index 003e8dc39c..d3743297c7 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/CreateSarifReportTask.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/CreateSarifReportTask.kt @@ -1,5 +1,8 @@ -package org.utbot.sarif +package org.utbot.gradle.plugin +import mu.KLogger +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction import org.utbot.common.bracket import org.utbot.common.debug import org.utbot.framework.codegen.ForceStaticMocking @@ -9,18 +12,16 @@ import org.utbot.framework.plugin.api.UtBotTestCaseGenerator import org.utbot.framework.plugin.api.UtTestCase import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.sarif.extension.SarifGradleExtensionProvider -import org.utbot.sarif.wrappers.GradleProjectWrapper -import org.utbot.sarif.wrappers.SourceFindingStrategyGradle -import org.utbot.sarif.wrappers.SourceSetWrapper -import org.utbot.sarif.wrappers.TargetClassWrapper +import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider +import org.utbot.gradle.plugin.wrappers.GradleProjectWrapper +import org.utbot.gradle.plugin.wrappers.SourceFindingStrategyGradle +import org.utbot.gradle.plugin.wrappers.SourceSetWrapper +import org.utbot.gradle.plugin.wrappers.TargetClassWrapper +import org.utbot.sarif.SarifReport import org.utbot.summary.summarize import java.net.URLClassLoader import java.nio.file.Path import javax.inject.Inject -import mu.KLogger -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.TaskAction /** * The main class containing the entry point [createSarifReport]. @@ -59,7 +60,7 @@ open class CreateSarifReportTask @Inject constructor( // internal // overwriting the getLogger() function from the DefaultTask - private val logger: KLogger = org.utbot.sarif.logger + private val logger: KLogger = org.utbot.gradle.plugin.logger /** * Generates tests and a SARIF report for classes in the [gradleProject] and in all its child projects. diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/SarifGradlePlugin.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/SarifGradlePlugin.kt similarity index 97% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/SarifGradlePlugin.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/SarifGradlePlugin.kt index e190eb772d..1638303d33 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/SarifGradlePlugin.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/SarifGradlePlugin.kt @@ -1,14 +1,14 @@ -package org.utbot.sarif +package org.utbot.gradle.plugin -import org.utbot.sarif.extension.SarifGradleExtension -import org.utbot.sarif.extension.SarifGradleExtensionProvider -import java.io.File +import mu.KotlinLogging import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.TaskProvider -import mu.KotlinLogging +import org.utbot.gradle.plugin.extension.SarifGradleExtension +import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider +import java.io.File internal val logger = KotlinLogging.logger {} diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/extension/SarifGradleExtension.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt similarity index 98% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/extension/SarifGradleExtension.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt index 394ffd3c6e..7a14de1d43 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/extension/SarifGradleExtension.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt @@ -1,4 +1,4 @@ -package org.utbot.sarif.extension +package org.utbot.gradle.plugin.extension import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/extension/SarifGradleExtensionProvider.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt similarity index 93% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/extension/SarifGradleExtensionProvider.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt index a42201f1ec..b5d291d3b8 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/extension/SarifGradleExtensionProvider.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt @@ -1,21 +1,14 @@ -package org.utbot.sarif.extension +package org.utbot.gradle.plugin.extension +import org.gradle.api.Project import org.utbot.common.PathUtil.toPath import org.utbot.engine.Mocker -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.Junit4 -import org.utbot.framework.codegen.Junit5 -import org.utbot.framework.codegen.MockitoStaticMocking -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.TestNg +import org.utbot.framework.codegen.* import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.MockStrategyApi import java.io.File -import org.gradle.api.Project /** * Provides all [SarifGradleExtension] fields in a convenient form: diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/GradleProjectWrapper.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt similarity index 94% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/GradleProjectWrapper.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt index e79aba126f..6f8b099a8a 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/GradleProjectWrapper.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt @@ -1,12 +1,12 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers -import org.utbot.common.FileUtil.createNewFileWithParentDirectories -import org.utbot.sarif.extension.SarifGradleExtensionProvider -import java.io.File -import java.nio.file.Paths import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet +import org.utbot.common.FileUtil.createNewFileWithParentDirectories +import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider +import java.io.File +import java.nio.file.Paths /** * Contains information about the gradle project for which we are creating a SARIF report. diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/SourceFindingStrategyGradle.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt similarity index 97% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/SourceFindingStrategyGradle.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt index 9d09c8b8ad..e45a190a17 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/SourceFindingStrategyGradle.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt @@ -1,4 +1,4 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers import org.utbot.common.PathUtil import org.utbot.common.PathUtil.toPath diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/SourceSetWrapper.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt similarity index 99% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/SourceSetWrapper.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt index 2f78023680..6220bb7cf4 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/SourceSetWrapper.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt @@ -1,5 +1,6 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers +import org.gradle.api.tasks.SourceSet import org.utbot.common.FileUtil.createNewFileWithParentDirectories import org.utbot.common.FileUtil.findAllFilesOnly import org.utbot.common.PathUtil.classFqnToPath @@ -14,7 +15,6 @@ import java.io.File import java.net.URLClassLoader import java.nio.file.Path import java.nio.file.Paths -import org.gradle.api.tasks.SourceSet class SourceSetWrapper( val sourceSet: SourceSet, diff --git a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/TargetClassWrapper.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/TargetClassWrapper.kt similarity index 94% rename from utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/TargetClassWrapper.kt rename to utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/TargetClassWrapper.kt index 87bc84f857..b4c8812ea7 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/sarif/wrappers/TargetClassWrapper.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/TargetClassWrapper.kt @@ -1,4 +1,4 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers import org.utbot.framework.plugin.api.UtMethod import java.io.File diff --git a/utbot-gradle/src/test/kotlin/org/utbot/sarif/SarifGradlePluginTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/SarifGradlePluginTest.kt similarity index 96% rename from utbot-gradle/src/test/kotlin/org/utbot/sarif/SarifGradlePluginTest.kt rename to utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/SarifGradlePluginTest.kt index 062e7f43d8..486d714c07 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/sarif/SarifGradlePluginTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/SarifGradlePluginTest.kt @@ -1,4 +1,4 @@ -package org.utbot.sarif +package org.utbot.gradle.plugin import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test diff --git a/utbot-gradle/src/test/kotlin/org/utbot/sarif/SarifGradleTestUtil.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/SarifGradleTestUtil.kt similarity index 88% rename from utbot-gradle/src/test/kotlin/org/utbot/sarif/SarifGradleTestUtil.kt rename to utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/SarifGradleTestUtil.kt index 9f98304da9..1ac588475f 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/sarif/SarifGradleTestUtil.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/SarifGradleTestUtil.kt @@ -1,4 +1,4 @@ -package org.utbot.sarif +package org.utbot.gradle.plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginConvention @@ -8,7 +8,7 @@ import org.gradle.testfixtures.ProjectBuilder internal fun buildProject(): Project { val project = ProjectBuilder.builder().build() project.pluginManager.apply("java") - project.pluginManager.apply("org.utbot.sarif") + project.pluginManager.apply("org.utbot.gradle.plugin") return project } diff --git a/utbot-gradle/src/test/kotlin/org/utbot/sarif/extension/SarifGradleExtensionProviderTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt similarity index 96% rename from utbot-gradle/src/test/kotlin/org/utbot/sarif/extension/SarifGradleExtensionProviderTest.kt rename to utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt index b3d9fb8f71..9597f982fc 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/sarif/extension/SarifGradleExtensionProviderTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt @@ -1,27 +1,20 @@ -package org.utbot.sarif.extension +package org.utbot.gradle.plugin.extension +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.Mockito import org.utbot.common.PathUtil.toPath import org.utbot.engine.Mocker -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.Junit4 -import org.utbot.framework.codegen.Junit5 -import org.utbot.framework.codegen.MockitoStaticMocking -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.TestNg +import org.utbot.framework.codegen.* import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.sarif.buildProject +import org.utbot.gradle.plugin.buildProject import java.io.File -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.mockito.Mockito class SarifGradleExtensionProviderTest { diff --git a/utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/GradleProjectWrapperTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapperTest.kt similarity index 94% rename from utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/GradleProjectWrapperTest.kt rename to utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapperTest.kt index 4ba75428e8..8eb307b023 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/GradleProjectWrapperTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapperTest.kt @@ -1,17 +1,15 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers -import org.utbot.sarif.buildProject -import org.utbot.sarif.extension.SarifGradleExtensionProvider -import java.io.File import org.gradle.api.Project import org.gradle.api.tasks.SourceSet -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.mockito.Mockito +import org.utbot.gradle.plugin.buildProject +import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider +import java.io.File class GradleProjectWrapperTest { diff --git a/utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/SourceFindingStrategyGradleTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradleTest.kt similarity index 98% rename from utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/SourceFindingStrategyGradleTest.kt rename to utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradleTest.kt index 718ff3730f..2accab875d 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/SourceFindingStrategyGradleTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradleTest.kt @@ -1,12 +1,12 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers -import org.utbot.common.PathUtil.toPath -import java.nio.file.Paths import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.mockito.Mockito +import org.utbot.common.PathUtil.toPath +import java.nio.file.Paths class SourceFindingStrategyGradleTest { diff --git a/utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/SourceSetWrapperTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt similarity index 93% rename from utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/SourceSetWrapperTest.kt rename to utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt index f640c21f22..5a98a7354c 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/sarif/wrappers/SourceSetWrapperTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt @@ -1,20 +1,19 @@ -package org.utbot.sarif.wrappers +package org.utbot.gradle.plugin.wrappers -import org.utbot.common.FileUtil.createNewFileWithParentDirectories -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.util.* -import org.utbot.sarif.buildProject -import org.utbot.sarif.extension.SarifGradleExtensionProvider -import org.utbot.sarif.mainSourceSet -import java.nio.file.Paths import org.gradle.api.Project -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.mockito.Mockito +import org.utbot.common.FileUtil.createNewFileWithParentDirectories +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.util.Snippet +import org.utbot.framework.util.compileClassFile +import org.utbot.gradle.plugin.buildProject +import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider +import org.utbot.gradle.plugin.mainSourceSet +import java.nio.file.Paths class SourceSetWrapperTest { From 8d3ad965251ec82ea686382d578b56a6a03b0361 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 31 May 2022 17:28:48 +0300 Subject: [PATCH 009/120] Generate more human-readable initializers for small arrays (#67) Generate more human-readable initializers for small arrays --- .../constructor/tree/CgVariableConstructor.kt | 85 ++++++++++++------- .../framework/codegen/model/tree/CgElement.kt | 8 +- .../framework/codegen/model/util/DslUtil.kt | 3 +- .../model/visitor/CgAbstractRenderer.kt | 49 ++++++++++- .../codegen/model/visitor/CgJavaRenderer.kt | 24 ++++-- .../codegen/model/visitor/CgKotlinRenderer.kt | 20 ++++- .../codegen/model/visitor/CgVisitor.kt | 2 + 7 files changed, 147 insertions(+), 44 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index 43d4092409..534505dec5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -7,9 +7,11 @@ import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.CgComponents import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor import org.utbot.framework.codegen.model.constructor.util.get +import org.utbot.framework.codegen.model.constructor.util.isDefaultValueOf import org.utbot.framework.codegen.model.constructor.util.isNotDefaultValueOf import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray +import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgDeclaration import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess import org.utbot.framework.codegen.model.tree.CgExpression @@ -222,49 +224,63 @@ internal class CgVariableConstructor(val context: CgContext) : return CgLiteral(classId, paramModel.value) } - private fun constructArray(model: UtArrayModel, baseName: String?): CgVariable { - val elementType = model.classId.elementClassId!! - val array = newVar(model.classId, baseName) { CgAllocateArray(model.classId, elementType, model.length) } - valueByModelId[model.id] = array - if (model.length <= 0) return array - if (model.length == 1) { + private fun constructArray(arrayModel: UtArrayModel, baseName: String?): CgVariable { + val elementType = arrayModel.classId.elementClassId!! + val elementModels = (0 until arrayModel.length).map { + arrayModel.stores.getOrDefault(it, arrayModel.constModel) + } + + val canInitWithValues = elementModels.all { it is UtPrimitiveModel } || elementModels.all { it is UtNullModel } + + val initializer = if (canInitWithValues) { + CgAllocateInitializedArray(arrayModel) + } 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 = model[0] + val elementModel = arrayModel[0] if (elementModel isNotDefaultValueOf elementType) { array.setArrayElement(0, getOrCreateVariable(elementModel)) } } else { - val indexedValuesFromStores = if (model.stores.size == model.length) { - // do not use constModel because stores fully cover array - - // choose all not default values from stores - model.stores.entries.filter { (_, element) -> element isNotDefaultValueOf elementType } - } else { - val initialValue = when { - model.constModel isNotDefaultValueOf elementType -> model.constModel - else -> elementType.defaultValueModel() - } + val indexedValuesFromStores = + if (arrayModel.stores.size == arrayModel.length) { + // do not use constModel because stores fully cover array + arrayModel.stores.entries.filter { (_, element) -> element isNotDefaultValueOf elementType } + } else { + // fill array if constModel is not default type value + if (arrayModel.constModel isNotDefaultValueOf elementType) { + val defaultVariable = getOrCreateVariable(arrayModel.constModel, "defaultValue") + basicForLoop(arrayModel.length) { i -> + array.setArrayElement(i, defaultVariable) + } + } - // fill array via constModel if it is not default type value - if (model.constModel isNotDefaultValueOf elementType) { - // fill array with default values from model - val defaultValue = getOrCreateVariable(model.constModel, "defaultValue") - basicForLoop(model.length) { i -> - array.setArrayElement(i, defaultValue) + // choose all not default values + val defaultValue = if (arrayModel.constModel isDefaultValueOf elementType) { + arrayModel.constModel + } else { + elementType.defaultValueModel() } + arrayModel.stores.entries.filter { (_, element) -> element != defaultValue } } - // choose all not default values - model.stores.entries.filter { (_, element) -> element != initialValue } - } - - val sortedByIndexValuesFromStores = indexedValuesFromStores.sortedBy { it.key } - // set all values from stores manually - sortedByIndexValuesFromStores.forEach { (index, element) -> - array.setArrayElement(index, getOrCreateVariable(element)) - } + indexedValuesFromStores + .sortedBy { it.key } + .forEach { (index, element) -> array.setArrayElement(index, getOrCreateVariable(element)) } } + return array } @@ -299,7 +315,10 @@ internal class CgVariableConstructor(val context: CgContext) : /** * [indexedValuesFromStores] have to be continuous sorted range */ - private fun setStoresRange(array: CgVariable, indexedValuesFromStores: List>) { + private fun setStoresRange( + array: CgVariable, + indexedValuesFromStores: List> + ) { if (indexedValuesFromStores.size < 3) { // range is too small, better set manually indexedValuesFromStores.forEach { (index, element) -> diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt index 121a6ee760..c3630a7192 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt @@ -18,6 +18,7 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId @@ -81,6 +82,7 @@ interface CgElement { is CgLiteral -> visit(element) is CgNonStaticRunnable -> visit(element) is CgStaticRunnable -> visit(element) + is CgAllocateInitializedArray -> visit(element) is CgAllocateArray -> visit(element) is CgEnumConstantAccess -> visit(element) is CgFieldAccess -> visit(element) @@ -644,7 +646,7 @@ class CgStaticRunnable(type: ClassId, val classId: ClassId, methodId: MethodId): // Array allocation -class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) +open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) : CgReferenceExpression { override val type: ClassId by lazy { CgClassId(type.name, updateElementType(elementType), isNullable = type.isNullable) } val elementType: ClassId by lazy { @@ -662,6 +664,10 @@ class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) } } +class CgAllocateInitializedArray(val model: UtArrayModel) + : CgAllocateArray(model.classId, model.classId.elementClassId!!, model.length) + + // Spread operator (for Kotlin, empty for Java) class CgSpread(override val type: ClassId, val array: CgExpression): CgExpression diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt index 8a36e18fa7..5edbf60ec6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt @@ -22,6 +22,7 @@ import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId import org.utbot.framework.plugin.api.util.charClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId @@ -53,7 +54,7 @@ fun intLiteral(num: Int) = CgLiteral(intClassId, num) fun longLiteral(num: Long) = CgLiteral(longClassId, num) -fun byteLiteral(num: Byte) = CgLiteral(booleanClassId, num) +fun byteLiteral(num: Byte) = CgLiteral(byteClassId, num) fun shortLiteral(num: Short) = CgLiteral(shortClassId, num) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index e9c06b873e..9974010925 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -1,5 +1,6 @@ package org.utbot.framework.codegen.model.visitor +import org.apache.commons.text.StringEscapeUtils import org.utbot.common.WorkaroundReason.LONG_CODE_FRAGMENTS import org.utbot.common.workaround import org.utbot.framework.codegen.Import @@ -77,11 +78,23 @@ import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.tree.CgWhileLoop import org.utbot.framework.codegen.model.util.CgPrinter import org.utbot.framework.codegen.model.util.CgPrinterImpl +import org.utbot.framework.codegen.model.util.resolve import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.TypeParameters -import org.apache.commons.text.StringEscapeUtils +import org.utbot.framework.plugin.api.UtArrayModel +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.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId internal abstract class CgAbstractRenderer(val context: CgContext, val printer: CgPrinter = CgPrinterImpl()) : CgVisitor, CgPrinter by printer { @@ -98,6 +111,17 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: protected abstract val langPackage: String + //We may render array elements in initializer in one line or in separate lines: + //items count in one line depends on the value type. + protected fun arrayElementsInLine(constModel: UtModel): Int { + if (constModel is UtNullModel) return 10 + return when (constModel.classId) { + intClassId, byteClassId, longClassId, charClassId -> 8 + booleanClassId, shortClassId, doubleClassId, floatClassId -> 6 + else -> error("Non primitive value of type ${constModel.classId} is unexpected in array initializer") + } + } + private val MethodId.accessibleByName: Boolean get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.currentTestClass @@ -689,6 +713,17 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: protected abstract fun renderExceptionCatchVariable(exception: CgVariable) + protected fun UtArrayModel.getElementExpr(index: Int): CgExpression { + val itemModel = stores.getOrDefault(index, constModel) + val cgValue: CgExpression = when (itemModel) { + is UtPrimitiveModel -> itemModel.value.resolve() + is UtNullModel -> null.resolve() + else -> error("Non primitive or null model $itemModel is unexpected in array initializer") + } + + return cgValue + } + protected fun getEscapedImportRendering(import: Import): String = import.qualifiedName .split(".") @@ -720,6 +755,18 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } } + protected fun UtArrayModel.renderElements(length: Int, elementsInLine: Int) { + for (i in 0 until length) { + val expr = this.getElementExpr(i) + + if (i == 0 && length >= elementsInLine || i != 0 && i % elementsInLine == 0) { + println() + } + expr.accept(this@CgAbstractRenderer) + if (i != length - 1) print(",") + } + } + protected inline fun withIndent(block: () -> Unit) { try { pushIndent() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt index ddcb55db47..f9381fc0a5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt @@ -1,9 +1,11 @@ package org.utbot.framework.codegen.model.visitor +import org.apache.commons.text.StringEscapeUtils import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.tree.CgAllocateArray +import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgBreakStatement @@ -39,9 +41,9 @@ import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.TypeParameters import org.utbot.framework.plugin.api.util.wrapperByPrimitive -import org.apache.commons.text.StringEscapeUtils -internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { +internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : + CgAbstractRenderer(context, printer) { override val statementEnding: String = ";" @@ -154,11 +156,19 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter } override fun visit(element: CgAllocateArray) { - // TODO: definitely rewrite later - // TODO: check array type rendering - val beforeLength = element.type.canonicalName.substringBefore("[") - val afterLength = element.type.canonicalName.substringAfter("]") - print("new $beforeLength[${element.size}]$afterLength") + // TODO: Arsen strongly required to rewrite later + val typeName = element.type.canonicalName.substringBefore("[") + val otherDimensions = element.type.canonicalName.substringAfter("]") + print("new $typeName[${element.size}]$otherDimensions") + } + + override fun visit(element: CgAllocateInitializedArray) { + val arrayModel = element.model + val elementsInLine = arrayElementsInLine(arrayModel.constModel) + + print("{") + arrayModel.renderElements(element.size, elementsInLine) + print("}") } override fun visit(element: CgGetLength) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 4817372fcf..c5fe8c3bd0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -1,5 +1,6 @@ package org.utbot.framework.codegen.model.visitor +import org.apache.commons.text.StringEscapeUtils import org.utbot.common.WorkaroundReason import org.utbot.common.workaround import org.utbot.framework.codegen.RegularImport @@ -7,6 +8,7 @@ import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.isLanguageKeyword import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.tree.CgAllocateArray +import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgArrayElementAccess @@ -43,6 +45,7 @@ import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray @@ -50,7 +53,6 @@ import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.kClass import org.utbot.framework.plugin.api.util.voidClassId -import org.apache.commons.text.StringEscapeUtils //TODO rewrite using KtPsiFactory? internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { @@ -242,6 +244,22 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint } } + override fun visit(element: CgAllocateInitializedArray) { + val arrayModel = element.model + val elementsInLine = arrayElementsInLine(arrayModel.constModel) + + if (arrayModel.constModel is UtPrimitiveModel) { + val prefix = arrayModel.constModel.classId.name.toLowerCase() + print("${prefix}ArrayOf(") + arrayModel.renderElements(element.size, elementsInLine) + print(")") + } else { + print(getKotlinClassString(element.type)) + print("(${element.size})") + if (!element.elementType.isPrimitive) print(" { null }") + } + } + override fun visit(element: CgGetLength) { element.variable.accept(this) print(".size") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt index a88d485a4b..23cf060706 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.model.visitor import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment import org.utbot.framework.codegen.model.tree.CgAllocateArray +import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgArrayElementAccess @@ -198,6 +199,7 @@ interface CgVisitor { // Array allocation fun visit(element: CgAllocateArray): R + fun visit(element: CgAllocateInitializedArray): R // Spread operator fun visit(element: CgSpread): R From 018df4abcd472a43716721b0c64acda09bab7e9b Mon Sep 17 00:00:00 2001 From: Vassiliy Kudryashov Date: Tue, 31 May 2022 17:32:28 +0300 Subject: [PATCH 010/120] Framework tests fail because of KOTLIN_HOME absence closes #65 (#85) --- .../src/main/kotlin/org/utbot/framework/plugin/api/Api.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 72e11211ba..bf9da9751c 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -1155,6 +1155,9 @@ enum class CodegenLanguage( ).plus(sourcesFiles) KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath).plus(sourcesFiles) } + if (this == KOTLIN && System.getenv("KOTLIN_HOME") == null) { + throw RuntimeException("'KOTLIN_HOME' environment variable is not defined. Standard location is {IDEA installation dir}/plugins/Kotlin/kotlinc") + } return listOf(compilerExecutableAbsolutePath) + isolateCommandLineArgumentsToArgumentFile(arguments) } From a21a686811d3bfc970b7033b5f2d80103edb7833 Mon Sep 17 00:00:00 2001 From: Victoria <32179813+victoriafomina@users.noreply.github.com> Date: Tue, 31 May 2022 17:44:37 +0300 Subject: [PATCH 011/120] Add utbot-instrumentation-tests tests closes #86 (#87) --- .../org/utbot/examples/TestConstructors.kt | 141 +++++ .../examples/TestCoverageInstrumentation.kt | 196 +++++++ .../utbot/examples/TestGetSourceFileName.kt | 104 ++++ .../examples/TestInvokeInstrumentation.kt | 156 ++++++ .../TestInvokeWithStaticsInstrumentation.kt | 139 +++++ .../kotlin/org/utbot/examples/TestIsolated.kt | 135 +++++ .../org/utbot/examples/TestStaticMethods.kt | 105 ++++ .../utbot/examples/TestWithInstrumentation.kt | 90 +++ .../org/utbot/examples/benchmark/Benchmark.kt | 85 +++ .../examples/benchmark/BenchmarkFibonacci.kt | 81 +++ .../org/utbot/examples/benchmark/Classes.kt | 32 ++ .../benchmark/TestBenchmarkClasses.kt | 49 ++ .../org/utbot/examples/et/TestMixedExTrace.kt | 54 ++ .../utbot/examples/et/TestSimpleExTrace.kt | 525 ++++++++++++++++++ .../org/utbot/examples/et/TestStaticsUsage.kt | 58 ++ .../utbot/examples/jacoco/TestSameAsJaCoCo.kt | 214 +++++++ .../examples/samples/ClassWithWrongPackage.kt | 4 + .../utbot/examples/samples/ExampleClass.kt | 67 +++ 18 files changed, 2235 insertions(+) create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ClassWithWrongPackage.kt create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt new file mode 100644 index 0000000000..93801a9621 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt @@ -0,0 +1,141 @@ +package org.utbot.examples + +import org.utbot.examples.samples.ClassWithMultipleConstructors +import org.utbot.examples.samples.ClassWithSameMethodNames +import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.utbot.instrumentation.instrumentation.et.ExecutionTraceInstrumentation +import org.utbot.instrumentation.instrumentation.et.convert +import org.utbot.instrumentation.instrumentation.et.function +import org.utbot.instrumentation.instrumentation.et.invoke +import org.utbot.instrumentation.instrumentation.et.pass +import org.utbot.instrumentation.instrumentation.et.ret +import org.utbot.instrumentation.withInstrumentation +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class TestConstructors { + lateinit var utContext: AutoCloseable + + private val CLASSPATH = ClassWithSameMethodNames::class.java.protectionDomain.codeSource.location.path + + @Test + fun testDefaultConstructor() { + ConcreteExecutor( + InvokeInstrumentation(), + CLASSPATH + ).use { executor -> + val constructors = ClassWithMultipleConstructors::class.constructors + val constr = constructors.first { it.parameters.isEmpty() } + val res = executor.execute(constr, arrayOf()) + val checkClass = ClassWithMultipleConstructors() + assertEquals(checkClass, res.getOrNull()) + assertFalse(checkClass === res.getOrNull()) + } + } + + @Test + fun testIntConstructors() { + ConcreteExecutor( + InvokeInstrumentation(), + CLASSPATH + ).use { executor -> + val constructors = ClassWithMultipleConstructors::class.constructors + + val constrI = constructors.first { it.signature == "(I)V" } + val resI = executor.execute(constrI, arrayOf(1)) + assertEquals(ClassWithMultipleConstructors(1), resI.getOrNull()) + + val constrII = constructors.first { it.signature == "(II)V" } + val resII = executor.execute(constrII, arrayOf(1, 2)) + assertEquals(ClassWithMultipleConstructors(3), resII.getOrNull()) + + val constrIII = constructors.first { it.signature == "(III)V" } + val resIII = executor.execute(constrIII, arrayOf(1, 2, 3)) + assertEquals(ClassWithMultipleConstructors(6), resIII.getOrNull()) + } + } + + @Test + fun testStringConstructors() { + withInstrumentation( + InvokeInstrumentation(), + CLASSPATH + ) { executor -> + val constructors = ClassWithMultipleConstructors::class.constructors + + val constrSS = constructors.first { it.parameters.size == 2 && it.signature != "(II)V" } + val resSS = executor.execute(constrSS, arrayOf("100", "23")) + assertEquals(ClassWithMultipleConstructors(123), resSS.getOrNull()) + + val constrS = constructors.first { it.parameters.size == 1 && it.signature != "(I)V" } + val resS1 = executor.execute(constrS, arrayOf("one")) + assertEquals(ClassWithMultipleConstructors(1), resS1.getOrNull()) + + val resS2 = executor.execute(constrS, arrayOf("kek")) + assertEquals(ClassWithMultipleConstructors(-1), resS2.getOrNull()) + } + } + + @Test + fun testCoverageConstructor() { + withInstrumentation( + CoverageInstrumentation, + CLASSPATH + ) { executor -> + val constructors = ClassWithMultipleConstructors::class.constructors + + val constrIII = constructors.first { it.signature == "(III)V" } + executor.execute(constrIII, arrayOf(1, 2, 3)) + + val coverage = executor.collectCoverage(ClassWithMultipleConstructors::class.java) + val method2instr = coverage.methodToInstrRange + assertTrue(method2instr["()V"]!!.minus(coverage.visitedInstrs).isEmpty()) + assertTrue(method2instr["(I)V"]!!.minus(coverage.visitedInstrs).isEmpty()) + assertTrue(method2instr["(II)V"]!!.minus(coverage.visitedInstrs).isEmpty()) + assertTrue(method2instr["(III)V"]!!.minus(coverage.visitedInstrs).toList() == (36..40).toList()) + } + } + + @Test + fun testExecutionTraceConstructor() { + withInstrumentation( + ExecutionTraceInstrumentation(), + CLASSPATH + ) { executor -> + val constructors = ClassWithMultipleConstructors::class.constructors + + val constrIII = constructors.first { it.signature == "(III)V" } + val trace = executor.execute(constrIII, arrayOf(1, 2, 3)) + + assertEquals( + function("(III)V") { + pass() + invoke("(II)V") { + pass() + invoke("(I)V") { + pass() + invoke("()V") { + pass() + ret() + } + pass() + ret() + } + pass() + ret() + } + pass() + ret() + }, + convert(trace) + ) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt new file mode 100644 index 0000000000..b99cacb5b4 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt @@ -0,0 +1,196 @@ +package org.utbot.examples + +import org.utbot.examples.samples.ExampleClass +import org.utbot.examples.statics.substitution.StaticSubstitution +import org.utbot.examples.statics.substitution.StaticSubstitutionExamples +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.utbot.instrumentation.util.ChildProcessError +import org.utbot.instrumentation.util.StaticEnvironment +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TestCoverageInstrumentation { + lateinit var utContext: AutoCloseable + + @Test + fun testCatchTargetException() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val res = it.execute(ExampleClass::kek2, arrayOf(testObject, 123)) + val coverageInfo = it.collectCoverage(ExampleClass::class.java) + + assertEquals(5, coverageInfo.visitedInstrs.size) + assertEquals(50..55, coverageInfo.methodToInstrRange[ExampleClass::kek2.signature]) + assertTrue(res.exceptionOrNull() is ArrayIndexOutOfBoundsException) + } + } + + @Test + fun testIfBranches() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + it.execute(ExampleClass::bar, arrayOf(testObject, 2)) + val coverageInfo1 = it.collectCoverage(ExampleClass::class.java) + + assertEquals(21, coverageInfo1.visitedInstrs.size) + assertEquals(13..49, coverageInfo1.methodToInstrRange[ExampleClass::bar.signature]) + + it.execute(ExampleClass::bar, arrayOf(testObject, 0)) + val coverageInfo2 = it.collectCoverage(ExampleClass::class.java) + + assertEquals(20, coverageInfo2.visitedInstrs.size) + assertEquals(13..49, coverageInfo2.methodToInstrRange[ExampleClass::bar.signature]) + } + } + + @Test + fun testWrongArgumentsException() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + val exc = assertThrows { + it.execute( + ExampleClass::bar, + arrayOf(testObject, 1, 2, 3) + ) + } + + assertInstanceOf( + IllegalArgumentException::class.java, + exc.cause!! + ) + } + } + + + @Test + fun testMultipleRunsInsideCoverage() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + val exc = assertThrows { + it.execute( + ExampleClass::bar, + arrayOf(testObject, 1, 2, 3) + ) + } + + assertInstanceOf( + IllegalArgumentException::class.java, + exc.cause!! + ) + + it.execute(ExampleClass::bar, arrayOf(testObject, 2)) + val coverageInfo1 = it.collectCoverage(ExampleClass::class.java) + + assertEquals(21, coverageInfo1.visitedInstrs.size) + assertEquals(13..49, coverageInfo1.methodToInstrRange[ExampleClass::bar.signature]) + + it.execute(ExampleClass::bar, arrayOf(testObject, 0)) + val coverageInfo2 = it.collectCoverage(ExampleClass::class.java) + + assertEquals(20, coverageInfo2.visitedInstrs.size) + assertEquals(13..49, coverageInfo2.methodToInstrRange[ExampleClass::bar.signature]) + } + } + + + @Test + fun testSameResult() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + it.execute(ExampleClass::dependsOnField, arrayOf(testObject)) + val coverageInfo1 = it.collectCoverage(ExampleClass::class.java) + + assertEquals(19, coverageInfo1.visitedInstrs.size) + assertEquals(99..124, coverageInfo1.methodToInstrRange[ExampleClass::dependsOnField.signature]) + + it.execute(ExampleClass::dependsOnField, arrayOf(testObject)) + val coverageInfo2 = it.collectCoverage(ExampleClass::class.java) + + assertEquals(19, coverageInfo2.visitedInstrs.size) + assertEquals(99..124, coverageInfo2.methodToInstrRange[ExampleClass::dependsOnField.signature]) + } + } + + @Test + fun testResult() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val res = it.execute(ExampleClass::foo, arrayOf(testObject, 3)) + val coverageInfo = it.collectCoverage(ExampleClass::class.java) + + assertEquals(1, res.getOrNull()) + assertEquals(35, coverageInfo.visitedInstrs.size) + assertEquals(56..98, coverageInfo.methodToInstrRange[ExampleClass::foo.signature]) + } + } + + @Test + fun testEmptyMethod() { + ConcreteExecutor( + CoverageInstrumentation, + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val res = it.execute(ExampleClass::emptyMethod, arrayOf(testObject)) + val coverageInfo = it.collectCoverage(ExampleClass::class.java) + + assertEquals(Unit::class, res.getOrNull()!!::class) + assertEquals(1, coverageInfo.visitedInstrs.size) + } + } + + @Test + fun testTernaryOperator() { + ConcreteExecutor( + CoverageInstrumentation, + StaticSubstitutionExamples::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = StaticSubstitutionExamples() + + val emptyStaticEnvironment = StaticEnvironment() + + val res1 = it.execute(StaticSubstitutionExamples::lessThanZero, arrayOf(testObject), parameters = emptyStaticEnvironment) + + val staticEnvironment = StaticEnvironment( + StaticSubstitution::mutableValue.fieldId to -1 + ) + val res2 = it.execute(StaticSubstitutionExamples::lessThanZero, arrayOf(testObject), parameters = staticEnvironment) + val coverageInfo = it.collectCoverage(StaticSubstitutionExamples::class.java) + + assertEquals(res1.getOrNull(), 5) + assertEquals(res2.getOrNull(), 0) + assertEquals(coverageInfo.visitedInstrs, (3..10).toList()) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt new file mode 100644 index 0000000000..bb256f024a --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt @@ -0,0 +1,104 @@ +package org.utbot.examples + +import ClassWithoutPackage +import org.utbot.examples.samples.ClassWithInnerClasses +import org.utbot.examples.samples.ExampleClass +import org.utbot.examples.samples.wrongpackage.ClassWithWrongPackage +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import java.nio.file.Paths +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class TestGetSourceFileName { + private lateinit var cookie: AutoCloseable + + @BeforeEach + fun setup() { + cookie = UtContext.setUtContext(UtContext(ClassLoader.getSystemClassLoader())) + } + + @AfterEach + fun tearDown() { + cookie.close() + } + + @Test + fun testThis() { + assertEquals("TestGetSourceFileName.kt", Instrumenter.computeSourceFileName(TestGetSourceFileName::class.java)) + } + + @Test + fun testKotlinExample() { + assertEquals("ExampleClass.kt", Instrumenter.computeSourceFileName(ExampleClass::class.java)) + } + + @Test + fun testJavaExample() { + assertEquals( + "ClassWithInnerClasses.java", + Instrumenter.computeSourceFileName(ClassWithInnerClasses::class.java) + ) + } + + @Test + fun testInnerClass() { + assertEquals( + "ClassWithInnerClasses.java", + Instrumenter.computeSourceFileName(ClassWithInnerClasses.InnerStaticClass::class.java) + ) + } + + @Test + fun testSameNameButDifferentPackages() { + assertEquals( + true, + Instrumenter.computeSourceFileByClass(org.utbot.examples.samples.root.MyClass::class.java)?.toPath() + ?.endsWith(Paths.get("root", "MyClass.java")) + ) + assertEquals( + true, + Instrumenter.computeSourceFileByClass(org.utbot.examples.samples.root.child.MyClass::class.java) + ?.toPath()?.endsWith(Paths.get("root", "child", "MyClass.java")) + ) + } + + @Test + fun testEmptyPackage() { + assertEquals( + true, + Instrumenter.computeSourceFileByClass(ClassWithoutPackage::class.java)?.toPath() + ?.endsWith("java/ClassWithoutPackage.java") + ) + } + + @Test + fun testPackageDoesNotMatchDir() { + assertEquals( + true, + Instrumenter.computeSourceFileByClass(ClassWithWrongPackage::class.java)?.toPath() + ?.endsWith("org/utbot/examples/samples/ClassWithWrongPackage.kt") + ) + } + + @Test + fun testSearchDir() { + assertEquals( + null, + Instrumenter.computeSourceFileByClass( + org.utbot.examples.samples.root.MyClass::class.java, + Paths.get("src/test/kotlin") + )?.name + ) + + assertEquals( + "MyClass.java", + Instrumenter.computeSourceFileByClass( + org.utbot.examples.samples.root.MyClass::class.java, + Paths.get("src/test") + )?.name + ) + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt new file mode 100644 index 0000000000..9b0b06669e --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt @@ -0,0 +1,156 @@ +package org.utbot.examples + +import org.utbot.examples.samples.ClassWithSameMethodNames +import org.utbot.examples.samples.ExampleClass +import org.utbot.examples.samples.staticenvironment.StaticExampleClass +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.util.ChildProcessError +import kotlin.reflect.full.declaredMembers +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TestInvokeInstrumentation { + lateinit var utContext: AutoCloseable + + @Test + fun testCatchTargetException() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + + val testObject = ExampleClass() + + val res = it.execute(ExampleClass::kek2, arrayOf(testObject, 123)) + + assertTrue(res.exceptionOrNull() is ArrayIndexOutOfBoundsException) + } + } + + @Test + fun testWrongArgumentsException() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + val exc = assertThrows { + it.execute( + ExampleClass::bar, + arrayOf(testObject, 1, 2, 3) + ) + } + assertInstanceOf( + IllegalArgumentException::class.java, + exc.cause!! + ) + } + } + + @Test + fun testSameResult() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val res1 = it.execute(ExampleClass::dependsOnFieldReturn, arrayOf(testObject)) + assertEquals(2, res1.getOrNull()) + + val res2 = it.execute(ExampleClass::dependsOnFieldReturn, arrayOf(testObject)) + assertEquals(2, res2.getOrNull()) + } + } + + @Test + fun testEmptyMethod() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val res = it.execute(ExampleClass::emptyMethod, arrayOf(testObject)) + + assertEquals(Unit::class, res.getOrNull()!!::class) + } + } + + @Test + fun testStaticMethodCall() { + ConcreteExecutor( + InvokeInstrumentation(), + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val res1 = it.execute(StaticExampleClass::inc, arrayOf()) + assertEquals(0, res1.getOrNull()) + + val res2 = it.execute( + StaticExampleClass::plus, + arrayOf(5) + ) + + assertEquals(0, res2.getOrNull()) + } + } + + @Test + fun testNullableMethod() { + ConcreteExecutor( + InvokeInstrumentation(), + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val res1 = it.execute( + StaticExampleClass::canBeNull, + arrayOf(10, + "123") + ) + assertEquals("123", res1.getOrNull()) + + val res2 = it.execute( + StaticExampleClass::canBeNull, + arrayOf(0, + "kek") + ) + + assertEquals(null, res2.getOrNull()) + + val res3 = it.execute( + StaticExampleClass::canBeNull, + arrayOf(1, + null) + ) + + assertEquals(null, res3.getOrNull()) + } + } + + @Test + fun testDifferentSignaturesButSameMethodNames() { + ConcreteExecutor( + InvokeInstrumentation(), + ClassWithSameMethodNames::class.java.protectionDomain.codeSource.location.path + ).use { + val clazz = ClassWithSameMethodNames::class + + val sumVararg = clazz.declaredMembers.first { it.parameters.size == 1 } + val sum2 = clazz.declaredMembers.first { it.parameters.size == 2 } + val sum3 = clazz.declaredMembers.first { it.parameters.size == 3 } + + val resVararg = it.execute(sumVararg, arrayOf(intArrayOf(1, 2, 3, 4, 5))) + assertEquals(Result.success(15), resVararg) + + val resSum2 = it.execute(sum2, arrayOf(1, 5)) + assertEquals(Result.success(8), resSum2) + + val resSum3 = it.execute(sum3, arrayOf(1, 5, 4)) + assertEquals(Result.success(13), resSum3) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt new file mode 100644 index 0000000000..b1865499e0 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt @@ -0,0 +1,139 @@ +package org.utbot.examples + +import org.utbot.examples.samples.staticenvironment.InnerClass +import org.utbot.examples.samples.staticenvironment.MyHiddenClass +import org.utbot.examples.samples.staticenvironment.ReferenceEqualityExampleClass +import org.utbot.examples.samples.staticenvironment.StaticExampleClass +import org.utbot.examples.samples.staticenvironment.TestedClass +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.InvokeWithStaticsInstrumentation +import org.utbot.instrumentation.util.StaticEnvironment +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +class TestInvokeWithStaticsInstrumentation { + lateinit var utContext: AutoCloseable + + @BeforeEach + fun initContext() { + utContext = UtContext.setUtContext(UtContext(ClassLoader.getSystemClassLoader())) + } + + @AfterEach + fun closeConctext() { + utContext.close() + } + + val CLASSPATH = StaticExampleClass::class.java.protectionDomain.codeSource.location.path + + @Test + fun testIfBranches() { + ConcreteExecutor( + InvokeWithStaticsInstrumentation(), + CLASSPATH + ).use { + val res = it.execute(StaticExampleClass::inc, arrayOf(), null) + assertEquals(0, res.getOrNull()) + + val staticEnvironment = StaticEnvironment( + StaticExampleClass::digit.fieldId to 5, + ) + val resWithStatics = it.execute(StaticExampleClass::inc, arrayOf(), parameters = staticEnvironment) + assertEquals(1, resWithStatics.getOrNull()) + } + } + + @Test + fun testHiddenClass1() { + ConcreteExecutor( + InvokeWithStaticsInstrumentation(), + CLASSPATH + ).use { + val res = it.execute(TestedClass::slomayInts, arrayOf(), null) + assertEquals(12, res.getOrNull()) + + val se = StaticEnvironment( + TestedClass::x.fieldId to 0, + MyHiddenClass::var0.fieldId to 0 + ) + val resWithStatics = it.execute(TestedClass::slomayInts, arrayOf(), parameters = se) + assertEquals(2, resWithStatics.getOrNull()) + } + } + + @Disabled("Question: What to do when user hasn't provided all the used static fields?") + @Test + fun testHiddenClassRepeatCall() { + ConcreteExecutor( + InvokeWithStaticsInstrumentation(), + CLASSPATH + ).use { + val se = StaticEnvironment( + TestedClass::x.fieldId to 0, + MyHiddenClass::var0.fieldId to 0 + ) + val resWithStatics = it.execute(TestedClass::slomayInts, arrayOf(), parameters = se) + assertEquals(2, resWithStatics.getOrNull()) + + val resAgain = it.execute(TestedClass::slomayInts, arrayOf(), null) + assertEquals(12, resAgain.getOrNull()) + } + } + + @Test + fun testReferenceEquality() { + ConcreteExecutor( + InvokeWithStaticsInstrumentation(), + CLASSPATH + ).use { + + val thisObject = ReferenceEqualityExampleClass() + + val res12 = it.execute(ReferenceEqualityExampleClass::test12, arrayOf(thisObject), null) + val res23 = it.execute(ReferenceEqualityExampleClass::test23, arrayOf(thisObject), null) + val res31 = it.execute(ReferenceEqualityExampleClass::test31, arrayOf(thisObject), null) + + assertEquals(true, res12.getOrNull()) + assertEquals(false, res23.getOrNull()) + assertEquals(false, res31.getOrNull()) + + val ic1 = InnerClass() + + val se = StaticEnvironment( + ReferenceEqualityExampleClass::field1.fieldId to ic1, + ReferenceEqualityExampleClass::field2.fieldId to ic1, + ReferenceEqualityExampleClass::field3.fieldId to ic1 + ) + + val res12_2 = it.execute(ReferenceEqualityExampleClass::test12, arrayOf(thisObject), parameters = se) + val res23_2 = it.execute(ReferenceEqualityExampleClass::test23, arrayOf(thisObject), parameters = se) + val res31_2 = it.execute(ReferenceEqualityExampleClass::test31, arrayOf(thisObject), parameters = se) + + assertEquals(true, res12_2.getOrNull()) + assertEquals(true, res23_2.getOrNull()) + assertEquals(true, res31_2.getOrNull()) + + val ic2 = InnerClass() + val ic3 = InnerClass() + + val se2 = StaticEnvironment( + ReferenceEqualityExampleClass::field1.fieldId to ic3, + ReferenceEqualityExampleClass::field2.fieldId to ic2, + ReferenceEqualityExampleClass::field3.fieldId to ic3 + ) + val res12_3 = it.execute(ReferenceEqualityExampleClass::test12, arrayOf(thisObject), parameters = se2) + val res23_3 = it.execute(ReferenceEqualityExampleClass::test23, arrayOf(thisObject), parameters = se2) + val res31_3 = it.execute(ReferenceEqualityExampleClass::test31, arrayOf(thisObject), parameters = se2) + + assertEquals(false, res12_3.getOrNull()) + assertEquals(false, res23_3.getOrNull()) + assertEquals(true, res31_3.getOrNull()) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt new file mode 100644 index 0000000000..c86695d4e0 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt @@ -0,0 +1,135 @@ +package org.utbot.examples + +import org.utbot.examples.samples.ExampleClass +import org.utbot.examples.samples.staticenvironment.StaticExampleClass +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.util.ChildProcessError +import org.utbot.instrumentation.util.Isolated +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows + +class TestIsolated { + lateinit var utContext: AutoCloseable + + @Test + fun testCatchTargetException() { + val javaClass = ExampleClass::class.java + ConcreteExecutor( + InvokeInstrumentation(), + javaClass.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val isolatedFunction = Isolated(ExampleClass::kek2, it) + + val res = isolatedFunction(testObject, 123) + + assertTrue(res.exceptionOrNull() is ArrayIndexOutOfBoundsException) + } + } + + @Test + fun testWrongArgumentsException() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + val isolatedFunction = Isolated(ExampleClass::bar, it) + + assertDoesNotThrow { + isolatedFunction(testObject, 1) + } + + + val exc = assertThrows { + isolatedFunction(testObject, 1, 2, 3) + } + + assertInstanceOf( + IllegalArgumentException::class.java, + exc.cause!! + ) + } + } + + @Test + fun testSameResult() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val isolatedFunction = Isolated(ExampleClass::dependsOnFieldReturn, it) + + val res1 = isolatedFunction(testObject) + assertEquals(2, res1.getOrNull()) + + val res2 = isolatedFunction(testObject) + assertEquals(2, res2.getOrNull()) + } + } + + @Test + fun testEmptyMethod() { + ConcreteExecutor( + InvokeInstrumentation(), + ExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val testObject = ExampleClass() + + val isolatedFunction = Isolated(ExampleClass::emptyMethod, it) + + val res = isolatedFunction(testObject) + + assertEquals(Unit::class, res.getOrNull()!!::class) + } + } + + @Test + fun testStaticMethodCall() { + ConcreteExecutor( + InvokeInstrumentation(), + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val isolatedFunctionInc = Isolated(StaticExampleClass::inc, it) + + val res1 = isolatedFunctionInc() + assertEquals(0, res1.getOrNull()) + + val isolatedFunctionPlus = Isolated(StaticExampleClass::plus, it) + + val res2 = isolatedFunctionPlus(5) + + assertEquals(0, res2.getOrNull()) + } + } + + @Test + fun testNullableMethod() { + ConcreteExecutor( + InvokeInstrumentation(), + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val isolatedFunction = Isolated(StaticExampleClass::canBeNull, it) + + val res1 = isolatedFunction(10, "123") + + assertEquals("123", res1.getOrNull()) + + val res2 = isolatedFunction(0, "kek") + + assertEquals(null, res2.getOrNull()) + + val res3 = isolatedFunction(1, null) + + assertEquals(null, res3.getOrNull()) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt new file mode 100644 index 0000000000..6ad3bca179 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt @@ -0,0 +1,105 @@ +package org.utbot.examples + +import org.utbot.examples.samples.staticenvironment.StaticExampleClass +import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + + +class TestStaticMethods { + lateinit var utContext: AutoCloseable + + @Test + fun testStaticMethodCall() { + ConcreteExecutor( + CoverageInstrumentation, + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val res1 = it.execute(StaticExampleClass::inc, arrayOf()) + val coverageInfo1 = it.collectCoverage(StaticExampleClass::class.java) + + assertEquals(0, res1.getOrNull()) + assertEquals((3..6).toList() + (9..22).toList(), coverageInfo1.visitedInstrs) + + val res2 = it.execute( + StaticExampleClass::plus, + arrayOf(5) + ) + val coverageInfo2 = it.collectCoverage(StaticExampleClass::class.java) + + assertEquals(0, res2.getOrNull()) + assertTrue( + coverageInfo2.methodToInstrRange[StaticExampleClass::plus.signature]!!.toList() + .subtract(coverageInfo2.visitedInstrs) + .isEmpty() + ) + } + } + + @Test + fun testNullableMethod() { + ConcreteExecutor( + CoverageInstrumentation, + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val res1 = it.execute( + StaticExampleClass::canBeNull, + arrayOf(10, + "123") + ) + assertEquals("123", res1.getOrNull()) + + val res2 = it.execute( + StaticExampleClass::canBeNull, + arrayOf(0, + "kek") + ) + + assertEquals(null, res2.getOrNull()) + + val res3 = it.execute( + StaticExampleClass::canBeNull, + arrayOf(1, + null) + ) + + assertEquals(null, res3.getOrNull()) + } + } + + @Test + fun testNullableMethodWithoutAnnotations() { + ConcreteExecutor( + CoverageInstrumentation, + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ).use { + val res1 = it.execute( + StaticExampleClass::canBeNullWithoutAnnotations, + arrayOf(10, + "123") + ) + assertEquals("123", res1.getOrNull()) + + val res2 = it.execute( + StaticExampleClass::canBeNullWithoutAnnotations, + arrayOf(0, + "kek") + ) + + assertEquals(null, res2.getOrNull()) + + val res3 = it.execute( + StaticExampleClass::canBeNullWithoutAnnotations, + arrayOf(1, + null) + ) + + assertEquals(null, res3.getOrNull()) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt new file mode 100644 index 0000000000..f49c010b81 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt @@ -0,0 +1,90 @@ +package org.utbot.examples + +import org.utbot.examples.samples.ClassWithInnerClasses +import org.utbot.examples.samples.ClassWithSameMethodNames +import org.utbot.examples.samples.staticenvironment.StaticExampleClass +import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.utbot.instrumentation.withInstrumentation +import kotlin.reflect.full.declaredMembers +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TestWithInstrumentation { + lateinit var utContext: AutoCloseable + + @Test + fun testStaticMethodCall() { + withInstrumentation( + CoverageInstrumentation, + StaticExampleClass::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StaticExampleClass::inc, arrayOf()) + val coverageInfo1 = executor.collectCoverage(StaticExampleClass::class.java) + + assertEquals(0, res1.getOrNull()) + assertEquals((3..6).toList() + (9..22).toList(), coverageInfo1.visitedInstrs) + + val res2 = executor.execute( + StaticExampleClass::plus, + arrayOf(5) + ) + val coverageInfo2 = executor.collectCoverage(StaticExampleClass::class.java) + + assertEquals(0, res2.getOrNull()) + Assertions.assertTrue( + coverageInfo2.methodToInstrRange[StaticExampleClass::plus.signature]!!.toList() + .subtract(coverageInfo2.visitedInstrs) + .isEmpty() + ) + } + } + + @Test + fun testDifferentSignaturesButSameMethodNames() { + withInstrumentation( + InvokeInstrumentation(), + ClassWithSameMethodNames::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val clazz = ClassWithSameMethodNames::class + + val sumVararg = clazz.declaredMembers.first { it.parameters.size == 1 } + val sum2 = clazz.declaredMembers.first { it.parameters.size == 2 } + val sum3 = clazz.declaredMembers.first { it.parameters.size == 3 } + + val resVararg = executor.execute(sumVararg, arrayOf(intArrayOf(1, 2, 3, 4, 5))) + assertEquals(Result.success(15), resVararg) + + val resSum2 = executor.execute(sum2, arrayOf(1, 5)) + assertEquals(Result.success(8), resSum2) + + val resSum3 = executor.execute(sum3, arrayOf(1, 5, 4)) + assertEquals(Result.success(13), resSum3) + } + } + + @Test + fun testInnerClasses() { + withInstrumentation( + CoverageInstrumentation, + ClassWithInnerClasses::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val innerClazz = ClassWithInnerClasses.InnerClass::class.java + val innerStaticClazz = ClassWithInnerClasses.InnerStaticClass::class.java + + val classWithInnerClasses = ClassWithInnerClasses(5) + val res = executor.execute(ClassWithInnerClasses::doSomething, arrayOf(classWithInnerClasses, 1, 1)) + assertEquals(8, res.getOrNull()) + + val coverageInnerClass = executor.collectCoverage(innerClazz) + assertEquals((0..24).toList().minus(listOf(11, 12)), coverageInnerClass.visitedInstrs) + + val coverageInnerStaticClazz = executor.collectCoverage(innerStaticClazz) + assertEquals((3..6).toList(), coverageInnerStaticClazz.visitedInstrs) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt new file mode 100644 index 0000000000..41ec9da126 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt @@ -0,0 +1,85 @@ +package org.utbot.examples.benchmark + +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.utbot.instrumentation.util.Isolated +import kotlin.system.measureNanoTime +import org.junit.jupiter.api.Assertions.assertEquals + + +fun getBasicCoverageTime(count: Int): Double { + var time: Long + ConcreteExecutor( + CoverageInstrumentation, + Repeater::class.java.protectionDomain.codeSource.location.path + ).use { executor -> + val dc0 = Repeater(", ") + val concat = Isolated(Repeater::concat, executor) + + for (i in 0..20000) { + val res = concat(dc0, "flex", "mega-", 10) + assertEquals("mega-mega-mega-mega-mega-mega-mega-mega-mega-mega-flex", res.getOrNull()) + } + + time = measureNanoTime { + for (i in 0..count) { + val res = concat(dc0, "flex", "mega-", 10) + assertEquals("mega-mega-mega-mega-mega-mega-mega-mega-mega-mega-flex", res.getOrNull()) + } + executor.collectCoverage(Repeater::class.java) + } + } + return time / 1e6 +} + +fun getNativeCallTime(count: Int): Double { + val dc0 = Repeater(", ") + for (i in 0..20000) { + val res0 = dc0.concat("flex", "mega-", 10) + assertEquals("mega-mega-mega-mega-mega-mega-mega-mega-mega-mega-flex", res0) + } + val time = measureNanoTime { + for (i in 0..count) { + dc0.concat("flex", "mega-", 10) + } + } + return time / 1e6 +} + +fun getJustResultTime(count: Int): Double { + var time: Long + ConcreteExecutor( + InvokeInstrumentation(), + Repeater::class.java.protectionDomain.codeSource.location.path + ).use { + val dc0 = Repeater(", ") + val concat = Isolated(Repeater::concat, it) + + for (i in 0..20000) { + val res = concat(dc0, "flex", "mega-", 10) + assertEquals("mega-mega-mega-mega-mega-mega-mega-mega-mega-mega-flex", res.getOrNull()) + } + + time = measureNanoTime { + for (i in 0..count) { + concat(dc0, "flex", "mega-", 10) + } + } + } + return time / 1e6 +} + +fun main() { + val callsCount = 400_000 + + val nativeCallTime = getNativeCallTime(callsCount) + val basicCoverageTime = getBasicCoverageTime(callsCount) + val justResultTime = getJustResultTime(callsCount) + + println("Running results on $callsCount method calls") + println("nativeCall: $nativeCallTime ms") + println("basicCoverage: $basicCoverageTime ms, overhead per call: ${(basicCoverageTime - nativeCallTime) / callsCount} ms") + println("justResult: $justResultTime ms, overhead per call: ${(justResultTime - nativeCallTime) / callsCount} ms") +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt new file mode 100644 index 0000000000..4c370553de --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt @@ -0,0 +1,81 @@ +package org.utbot.examples.benchmark + +import org.utbot.examples.samples.benchmark.Fibonacci +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.utbot.instrumentation.util.Isolated +import kotlin.system.measureNanoTime + +fun getBasicCoverageTime_fib(count: Int): Double { + var time: Long + ConcreteExecutor( + CoverageInstrumentation, + Fibonacci::class.java.protectionDomain.codeSource.location.path + ).use { + val fib = Isolated(Fibonacci::calc, it) + for (i in 0..20_000) { + fib(1, 1, 13) + } + + time = measureNanoTime { + for (i in 0..count) { + fib(1, 1, 13) + } + it.collectCoverage(Fibonacci::class.java) + } + } + return time / 1e6 +} + +fun getNativeCallTime_fib(count: Int): Double { + for (i in 0..20_000) { + Fibonacci.calc(1, 1, 13) + + } + val time = measureNanoTime { + for (i in 0..count) { + Fibonacci.calc(1, 1, 13) + } + } + return time / 1e6 +} + +fun getJustResultTime_fib(count: Int): Double { + var time: Long + ConcreteExecutor( + InvokeInstrumentation(), + Fibonacci::class.java.protectionDomain.codeSource.location.path + ).use { + val fib = Isolated(Fibonacci::calc, it) + + for (i in 0..20_000) { + fib(1, 1, 13) + } + + time = measureNanoTime { + for (i in 0..count) { + fib(1, 1, 13) + } + } + } + return time / 1e6 +} + +fun main() { + withUtContext(UtContext(ClassLoader.getSystemClassLoader())) { + val callsCount = 300_000 + + val nativeCallTime = getNativeCallTime_fib(callsCount) + val basicCoverageTime = getBasicCoverageTime_fib(callsCount) + val justResultTime = getJustResultTime_fib(callsCount) + + println("Running results on $callsCount method calls") + println("nativeCall: $nativeCallTime ms") + println("basicCoverage: $basicCoverageTime ms, overhead per call: ${(basicCoverageTime - nativeCallTime) / callsCount} ms") + println("justResult: $justResultTime ms, overhead per call: ${(justResultTime - nativeCallTime) / callsCount} ms") + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt new file mode 100644 index 0000000000..a4e0656e12 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.benchmark + +import javafx.util.Pair + +class Repeater(var sep: String) { + /* public DifferentClass0() { + this.sep = "-"; + }*/ + fun repeat(str: String?, times: Int): String { + return concat(sep, str, times) + } + + fun concat(x: String?, y: String?, times: Int): String { + val sb = StringBuilder() + for (i in 0 until times) { + sb.append(y) + } + sb.append(x) + return sb.toString() + } +} + +class Unzipper { + var dc0 = Repeater("-") + fun unzip(chars: Array>): String { + val sb = java.lang.StringBuilder() + for (pr in chars) { + sb.append(dc0.repeat(pr.value.toString(), pr.key!!)) + } + return sb.toString() + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt new file mode 100644 index 0000000000..f1692a0334 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt @@ -0,0 +1,49 @@ +package org.utbot.examples.benchmark + +import org.utbot.examples.samples.benchmark.Fibonacci +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import java.math.BigInteger +import javafx.util.Pair +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TestBenchmarkClasses { + lateinit var utContext: AutoCloseable + + @Test + fun testRepeater() { + ConcreteExecutor( + CoverageInstrumentation, + Repeater::class.java.protectionDomain.codeSource.location.path + ).use { + val dc0 = Repeater(", ") + val res0 = it.execute(Repeater::concat, arrayOf(dc0, "flex", "mega-", 2)) + assertEquals("mega-mega-flex", res0.getOrNull()) + + + val dc1 = Unzipper() + val arr = arrayOf(Pair(1, 'h'), Pair(1, 'e'), Pair(2, 'l'), Pair(1, 'o')) + val res1 = it.execute(Unzipper::unzip, arrayOf(dc1, arr)) + assertEquals("h-e-ll-o-", res1.getOrNull()) + } + } + + @Test + fun testFibonacci() { + ConcreteExecutor( + InvokeInstrumentation(), + Fibonacci::class.java.protectionDomain.codeSource.location.path + ).use { + val res = + it.execute( + Fibonacci::calc, + arrayOf(1, 1, 10) + ) + assertEquals(Result.success(BigInteger.valueOf(89)), res) + } + } +} + diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt new file mode 100644 index 0000000000..560a314633 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt @@ -0,0 +1,54 @@ +package org.utbot.examples.et + +import org.utbot.examples.samples.et.ClassMixedWithNotInstrumented_Instr +import org.utbot.examples.samples.et.ClassMixedWithNotInstrumented_Not_Instr +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.et.ExecutionTraceInstrumentation +import org.utbot.instrumentation.instrumentation.et.convert +import org.utbot.instrumentation.instrumentation.et.function +import org.utbot.instrumentation.instrumentation.et.invoke +import org.utbot.instrumentation.instrumentation.et.pass +import org.utbot.instrumentation.instrumentation.et.ret +import org.utbot.instrumentation.util.Isolated +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +class TestMixedExTrace { + lateinit var utContext: AutoCloseable + + + val CLASSPATH = ClassMixedWithNotInstrumented_Instr::class.java.protectionDomain.codeSource.location.path + + @Disabled("The execution trace of mixed calls is not properly supported yet") + // `mixed calls` means such calls: A -> {B -> {A -> ...}, ... }, where A has been instrumented but B has not. + @Test + fun testMixedDoesNotThrow() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val A = Isolated(ClassMixedWithNotInstrumented_Instr::a, it) + val B = Isolated(ClassMixedWithNotInstrumented_Not_Instr::b, it) + + val res = A(1) + assertEquals( + function(A.signature) { + pass() + invoke(B.signature) { // TODO: think on clear DSL API for not instrumented calls + invoke(A.signature) { + pass() + ret() + } + invoke(A.signature) { + pass() + ret() + } + } + ret() + }, + convert(res) + ) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt new file mode 100644 index 0000000000..e7dab32d5f --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt @@ -0,0 +1,525 @@ +package org.utbot.examples.et + +import org.utbot.examples.samples.et.ClassBinaryRecursionWithThrow +import org.utbot.examples.samples.et.ClassBinaryRecursionWithTrickyThrow +import org.utbot.examples.samples.et.ClassSimple +import org.utbot.examples.samples.et.ClassSimpleCatch +import org.utbot.examples.samples.et.ClassSimpleNPE +import org.utbot.examples.samples.et.ClassSimpleRecursive +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.et.ExecutionTraceInstrumentation +import org.utbot.instrumentation.instrumentation.et.convert +import org.utbot.instrumentation.instrumentation.et.explThr +import org.utbot.instrumentation.instrumentation.et.function +import org.utbot.instrumentation.instrumentation.et.implThr +import org.utbot.instrumentation.instrumentation.et.invoke +import org.utbot.instrumentation.instrumentation.et.pass +import org.utbot.instrumentation.instrumentation.et.ret +import org.utbot.instrumentation.util.Isolated +import kotlin.reflect.full.declaredFunctions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + + +class TestSimpleExTrace { + lateinit var utContext: AutoCloseable + + val CLASSPATH = ClassSimple::class.java.protectionDomain.codeSource.location.path + + /** + * #1. doesNotThrow + * #2. alwaysThrows + * #3. maybeThrows(-1) maybeThrows(0) + */ + @Test + fun testClassSimple() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val alwaysThrows = Isolated(ClassSimple::alwaysThrows, it) + val maybeThrows = Isolated(ClassSimple::maybeThrows, it) + val doesNotThrow = Isolated(ClassSimple::doesNotThrow, it) + + val et1 = doesNotThrow() + assertEquals( + function(doesNotThrow.signature) { + pass() + ret() + }, + convert(et1) + ) + + val et2 = alwaysThrows() + assertEquals( + function(alwaysThrows.signature) { + pass() + explThr() + }, + convert(et2) + ) + + val et3 = maybeThrows(-1) + assertEquals( + function(maybeThrows.signature) { + pass() + explThr() + }, + convert(et3) + ) + + val et4 = maybeThrows(0) + assertEquals( + function(maybeThrows.signature) { + pass() + ret() + }, + convert(et4) + ) + } + } + + + /** + * #1. A + * #2. A_catches + * #3. A_doesNotCatch + * #4. A_catchesWrongException + */ + @Test + fun testClasSimpleCatch() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val A = Isolated(ClassSimpleCatch::A, it) + val A_catches = Isolated(ClassSimpleCatch::A_catches, it) + val A_catchesWrongException = Isolated(ClassSimpleCatch::A_catchesWrongException, it) + val A_doesNotCatch = Isolated(ClassSimpleCatch::A_doesNotCatch, it) + + val B = Isolated(ClassSimpleCatch::B, it) + val B_throws = Isolated(ClassSimpleCatch::B_throws, it) + + + val et1 = A() + assertEquals( + function(A.signature) { + invoke(B.signature) { + pass() + ret() + } + pass() + ret() + }, + convert(et1) + ) + + val et2 = A_catches() + assertEquals( + function(A_catches.signature) { + invoke(B_throws.signature) { + pass() + implThr() + } + pass() + ret() + }, + convert(et2) + ) + + val et3 = A_catchesWrongException() + assertEquals( + function(A_catchesWrongException.signature) { + invoke(B_throws.signature) { + pass() + implThr() + } + }, + convert(et3) + ) + + val et4 = A_doesNotCatch() + assertEquals( + function(A_doesNotCatch.signature) { + invoke(B_throws.signature) { + pass() + implThr() + } + }, + convert(et4) + ) + } + } + + /** + * #1. A(3) + * #2. A_recursive(3, 2) + * #3. A_recursive(3, 1) + */ + @Test + fun testClassSimpleRecursive() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val A = Isolated(ClassSimpleRecursive::A, it) + val A_recursive = Isolated(ClassSimpleRecursive::A_recursive, it) + + val et1 = A(3) + assertEquals( + function(A.signature) { + pass() + invoke(A.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + ret() + } + ret() + }, + convert(et1) + ) + + val et2 = A_recursive(3, 2) + assertEquals( + function(A_recursive.signature) { + pass() + invoke(A_recursive.signature) { + pass() + invoke(A_recursive.signature) { + pass() + explThr() + } + pass() + explThr() + } + pass() + ret() + }, + convert(et2) + ) + + val et3 = A_recursive(3, 1) + assertEquals( + function(A_recursive.signature) { + pass() + invoke(A_recursive.signature) { + pass() + invoke(A_recursive.signature) { + pass() + explThr() + } + pass() + explThr() + } + }, + convert(et3) + ) + } + } + + /** + * #1. A(1, 10, 2) + * #2. A_catchesAll(true, 2) + * #3. A_notAll(right, 2) + */ + @Test + fun testClassBinaryRecursionWithTrickyThrow() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val A = Isolated(ClassBinaryRecursionWithTrickyThrow::A, it) + val A_catchesAll = Isolated(ClassBinaryRecursionWithTrickyThrow::A_catchesAll, it) + val A_notAll = Isolated(ClassBinaryRecursionWithTrickyThrow::A_notAll, it) + + val et1 = A(1, 10, 2) + assertEquals( + function(A.signature) { + pass() + invoke(A.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + ret() + }, + convert(et1) + ) + + val et2 = A_catchesAll(true, 2) + + assertEquals( + function(A_catchesAll.signature) { + pass() + invoke(A_catchesAll.signature) { + pass() + invoke(A_catchesAll.signature) { + pass() + explThr() + } + pass() + invoke(A_catchesAll.signature) { + pass() + explThr() + } + pass() + ret() + } + pass() + invoke(A_catchesAll.signature) { + pass() + invoke(A_catchesAll.signature) { + pass() + explThr() + } + } + pass() + ret() + }, + convert(et2) + ) + + val et3 = A_notAll(false, 2) + assertEquals( + function(A_notAll.signature) { + pass() + invoke(A_notAll.signature) { + pass() + invoke(A_notAll.signature) { + pass() + explThr() + } + } + pass() + invoke(A_notAll.signature) { + pass() + invoke(A_notAll.signature) { + pass() + explThr() + } + pass() + invoke(A_notAll.signature) { + pass() + explThr() + } + } + pass() + ret() + }, + convert(et3) + ) + } + } + + + /** + * #1. A(1, 2, false) + * #2. A(1, 2, true) + */ + @Test + fun testClassBinaryRecursionWithThrow() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val A = Isolated(ClassBinaryRecursionWithThrow::A, it) + val B = Isolated(ClassBinaryRecursionWithThrow::class.declaredFunctions.first { it.name == "B" }, it) + + val et1 = A(1, 2, false) + assertEquals( + function(A.signature) { + pass() + invoke(B.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + invoke(B.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + ret() + } + ret() + } + ret() + }, + convert(et1) + ) + + val et2 = A(1, 2, true) + assertEquals( + function(A.signature) { + pass() + invoke(B.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + invoke(B.signature) { + pass() + explThr() + } + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + invoke(A.signature) { + pass() + ret() + } + pass() + invoke(A.signature) { + pass() + ret() + } + ret() + } + ret() + }, + convert(et2) + ) + } + } + + /** + * #1. A(false) + * #2. A(true) + */ + @Test + fun testClassSimpleNPE() { + ConcreteExecutor( + ExecutionTraceInstrumentation(), + CLASSPATH + ).use { + val A = Isolated(ClassSimpleNPE::A, it) + val B = Isolated(ClassSimpleNPE::B, it) + val C = Isolated(ClassSimpleNPE::C, it) + val D = Isolated(ClassSimpleNPE::D, it) + + val thisObject = ClassSimpleNPE() + + val et1 = A(thisObject, false) + assertEquals( + function(A.signature) { + pass() + invoke(B.signature) { + pass() + invoke(B.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + invoke(C.signature) { + pass() + invoke(C.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + invoke(D.signature) { + pass() + invoke(D.signature) { + pass() + invoke(D.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + ret() + } + ret() + }, + convert(et1) + ) + + val et2 = A(thisObject, true) + assertEquals( + function(A.signature) { + pass() + invoke(B.signature) { + pass() + invoke(B.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + invoke(C.signature) { + pass() + invoke(C.signature) { + pass() + ret() + } + pass() + ret() + } + pass() + invoke(D.signature) { + pass() + implThr() + } + }, + convert(et2) + ) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt new file mode 100644 index 0000000000..a7ec3e5780 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt @@ -0,0 +1,58 @@ +package org.utbot.examples.et + +import org.utbot.examples.objects.ObjectWithStaticFieldsClass +import org.utbot.examples.objects.ObjectWithStaticFieldsExample +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.field +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.et.ExecutionTraceInstrumentation +import org.utbot.instrumentation.withInstrumentation +import kotlin.reflect.jvm.javaField +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class StaticsUsageDetectionTest { + lateinit var utContext: AutoCloseable + + @BeforeEach + fun initContext() { + utContext = UtContext.setUtContext(UtContext(ClassLoader.getSystemClassLoader())) + } + + @AfterEach + fun closeConctext() { + utContext.close() + } + + + @Test + fun testStaticsUsageOneUsage() { + withInstrumentation( + ExecutionTraceInstrumentation(), + ObjectWithStaticFieldsExample::class.java.protectionDomain.codeSource.location.path + ) { + val instance = ObjectWithStaticFieldsExample() + val classInstance = ObjectWithStaticFieldsClass() + classInstance.x = 200 + classInstance.y = 200 + val result = it.execute(ObjectWithStaticFieldsExample::setStaticField, arrayOf(instance, classInstance)) + assertEquals(ObjectWithStaticFieldsClass::staticValue.javaField, result.usedStatics.single().field) + } + } + + @Test + fun testStaticsUsageZeroUsages() { + withInstrumentation( + ExecutionTraceInstrumentation(), + ObjectWithStaticFieldsExample::class.java.protectionDomain.codeSource.location.path + ) { + val instance = ObjectWithStaticFieldsExample() + val classInstance = ObjectWithStaticFieldsClass() + val result = it.execute(ObjectWithStaticFieldsExample::setStaticField, arrayOf(instance, classInstance)) + assertTrue(result.usedStatics.isEmpty()) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt new file mode 100644 index 0000000000..de8051a775 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt @@ -0,0 +1,214 @@ +package org.utbot.examples.jacoco + +import org.utbot.common.Reflection +import org.utbot.examples.samples.jacoco.ExceptionExamples +import org.utbot.examples.samples.jacoco.MonitorUsage +import org.utbot.examples.samples.jacoco.Recursion +import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.coverage.CoverageInfo +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.instrumentation.coverage.collectCoverage +import org.utbot.instrumentation.withInstrumentation +import java.io.InputStream +import java.lang.reflect.InvocationTargetException +import kotlin.reflect.KCallable +import kotlin.reflect.KClass +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.instanceParameter +import org.jacoco.core.analysis.Analyzer +import org.jacoco.core.analysis.CoverageBuilder +import org.jacoco.core.analysis.IClassCoverage +import org.jacoco.core.data.ExecutionDataStore +import org.jacoco.core.data.SessionInfoStore +import org.jacoco.core.instr.Instrumenter +import org.jacoco.core.runtime.IRuntime +import org.jacoco.core.runtime.LoggerRuntime +import org.jacoco.core.runtime.RuntimeData +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +class TestSameAsJaCoCo { + private fun checkSame(kClass: KClass<*>, method: KCallable<*>, executions: List) { + val methodCoverageJaCoCo = methodCoverageWithJaCoCo(kClass, method, executions) + val methodCoverageOur = methodCoverage(kClass, method, executions) + + Assertions.assertTrue(methodCoverageJaCoCo.first <= methodCoverageOur.first) { + "JaCoCo (${methodCoverageJaCoCo.first} should not cover more instructions than we cover" + + "(${methodCoverageOur.first})" + } + Assertions.assertEquals(methodCoverageJaCoCo, methodCoverageOur) + } + + @Test + @Disabled( + "Synchronized causes the creation of extra bytecode instructions by java compiler." + + "JaCoCo ignores them, but we do not." + ) + fun testSimpleMonitor() { + val monitorUsage = MonitorUsage() + val executions = listOf(listOf(monitorUsage, 1), listOf(monitorUsage, -1), listOf(monitorUsage, 0)) + + checkSame(MonitorUsage::class, MonitorUsage::simpleMonitor, executions) + } + + @Test + @Disabled( + "Finally block is copied into each catch branch, so instructions are duplicated." + + "JaCoCo treats such instructions as one instruction, but we treat them as separate." + ) + fun testFinallyChanging() { + val exceptionExamples = ExceptionExamples() + val executions = listOf(listOf(exceptionExamples, 0)) + + checkSame(ExceptionExamples::class, ExceptionExamples::finallyChanging, executions) + } + + @Test + @Disabled("If an exception happens, JaCoCo ignores passed instructions before the exception, but we do not.") + fun testThrowException() { + val exceptionExamples = ExceptionExamples() + val executions = listOf(listOf(exceptionExamples, -1), listOf(exceptionExamples, 1)) + + checkSame(ExceptionExamples::class, ExceptionExamples::throwException, executions) + } + + + @Test + @Disabled("For some reason (?), JaCoCo sometimes ignores instructions.") + fun recursionWithExceptionTest() { + val recursion = Recursion() + val executions = listOf(listOf(recursion, 41), listOf(recursion, 42), listOf(recursion, 43)) + + checkSame(Recursion::class, Recursion::recursionWithException, executions) + } + + @Test + @Disabled("For some reason (?), JaCoCo doesn't count instructions, even they were executed successfully.") + fun infiniteRecursionTest() { + val recursion = Recursion() + val executions = listOf(listOf(recursion, 0)) + + checkSame(Recursion::class, Recursion::infiniteRecursion, executions) + } +} + +private fun methodCoverageWithJaCoCo(kClass: KClass<*>, method: KCallable<*>, executions: List): Pair { + val methodSignature = method.signature + val coverage = calculateCoverage(kClass) { clazz -> + val instrumentedMethod = clazz.declaredFunctions.single { it.signature == methodSignature } + val onInstance = instrumentedMethod.instanceParameter != null + for (execution in executions) { + try { + if (onInstance) { + instrumentedMethod.call(clazz.java.anyInstance, *execution.drop(1).toTypedArray()) + } else { + instrumentedMethod.call(*execution.toTypedArray()) + } + } catch (_: InvocationTargetException) { + } + } + } + val methodCoverage = coverage.classes + .single { it.qualifiedName == kClass.qualifiedName } + .methods + .single { + "${it.name}${it.desc}" == methodSignature + } + + return methodCoverage.instructionCounter.let { it.coveredCount to it.coveredCount } +} + +private fun methodCoverage(kClass: KClass<*>, method: KCallable<*>, executions: List): Pair { + return withInstrumentation( + CoverageInstrumentation, + kClass.java.protectionDomain.codeSource.location.path + ) { executor -> + for (execution in executions) { + executor.execute(method, execution.toTypedArray()) + } + + val methodSignature = method.signature + val coverage = executor.collectCoverage(kClass.java) + coverage.toMethodCoverage(methodSignature) + } +} + +private fun CoverageInfo.toMethodCoverage(methodSignature: String): Pair { + val methodRange = methodToInstrRange[methodSignature]!! + val visitedCount = visitedInstrs.filter { it in methodRange }.size + return visitedCount to methodRange.count() +} + +// The following helper functions for counting JaCoCo coverage were copied from `utbot-framework`. + +private fun calculateCoverage(clazz: KClass<*>, block: (KClass<*>) -> Unit): CoverageBuilder { + val targetName = clazz.qualifiedName!! + + // IRuntime instance to collect execution data + val runtime: IRuntime = LoggerRuntime() + + // create a modified version of target class with probes + val instrumenter = Instrumenter(runtime) + val instrumented = instrument(clazz, instrumenter) + + // startup the runtime + val data = RuntimeData() + runtime.startup(data) + + // load class from byte[] instances + val memoryClassLoader = MemoryClassLoader() + memoryClassLoader.addDefinition(targetName, instrumented) + val targetClass = memoryClassLoader.loadClass(targetName).kotlin + + // execute code + block(targetClass) + + // shutdown the runtime + val executionData = ExecutionDataStore() + val sessionInfos = SessionInfoStore() + data.collect(executionData, sessionInfos, false) + runtime.shutdown() + + // calculate coverage + return CoverageBuilder().apply { + val analyzer = Analyzer(executionData, this) + clazz.asInputStream().use { + analyzer.analyzeClass(it, targetName) + } + } +} + +private fun instrument(clazz: KClass<*>, instrumenter: Instrumenter): ByteArray = + clazz.asInputStream().use { + instrumenter.instrument(it, clazz.qualifiedName) + } + +private fun KClass<*>.asInputStream(): InputStream = + java.getResourceAsStream("/${qualifiedName!!.replace('.', '/')}.class")!! + +private class MemoryClassLoader : ClassLoader() { + private val definitions: MutableMap = mutableMapOf() + + fun addDefinition(name: String, bytes: ByteArray) { + definitions[name] = bytes + } + + override fun loadClass(name: String, resolve: Boolean): Class<*> { + val bytes = definitions[name] + return if (bytes != null) { + defineClass(name, bytes, 0, bytes.size) + } else super.loadClass(name, resolve) + } +} + +private val IClassCoverage.qualifiedName: String + get() = this.name.replace('/', '.') + +@Suppress("DEPRECATION") +private val Class<*>.anyInstance: Any + get() { + return Reflection.unsafe.allocateInstance(this) + } \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ClassWithWrongPackage.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ClassWithWrongPackage.kt new file mode 100644 index 0000000000..62638aa43a --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ClassWithWrongPackage.kt @@ -0,0 +1,4 @@ +package org.utbot.examples.samples.wrongpackage + +class ClassWithWrongPackage { +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt new file mode 100644 index 0000000000..6f84b7c62c --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt @@ -0,0 +1,67 @@ +package org.utbot.examples.samples + +class ExampleClass { + var x1 = 1 + val arr = BooleanArray(5) + val arr2 = BooleanArray(10) + + fun bar(x: Int) { + if (x > 1) { + x1++ + x1++ + } else { + x1-- + x1-- + } + } + + fun kek2(x: Int) { + arr[x] = true + } + + fun foo(x: Int): Int { + x1 = x xor 2 + + var was = false + + for (i in 0 until x) { + was = true + var x2 = 0 + if (i > 5) { + was = false + x2 = 1 + } + if (was && x2 == 0) { + was = true + } + } + + // empty lines + return if (was) x1 else x1 + 1 + } + + fun dependsOnField() { + x1 = x1 xor 1 + if (x1 and 1 == 1) { + x1 += 4 + } else { + x1 += 2 + } + } + + fun dependsOnFieldReturn(): Int { + x1 = x1 xor 1 + if (x1 and 1 == 1) { + x1 += 4 + } else { + x1 += 2 + } + return x1 + } + + fun emptyMethod() { + } + + @Suppress("unused") + fun use() = arr2.size +} \ No newline at end of file From 9c7a052c11d6f6b98f0acca443e445c919ec9327 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 31 May 2022 18:16:13 +0300 Subject: [PATCH 012/120] Assert explicitly thrown exceptions in generated tests --- .../constructor/tree/CgMethodConstructor.kt | 21 ++++++++++++------- .../constructor/tree/TestFrameworkManager.kt | 5 +++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index adc28f3ab2..aaa9ab0516 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -107,6 +107,7 @@ import org.utbot.framework.plugin.api.UtEnumConstantModel import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation @@ -292,7 +293,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c // we cannot generate any assertions for constructor testing // but we need to generate a constructor call val constructorCall = currentExecutable as ConstructorId - currentExecution!!.result + val currentExecution = currentExecution!! + currentExecution.result .onSuccess { methodType = SUCCESSFUL @@ -308,15 +310,16 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } .onFailure { exception -> - processExecutionFailure(exception) + processExecutionFailure(currentExecution, exception) } } is BuiltinMethodId -> error("Unexpected BuiltinMethodId $currentExecutable while generating result assertions") is MethodId -> { emptyLineIfNeeded() val method = currentExecutable as MethodId + val currentExecution = currentExecution!! // build assertions - currentExecution!!.result + currentExecution.result .onSuccess { result -> methodType = SUCCESSFUL @@ -330,13 +333,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } .onFailure { exception -> - processExecutionFailure(exception) + processExecutionFailure(currentExecution, exception) } } } } - private fun processExecutionFailure(exception: Throwable) { + private fun processExecutionFailure(execution: UtExecution, exception: Throwable) { val methodInvocationBlock = { with(currentExecutable) { when (this) { @@ -346,7 +349,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } - if (shouldTestPassWithExpectedException(exception)) { + if (shouldTestPassWithException(execution, exception)) { testFrameworkManager.expectException(exception::class.id) { methodInvocationBlock() } @@ -373,11 +376,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c methodInvocationBlock() } - private fun shouldTestPassWithExpectedException(exception: Throwable): Boolean { + private fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean { // tests with timeout or crash should be processed differently if (exception is TimeoutException || exception is ConcreteExecutionFailureException) return false - return runtimeExceptionTestsBehaviour == PASS || exception !is RuntimeException + val exceptionRequiresAssert = exception !is RuntimeException || runtimeExceptionTestsBehaviour == PASS + val exceptionIsExplicit = execution.result is UtExplicitlyThrownException + return exceptionRequiresAssert || exceptionIsExplicit } private fun writeWarningAboutTimeoutExceeding() { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt index 6b3c570520..b671edef18 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt @@ -147,9 +147,10 @@ internal abstract class TestFrameworkManager(val context: CgContext) fun assertBoolean(actual: CgExpression) = assertBoolean(expected = true, actual) - // Exception expectation is very different in JUnit4 and JUnit5 + // Exception expectation differs between test frameworks // JUnit4 requires to add a specific argument to the test method annotation - // JUnit5 requires to use method assertThrows() + // JUnit5 requires using method assertThrows() + // TestNg allows both approaches, we use similar to JUnit5 abstract fun expectException(exception: ClassId, block: () -> Unit) open fun setTestExecutionTimeout(timeoutMs: Long) { From ce2c3011f50c54ccf59b94289eb9f5e5f2877725 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Wed, 1 Jun 2022 12:42:05 +0300 Subject: [PATCH 013/120] Add API to generate only fuzzed test cases (#90) --- .../org/utbot/engine/UtBotSymbolicEngine.kt | 7 +- .../org/utbot/external/api/UtBotJavaApi.kt | 97 +++++++++++++++++++ .../plugin/api/UtBotTestCaseGenerator.kt | 37 +++++-- .../examples/manual/UtBotJavaApiTest.java | 59 +++++++++++ .../manual/examples/StringSwitchExample.java | 18 ++++ .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 47 +++++++-- .../framework/plugin/api/ModelProviderTest.kt | 17 ++++ 7 files changed, 259 insertions(+), 23 deletions(-) create mode 100644 utbot-framework/src/test/java/org/utbot/examples/manual/examples/StringSwitchExample.java diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 18bc138d74..b407e72e4d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -513,10 +513,7 @@ class UtBotSymbolicEngine( //Simple fuzzing - fun fuzzing(modelProvider: ModelProvider = defaultModelProviders { nextDefaultModelId++ }) = flow { - if (!UtSettings.useFuzzing) - return@flow - + fun fuzzing(modelProvider: (ModelProvider) -> ModelProvider = { it }) = flow { val executableId = if (methodUnderTest.isConstructor) { methodUnderTest.javaConstructor!!.executableId } else { @@ -553,7 +550,7 @@ class UtBotSymbolicEngine( } val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)) - val modelProviderWithFallback = modelProvider.withFallback(fallbackModelProvider::toModel) + val modelProviderWithFallback = modelProvider(defaultModelProviders { nextDefaultModelId++ }).withFallback(fallbackModelProvider::toModel) val coveredInstructionTracker = mutableSetOf() var attempts = UtSettings.fuzzingMaxAttemps fuzz(methodUnderTestDescription, modelProviderWithFallback).forEachIndexed { index, parameters -> diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt index 107c357cc1..5c2b668c71 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt @@ -12,15 +12,25 @@ import org.utbot.framework.codegen.model.ModelBasedCodeGeneratorService import org.utbot.framework.concrete.UtConcreteExecutionData import org.utbot.framework.concrete.UtConcreteExecutionResult import org.utbot.framework.concrete.UtExecutionInstrumentation +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.MockStrategyApi import org.utbot.framework.plugin.api.UtBotTestCaseGenerator import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtTestCase import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.plugin.api.util.wrapperByPrimitive +import org.utbot.fuzzer.ModelProvider import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.execute import java.lang.reflect.Method @@ -91,6 +101,11 @@ object UtBotJavaApi { } } + /** + * Generates test cases using default workflow. + * + * @see [fuzzingTestCases] + */ @JvmStatic @JvmOverloads fun generateTestCases( @@ -128,6 +143,73 @@ object UtBotJavaApi { return testCases } + /** + * Generates test cases using only fuzzing workflow. + * + * @see [generateTestCases] + */ + @JvmStatic + @JvmOverloads + fun fuzzingTestCases( + methodsForAutomaticGeneration: List, + classUnderTest: Class<*>, + classpath: String, + dependencyClassPath: String, + mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES, + generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis, + primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null } + ): MutableList { + fun createPrimitiveModels(supplier: CustomFuzzerValueSupplier, classId: ClassId): Sequence = + supplier + .takeIf { classId.isPrimitive || classId.isPrimitiveWrapper || classId == stringClassId } + ?.get(classId.jClass) + ?.asSequence() + ?.filter { + val valueClassId = it.javaClass.id + when { + classId == valueClassId -> true + classId.isPrimitive -> wrapperByPrimitive[classId] == valueClassId + classId.isPrimitiveWrapper -> primitiveByWrapper[classId] == valueClassId + else -> false + } + } + ?.map { UtPrimitiveModel(it) } ?: emptySequence() + + val customModelProvider = ModelProvider { description, consumer -> + description.parametersMap.forEach { (classId, indices) -> + createPrimitiveModels(primitiveValuesSupplier, classId).forEach { model -> + indices.forEach { index -> + consumer.accept(index, model) + } + } + } + } + + return withUtContext(UtContext(classUnderTest.classLoader)) { + UtBotTestCaseGenerator + .apply { + init( + FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath + ) + }.generateForSeveralMethods( + methodsForAutomaticGeneration.map { + toUtMethod( + it.methodToBeTestedFromUserInput, + classUnderTest.kotlin + ) + }, + mockStrategyApi, + chosenClassesToMockAlways = emptySet(), + generationTimeoutInMillis, + generate = { symbolicEngine -> + symbolicEngine.fuzzing { defaultModelProvider -> + customModelProvider.withFallback(defaultModelProvider) + } + } + ) + }.toMutableList() + } + private fun generateUnitTests( concreteExecutor: ConcreteExecutor, testMethods: List, @@ -182,4 +264,19 @@ object UtBotJavaApi { listOf(utExecution) ) }.toList() +} + +/** + * Accepts type of parameter and returns collection of values for this type. + * + * Value types which can be generated: + * + * - primitive types: boolean, char, byte, short, int, long, float, double + * - primitive wrappers: Boolean, Character, Byte, Short, Integer, Long, Float, Double + * - String + * + * If null is returned instead of empty collection then default fuzzer values are produced. + */ +fun interface CustomFuzzerValueSupplier { + fun get(type: Class<*>): Collection? } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt index c686aaf07d..5ef2470615 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt @@ -166,11 +166,28 @@ object UtBotTestCaseGenerator : TestCaseGenerator { chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, executionTimeEstimator: ExecutionTimeEstimator = ExecutionTimeEstimator(utBotGenerationTimeoutInMillis, 1) ): Flow { + val engine = createSymbolicEngine( + controller, + method, + mockStrategy, + chosenClassesToMockAlways, + executionTimeEstimator + ) + return createDefaultFlow(engine) + } + + private fun createSymbolicEngine( + controller: EngineController, + method: UtMethod<*>, + mockStrategy: MockStrategyApi, + chosenClassesToMockAlways: Set, + executionTimeEstimator: ExecutionTimeEstimator + ): UtBotSymbolicEngine { // TODO: create classLoader from buildDir/classpath and migrate from UtMethod to MethodId? logger.debug("Starting symbolic execution for $method --$mockStrategy--") val graph = graph(method) - val engine = UtBotSymbolicEngine( + return UtBotSymbolicEngine( controller, method, graph, @@ -180,11 +197,14 @@ object UtBotTestCaseGenerator : TestCaseGenerator { chosenClassesToMockAlways = chosenClassesToMockAlways, solverTimeoutInMillis = executionTimeEstimator.updatedSolverCheckTimeoutMillis ) + } - return flowOf( - engine.traverse(), - engine.fuzzing(), - ).flattenConcat() + private fun createDefaultFlow(engine: UtBotSymbolicEngine): Flow { + var flow = engine.traverse() + if (UtSettings.useFuzzing) { + flow = flowOf(flow, engine.fuzzing()).flattenConcat() + } + return flow } // CONFLUENCE:The+UtBot+Java+timeouts @@ -224,7 +244,8 @@ object UtBotTestCaseGenerator : TestCaseGenerator { methods: List>, mockStrategy: MockStrategyApi, chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, - methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis + methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis, + generate: (engine: UtBotSymbolicEngine) -> Flow = ::createDefaultFlow ): List { if (isCanceled()) return methods.map { UtTestCase(it) } @@ -246,13 +267,13 @@ object UtBotTestCaseGenerator : TestCaseGenerator { //yield one to yield() - generateAsync( + generate(createSymbolicEngine( controller, method, mockStrategy, chosenClassesToMockAlways, executionTimeEstimator - ).collect { + )).collect { when (it) { is UtExecution -> method2executions.getValue(method) += it is UtError -> method2errors.getValue(method).merge(it.description, 1, Int::plus) diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java index f9c3fedcbc..89ffcb5157 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java +++ b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java @@ -13,6 +13,7 @@ import org.utbot.examples.manual.examples.DirectAccessExample; import org.utbot.examples.manual.examples.MultiMethodExample; import org.utbot.examples.manual.examples.ProvidedExample; +import org.utbot.examples.manual.examples.StringSwitchExample; import org.utbot.examples.manual.examples.Trivial; import org.utbot.examples.manual.examples.customer.B; import org.utbot.examples.manual.examples.customer.C; @@ -1244,6 +1245,64 @@ public void testOnObjectWithArrayOfComplexArrays() { compileClassFile(destinationClassName, snippet2); } + @Test + public void testFuzzingSimple() { + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(StringSwitchExample.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + classIdForType(StringSwitchExample.class) + ); + + Method methodUnderTest = getMethodByName(StringSwitchExample.class, "validate", String.class, int.class, int.class); + + IdentityHashMap models = modelFactory.produceAssembleModel( + methodUnderTest, + StringSwitchExample.class, + Collections.singletonList(classUnderTestModel) + ); + + EnvironmentModels methodState = new EnvironmentModels( + models.get(classUnderTestModel), + Arrays.asList(new UtPrimitiveModel("initial model"), new UtPrimitiveModel(-10), new UtPrimitiveModel(0)), + Collections.emptyMap() + ); + + TestMethodInfo methodInfo = new TestMethodInfo( + methodUnderTest, + methodState); + List utTestCases1 = UtBotJavaApi.fuzzingTestCases( + Collections.singletonList( + methodInfo + ), + StringSwitchExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + (type) -> { + if (int.class.equals(type) || Integer.class.equals(type)) { + return Arrays.asList(0, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + return null; + } + ); + + String generate = UtBotJavaApi.generate( + Collections.singletonList(methodInfo), + utTestCases1, + destinationClassName, + classpath, + dependencyClassPath, + StringSwitchExample.class + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generate); + compileClassFile(destinationClassName, snippet2); + } + @NotNull private String getClassPath(Class clazz) { return clazz.getProtectionDomain().getCodeSource().getLocation().getPath(); diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/StringSwitchExample.java b/utbot-framework/src/test/java/org/utbot/examples/manual/examples/StringSwitchExample.java new file mode 100644 index 0000000000..aab811c3c2 --- /dev/null +++ b/utbot-framework/src/test/java/org/utbot/examples/manual/examples/StringSwitchExample.java @@ -0,0 +1,18 @@ +package org.utbot.examples.manual.examples; + +public class StringSwitchExample { + + public int validate(String value, int number, int defaultValue) { + switch (value) { + case "one": + return number > 1 ? number : -1; + case "two": + return number > 2 ? number : -2; + case "three": + return number > 3 ? number : -3; + default: + return defaultValue; + } + } + +} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt index f5b6a62640..2f3013241c 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -43,14 +43,14 @@ fun interface ModelProvider { } /** - * Creates [ModelProvider] that passes unprocessed classes to `fallbackModelSupplier` function. + * Creates [ModelProvider] that passes unprocessed classes to `modelProvider`. * - * This model provider is called before function is called, therefore consumer will get values - * from this model provider and only after it created by `fallbackModelSupplier`. + * Returned model provider is called before `modelProvider` is called, therefore consumer will get values + * from returned model provider and only after it calls `modelProvider`. * - * @param fallbackModelSupplier is called for every [ClassId] which wasn't created by this model provider. + * @param modelProvider is called and every value of [ClassId] is collected which wasn't created by this model provider. */ - fun withFallback(fallbackModelSupplier: (ClassId) -> UtModel?) : ModelProvider { + fun withFallback(modelProvider: ModelProvider) : ModelProvider { return ModelProvider { description, consumer -> val providedByDelegateMethodParameters = mutableMapOf>() this@ModelProvider.generate(description) { index, model -> @@ -61,12 +61,39 @@ fun interface ModelProvider { consumer.accept(index, model) } } - (0 until description.parameters.size) - .filter { !providedByDelegateMethodParameters.containsKey(it) } - .associateWith { description.parameters[it] } - .forEach { (index, classId) -> - fallbackModelSupplier(classId)?.let { consumer.accept(index, it) } + val missingParameters = + (0 until description.parameters.size).filter { !providedByDelegateMethodParameters.containsKey(it) } + if (missingParameters.isNotEmpty()) { + val values = mutableMapOf>() + modelProvider.generate(description) { i, m -> values.computeIfAbsent(i) { mutableListOf() }.add(m) } + missingParameters.forEach { index -> + values[index]?.let { models -> + models.forEach { model -> + consumer.accept(index, model) + } + } + } + } + } + } + + /** + * Creates [ModelProvider] that passes unprocessed classes to `fallbackModelSupplier` function. + * + * This model provider is called before function is called, therefore consumer will get values + * from this model provider and only after it created by `fallbackModelSupplier`. + * + * @param fallbackModelSupplier is called for every [ClassId] which wasn't created by this model provider. + */ + fun withFallback(fallbackModelSupplier: (ClassId) -> UtModel?) : ModelProvider { + return withFallback { description, consumer -> + description.parametersMap.forEach { (classId, indices) -> + fallbackModelSupplier(classId)?.let { model -> + indices.forEach { index -> + consumer.accept(index, model) + } } + } } } diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index f129643eb8..17eb4fce0a 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -242,6 +242,23 @@ class ModelProviderTest { } } + @Test + fun `test fallback model can create custom values for any parameter`() { + val firstParameterIsUserGenerated = ModelProvider { _, consumer -> + consumer.accept(0, UtPrimitiveModel(-123)) + }.withFallback(PrimitivesModelProvider) + + val result = collect( + firstParameterIsUserGenerated, + parameters = listOf(intClassId, intClassId) + ) + + assertEquals(2, result.size) + assertEquals(1, result[0]!!.size) + assertTrue(result[1]!!.size > 1) + assertEquals(UtPrimitiveModel(-123), result[0]!![0]) + } + private fun collect( modelProvider: ModelProvider, name: String = "testMethod", From d6e9104e1d5d69f617a3f47d1ccf21a89e282b9e Mon Sep 17 00:00:00 2001 From: Denis Fokin Date: Wed, 1 Jun 2022 15:32:08 +0300 Subject: [PATCH 014/120] Pull request template is ignored for pull requests closes #109 --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From 08447fc5a1ea9eff7c2ce86a9bc726323183ec5a Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Wed, 1 Jun 2022 18:59:30 +0300 Subject: [PATCH 015/120] Fuzzer generates uncompilable test methods in Contest Estimator #111 (#114) --- .../src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index b407e72e4d..8d081900c6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -584,7 +584,7 @@ class UtBotSymbolicEngine( path = mutableListOf(), fullPath = emptyList(), coverage = concreteExecutionResult.coverage, - testMethodName = "test${methodUnderTest.callable.name.capitalize()}ByFuzzer${index}" + testMethodName = if (methodUnderTest.isMethod) "test${methodUnderTest.callable.name.capitalize()}ByFuzzer${index}" else null ) ) } catch (e: CancellationException) { From 7ca5a35c323640181e0ed28010441a37db0e51a9 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 2 Jun 2022 16:08:15 +0300 Subject: [PATCH 016/120] Multiline array initializer formatted rendered --- .../model/visitor/CgAbstractRenderer.kt | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index 9974010925..866787fb17 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -756,14 +756,30 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } protected fun UtArrayModel.renderElements(length: Int, elementsInLine: Int) { - for (i in 0 until length) { - val expr = this.getElementExpr(i) - - if (i == 0 && length >= elementsInLine || i != 0 && i % elementsInLine == 0) { - println() + if (length <= elementsInLine) { // one-line array + for (i in 0 until length) { + val expr = this.getElementExpr(i) + expr.accept(this@CgAbstractRenderer) + if (i != length - 1) { + print(", ") + } + } + } else { // multiline array + println() // line break after `int[] x = {` + withIndent { + for (i in 0 until length) { + val expr = this.getElementExpr(i) + expr.accept(this@CgAbstractRenderer) + + if (i == length - 1) { + println() + } else if (i % elementsInLine == elementsInLine - 1) { + println(",") + } else { + print(", ") + } + } } - expr.accept(this@CgAbstractRenderer) - if (i != length - 1) print(",") } } From 240c55d72202f8aff42ea1953d0bfe717cd5de80 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Fri, 3 Jun 2022 13:34:30 +0300 Subject: [PATCH 017/120] Useless throws in methods signature (#112) * Generate throws in method signature only if required --- .../constructor/builtin/MockitoBuiltins.kt | 8 ++ .../constructor/builtin/ReflectionBuiltins.kt | 15 +++- .../tree/CgCallableAccessManager.kt | 89 ++++++++++++++++--- .../constructor/tree/CgMethodConstructor.kt | 25 ++---- 4 files changed, 107 insertions(+), 30 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt index 5073b37aaa..1a2805b615 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt @@ -1,6 +1,7 @@ package org.utbot.framework.codegen.model.constructor.builtin import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.builtinMethodId import org.utbot.framework.plugin.api.util.builtinStaticMethodId @@ -15,6 +16,13 @@ import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.stringClassId +internal val mockitoBuiltins: Set + get() = setOf( + mockMethodId, whenMethodId, thenMethodId, thenReturnMethodId, + any, anyOfClass, anyByte, anyChar, anyShort, anyInt, anyLong, + anyFloat, anyDouble, anyBoolean, anyString + ) + internal val mockitoClassId = BuiltinClassId( name = "org.mockito.Mockito", canonicalName = "org.mockito.Mockito", diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt index 9516e9e186..dd5acb019f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt @@ -1,6 +1,7 @@ package org.utbot.framework.codegen.model.constructor.builtin import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId @@ -8,13 +9,25 @@ import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.voidClassId +import sun.misc.Unsafe import java.lang.reflect.AccessibleObject import java.lang.reflect.Field import java.lang.reflect.InvocationTargetException -import sun.misc.Unsafe // reflection methods ids +//TODO: these methods are called builtins, but actually are just [MethodId] +//may be fixed in https://github.com/UnitTestBot/UTBotJava/issues/138 + +internal val reflectionBuiltins: Set + get() = setOf( + setAccessible, invoke, newInstance, get, forName, + getDeclaredMethod, getDeclaredConstructor, allocateInstance, + getClass, getDeclaredField, isEnumConstant, getFieldName, + equals, getSuperclass, set, newArrayInstance, + setArrayElement, getArrayElement, getTargetException, + ) + internal val setAccessible = methodId( classId = AccessibleObject::class.id, name = "setAccessible", diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index 208b8b3459..4bd0e48791 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -1,13 +1,31 @@ package org.utbot.framework.codegen.model.constructor.tree +import kotlinx.collections.immutable.PersistentList +import org.utbot.framework.codegen.Junit5 +import org.utbot.framework.codegen.TestNg import org.utbot.framework.codegen.model.constructor.builtin.any import org.utbot.framework.codegen.model.constructor.builtin.anyOfClass +import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId +import org.utbot.framework.codegen.model.constructor.builtin.createArrayMethodId +import org.utbot.framework.codegen.model.constructor.builtin.createInstanceMethodId +import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId import org.utbot.framework.codegen.model.constructor.builtin.forName +import org.utbot.framework.codegen.model.constructor.builtin.getArrayLengthMethodId import org.utbot.framework.codegen.model.constructor.builtin.getDeclaredConstructor import org.utbot.framework.codegen.model.constructor.builtin.getDeclaredMethod +import org.utbot.framework.codegen.model.constructor.builtin.getEnumConstantByNameMethodId +import org.utbot.framework.codegen.model.constructor.builtin.getFieldValueMethodId +import org.utbot.framework.codegen.model.constructor.builtin.getStaticFieldValueMethodId +import org.utbot.framework.codegen.model.constructor.builtin.getTargetException +import org.utbot.framework.codegen.model.constructor.builtin.getUnsafeInstanceMethodId +import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId import org.utbot.framework.codegen.model.constructor.builtin.invoke +import org.utbot.framework.codegen.model.constructor.builtin.iterablesDeepEqualsMethodId +import org.utbot.framework.codegen.model.constructor.builtin.mapsDeepEqualsMethodId import org.utbot.framework.codegen.model.constructor.builtin.newInstance import org.utbot.framework.codegen.model.constructor.builtin.setAccessible +import org.utbot.framework.codegen.model.constructor.builtin.setFieldMethodId +import org.utbot.framework.codegen.model.constructor.builtin.setStaticFieldMethodId import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.CgComponents @@ -31,18 +49,20 @@ import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.isAccessibleFrom import org.utbot.framework.codegen.model.util.nullLiteral import org.utbot.framework.codegen.model.util.resolve +import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.util.exceptions import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.method import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId -import kotlinx.collections.immutable.PersistentList typealias Block = PersistentList @@ -99,25 +119,68 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA return methodCall } - // TODO: do not always use Throwable in the future, use more specific exceptions instead - private fun newMethodCall(id: MethodId) { - when { - id.isUtil -> { - requiredUtilMethods += id - addException(java.lang.Throwable::class.id) - } - else -> addException(java.lang.Throwable::class.id) + private fun newMethodCall(methodId: MethodId) { + if (methodId.isUtil) requiredUtilMethods += methodId + importIfNeeded(methodId) + + //Builtin methods does not have jClass, so [methodId.method] will crash on it, + //so we need to collect required exceptions manually from source codes + if (methodId is BuiltinMethodId) { + methodId.findExceptionTypes().forEach { addException(it) } + return + } + //If [InvocationTargetException] is thrown manually in test, we need + // to add "throws Throwable" and other exceptions are not required so on. + if (methodId == getTargetException) { + collectedExceptions.clear() + addException(Throwable::class.id) + return + } + + val methodIsUnderTestAndThrowsExplicitly = methodId == currentExecutable + && currentExecution?.result is UtExplicitlyThrownException + val frameworkSupportsAssertThrows = testFramework == Junit5 || testFramework == TestNg + + //If explicit exception is wrapped with assertThrows, + // no "throws" in test method signature is required. + if (methodIsUnderTestAndThrowsExplicitly && frameworkSupportsAssertThrows) { + return } - importIfNeeded(id) + + methodId.method.exceptionTypes.forEach { addException(it.id) } } - private fun newConstructorCall(id: ConstructorId) { - importIfNeeded(id.classId) - for (exception in id.exceptions) { + private fun newConstructorCall(constructorId: ConstructorId) { + importIfNeeded(constructorId.classId) + for (exception in constructorId.exceptions) { addException(exception) } } + private fun BuiltinMethodId.findExceptionTypes(): Set { + if (!this.isUtil) return emptySet() + + with(currentTestClass) { + return when (this@findExceptionTypes) { + getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id) + getStaticFieldValueMethodId, + getFieldValueMethodId, + setStaticFieldMethodId, + setFieldMethodId, + createInstanceMethodId, + getUnsafeInstanceMethodId -> setOf(Exception::class.id) + createArrayMethodId -> setOf(ClassNotFoundException::class.id) + deepEqualsMethodId, + arraysDeepEqualsMethodId, + iterablesDeepEqualsMethodId, + mapsDeepEqualsMethodId, + hasCustomEqualsMethodId, + getArrayLengthMethodId -> emptySet() + else -> error("Unknown util method $this") + } + } + } + private infix fun CgExpression?.canBeReceiverOf(executable: MethodId): Boolean = when { // TODO: rewrite by using CgMethodId, etc. diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index aaa9ab0516..7a4921e12a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -1134,7 +1134,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val varType = CgClassId( it.variableType, TypeParameters(listOf(typeParameter)), - isNullable=true, + isNullable = true, ) +CgDeclaration( varType, @@ -1638,21 +1638,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c * that may be thrown by these calls. */ private fun CgExecutableCall.intercepted() { - when (executableId) { - is MethodId -> { - if (executableId == invoke) { - this.wrapReflectiveCall() - } else { - +this - } - } - is ConstructorId -> { - if (executableId == newInstance) { - this.wrapReflectiveCall() - } else { - +this - } - } + val executableToWrap = when (executableId) { + is MethodId -> invoke + is ConstructorId -> newInstance + } + if (executableId == executableToWrap) { + this.wrapReflectiveCall() + } else { + +this } } } \ No newline at end of file From fdf3e1c30008feae7929eac31b4e1f5a772ddd4a Mon Sep 17 00:00:00 2001 From: Victoria <32179813+victoriafomina@users.noreply.github.com> Date: Sat, 4 Jun 2022 10:05:38 +0300 Subject: [PATCH 018/120] Add utbot-framework-api publications on GitHub Packages closes #142 (#147) --- .../workflows/publish-on-github-packages.yml | 80 +++++++++++++++++++ utbot-api/build.gradle | 17 ++++ utbot-core/build.gradle | 17 ++++ utbot-framework-api/build.gradle | 17 ++++ 4 files changed, 131 insertions(+) create mode 100644 .github/workflows/publish-on-github-packages.yml diff --git a/.github/workflows/publish-on-github-packages.yml b/.github/workflows/publish-on-github-packages.yml new file mode 100644 index 0000000000..23a1ba9811 --- /dev/null +++ b/.github/workflows/publish-on-github-packages.yml @@ -0,0 +1,80 @@ +name: "[M] Publish on GitHub Packages" + +on: workflow_dispatch + +jobs: + build_and_run_tests: + if: ${{ github.actor == 'korifey' || github.actor == 'denis-fokin' || github.actor == 'victoriafomina' || + github.actor == 'bissquit' }} + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + java-package: jdk+fx + cache: gradle + - uses: gradle/gradle-build-action@v2 + with: + gradle-version: 6.8 + + - name: "UTBot Java: build and run tests" + run: | + export KOTLIN_HOME="/usr" + gradle clean build --no-daemon + + - name: Upload utbot-framework logs + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: utbot_framework_logs + path: utbot-framework/logs/* + + - name: Upload utbot-framework tests report artifacts if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: utbot_framework_tests_report + path: utbot-framework/build/reports/tests/test/* + + publish_framework-api_and_dependencies: + needs: build_and_run_tests + runs-on: ubuntu-20.04 + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + java-package: jdk+fx + cache: gradle + - uses: gradle/gradle-build-action@v2 + with: + gradle-version: 6.8 + arguments: publish + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Set project version + run: + echo "VERSION="$(date +%Y).$(date +%-m)"" >> $GITHUB_ENV + + - name: "utbot-framework-api: build and run tests" + run: | + cd utbot-framework-api + gradle clean build --no-daemon -PsemVer=${{ env.VERSION }}-SNAPSHOT + + - name: "utbot-api: build" + run: | + cd utbot-api + gradle clean build --no-daemon -PsemVer=${{ env.VERSION }}-SNAPSHOT + + - name: "utbot-core: build" + run: | + cd utbot-core + gradle clean build --no-daemon -PsemVer=${{ env.VERSION }}-SNAPSHOT diff --git a/utbot-api/build.gradle b/utbot-api/build.gradle index fc50f68057..beed5b412f 100644 --- a/utbot-api/build.gradle +++ b/utbot-api/build.gradle @@ -1 +1,18 @@ +plugins { + id 'maven-publish' +} + apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" + +publishing { + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/UnitTestBot/UTBotJava" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} \ No newline at end of file diff --git a/utbot-core/build.gradle b/utbot-core/build.gradle index f70ab1014f..a56c02d7fd 100644 --- a/utbot-core/build.gradle +++ b/utbot-core/build.gradle @@ -1,6 +1,23 @@ +plugins { + id 'maven-publish' +} + apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" dependencies { implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.5.0' } + +publishing { + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/UnitTestBot/UTBotJava" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} diff --git a/utbot-framework-api/build.gradle b/utbot-framework-api/build.gradle index 96a133dcad..4e10c29487 100644 --- a/utbot-framework-api/build.gradle +++ b/utbot-framework-api/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'maven-publish' +} + apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" @@ -9,4 +13,17 @@ dependencies { // TODO do we really need apache commons? implementation group: 'org.apache.commons', name: 'commons-lang3', version: commons_lang_version implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version +} + +publishing { + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/UnitTestBot/UTBotJava" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } } \ No newline at end of file From 469a892c4de5567338328c5ece7c87a1a889ec14 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Mon, 6 Jun 2022 10:34:17 +0300 Subject: [PATCH 019/120] New fuzzing model providers: (#139) * New fuzzing model providers: * collection model provider creates some empty collections of set, list, map, etc. * array model provider creates empty and not-empty arrays * char to string model which combines constants into string * enum model providers creates values of enums * added extra primitive model providers: default values and values of wrappers * object model provider now can generate objects with another types as a parameter Bug fixes and workarounds: * UtAssembleModel fails to generate test with java.lang.String() * Several NPEs because of generating null values for unknown types * NullModelProvider generates models for the primitive parameter * New fuzzing model providers (test added) --- .../utbot/fuzzer/FuzzedMethodDescription.kt | 2 +- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 54 ++++-- .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 16 ++ .../fuzzer/providers/ArrayModelProvider.kt | 32 ++++ .../providers/CharToStringModelProvider.kt | 31 +++ .../providers/CollectionModelProvider.kt | 105 +++++++++++ .../fuzzer/providers/EnumModelProvider.kt | 24 +++ .../fuzzer/providers/NullModelProvider.kt | 9 +- .../fuzzer/providers/ObjectModelProvider.kt | 71 +++++-- .../PrimitiveDefaultsModelProvider.kt | 45 +++++ .../PrimitiveWrapperModelProvider.kt | 64 +++++++ .../providers/PrimitivesModelProvider.kt | 5 +- .../framework/plugin/api/ModelProviderTest.kt | 178 +++++++++++++++++- 13 files changed, 599 insertions(+), 37 deletions(-) create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt index 70535c1e06..5c85601a32 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -32,7 +32,7 @@ class FuzzedMethodDescription( } constructor(executableId: ExecutableId, concreteValues: Collection = emptyList()) : this( - executableId.name, + executableId.classId.simpleName + "." + executableId.name, executableId.returnType, executableId.parameters, concreteValues diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 75a57d160d..8b7a33a0e6 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -1,15 +1,19 @@ package org.utbot.fuzzer -import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.fuzzer.providers.ConstantsModelProvider import org.utbot.fuzzer.providers.ObjectModelProvider import org.utbot.fuzzer.providers.PrimitivesModelProvider import org.utbot.fuzzer.providers.StringConstantModelProvider import mu.KotlinLogging +import org.utbot.fuzzer.providers.ArrayModelProvider +import org.utbot.fuzzer.providers.CharToStringModelProvider +import org.utbot.fuzzer.providers.CollectionModelProvider +import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider +import org.utbot.fuzzer.providers.EnumModelProvider +import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider import java.util.concurrent.atomic.AtomicInteger -import java.util.function.ToIntFunction +import java.util.function.IntSupplier import kotlin.random.Random private val logger = KotlinLogging.logger {} @@ -24,21 +28,47 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi description.parameters.forEachIndexed { index, classId -> val models = values[index] if (models.isEmpty()) { - logger.warn { "There's no models provided classId=$classId. Null or default value will be provided" } - models.add(classId.defaultValueModel()) + logger.warn { "There's no models provided classId=$classId. No suitable values are generated for ${description.name}" } + return emptySequence() } } return CartesianProduct(values, Random(0L)).asSequence() } -fun defaultModelProviders(idGenerator: ToIntFunction = SimpleIdGenerator()): ModelProvider { - return ObjectModelProvider(idGenerator) - .with(ConstantsModelProvider) - .with(StringConstantModelProvider) - .with(PrimitivesModelProvider) +/** + * Creates a model provider from a list of default providers. + */ +fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { + return ModelProvider.of( + ObjectModelProvider(idGenerator), + CollectionModelProvider(idGenerator), + ArrayModelProvider(idGenerator), + EnumModelProvider, + ConstantsModelProvider, + StringConstantModelProvider, + CharToStringModelProvider, + PrimitivesModelProvider, + PrimitiveWrapperModelProvider, + ) } -private class SimpleIdGenerator : ToIntFunction { +/** + * Creates a model provider for [ObjectModelProvider] that generates values for object constructor. + */ +fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { + return ModelProvider.of( + CollectionModelProvider(idGenerator), + ArrayModelProvider(idGenerator), + EnumModelProvider, + StringConstantModelProvider, + CharToStringModelProvider, + ConstantsModelProvider, + PrimitiveDefaultsModelProvider, + PrimitiveWrapperModelProvider, + ) +} + +private class SimpleIdGenerator : IntSupplier { private val id = AtomicInteger() - override fun applyAsInt(value: ClassId?) = id.incrementAndGet() + override fun getAsInt() = id.incrementAndGet() } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt index 2f3013241c..9f1d494cf4 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -102,6 +102,18 @@ fun interface ModelProvider { fun of(vararg providers: ModelProvider): ModelProvider { return Combined(providers.toList()) } + + fun BiConsumer.consumeAll(indices: List, models: Sequence) { + models.forEach { model -> + indices.forEach { index -> + accept(index, model) + } + } + } + + fun BiConsumer.consumeAll(indices: List, models: List) { + consumeAll(indices, models.asSequence()) + } } /** @@ -114,4 +126,8 @@ fun interface ModelProvider { } } } +} + +inline fun ModelProvider.exceptIsInstance(): ModelProvider { + return except { it is T } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt new file mode 100644 index 0000000000..1ce6d00a06 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt @@ -0,0 +1,32 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.consumeAll +import java.util.function.BiConsumer +import java.util.function.IntSupplier + +class ArrayModelProvider( + private val idGenerator: IntSupplier +) : ModelProvider { + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + description.parametersMap + .asSequence() + .filter { (classId, _) -> classId.isArray } + .forEach { (arrayClassId, indices) -> + consumer.consumeAll(indices, listOf(0, 10).map { arraySize -> + UtArrayModel( + id = idGenerator.asInt, + arrayClassId, + length = arraySize, + arrayClassId.elementClassId!!.defaultValueModel(), + mutableMapOf() + ) + }) + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt new file mode 100644 index 0000000000..673d95adf6 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt @@ -0,0 +1,31 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import java.util.function.BiConsumer + +/** + * Collects all char constants and creates string with them. + */ +object CharToStringModelProvider : ModelProvider { + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + val indices = description.parametersMap[stringClassId] ?: return + if (indices.isNotEmpty()) { + val string = description.concreteValues.asSequence() + .filter { it.classId == charClassId } + .map { it.value } + .filterIsInstance() + .joinToString(separator = "") + if (string.isNotEmpty()) { + val model = UtPrimitiveModel(string) + indices.forEach { + consumer.accept(it, model) + } + } + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt new file mode 100644 index 0000000000..1e90016e85 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt @@ -0,0 +1,105 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +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.UtStatementModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.consumeAll +import java.util.function.BiConsumer +import java.util.function.IntSupplier + +/** + * Provides different collection for concrete classes. + * + * For example, ArrayList, LinkedList, Collections.singletonList can be passed to check + * if that parameter breaks anything. For example, in case method doesn't expect + * a non-modifiable collection and tries to add values. + */ +class CollectionModelProvider( + private val idGenerator: IntSupplier +) : ModelProvider { + + private val generators = mapOf( + java.util.List::class.java to ::createListModels, + java.util.Set::class.java to ::createSetModels, + java.util.Map::class.java to ::createMapModels, + java.util.Collection::class.java to ::createCollectionModels, + java.lang.Iterable::class.java to ::createCollectionModels, + java.util.Iterator::class.java to ::createIteratorModels, + ) + + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + description.parametersMap + .asSequence() + .forEach { (classId, indices) -> + generators[classId.jClass]?.let { createModels -> + consumer.consumeAll(indices, createModels()) + } + } + } + + private fun createListModels(): List { + return listOf( + java.util.List::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()), + java.util.List::class.java.createdBy(java.util.LinkedList::class.java.asConstructor()), + java.util.List::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyList", java.util.List::class.java)), + java.util.List::class.java.createdBy(java.util.Collections::class.java.methodCall("synchronizedList", java.util.List::class.java, params = listOf(java.util.List::class.java)), listOf( + java.util.List::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()) + )), + ) + } + + private fun createSetModels(): List { + return listOf( + java.util.Set::class.java.createdBy(java.util.HashSet::class.java.asConstructor()), + java.util.Set::class.java.createdBy(java.util.TreeSet::class.java.asConstructor()), + java.util.Set::class.java.createdBy(java.util.Collections::class.java.methodCall("emptySet", java.util.Set::class.java)) + ) + } + + private fun createMapModels(): List { + return listOf( + java.util.Map::class.java.createdBy(java.util.HashMap::class.java.asConstructor()), + java.util.Map::class.java.createdBy(java.util.TreeMap::class.java.asConstructor()), + java.util.Map::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyMap", java.util.Map::class.java)), + ) + } + + private fun createCollectionModels(): List { + return listOf( + java.util.Collection::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()), + java.util.Collection::class.java.createdBy(java.util.HashSet::class.java.asConstructor()), + java.util.Collection::class.java.createdBy(java.util.Collections::class.java.methodCall("emptySet", java.util.Set::class.java)), + ) + } + + private fun createIteratorModels(): List { + return listOf( + java.util.Iterator::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyIterator", java.util.Iterator::class.java)), + ) + } + + private fun Class<*>.asConstructor() = ConstructorId(id, emptyList()) + + private fun Class<*>.methodCall(methodName: String, returnType: Class<*>, params: List> = emptyList()) = MethodId(id, methodName, returnType.id, params.map { it.id }) + + private fun Class<*>.createdBy(init: ExecutableId, params: List = emptyList()): UtAssembleModel { + val instantiationChain = mutableListOf() + val genId = idGenerator.asInt + return UtAssembleModel( + genId, + id, + "${init.classId.name}${init.parameters}#" + genId.toString(16), + instantiationChain + ).apply { + instantiationChain += UtExecutableCallModel(null, init, params, this) + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt new file mode 100644 index 0000000000..b50ed9dcd7 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt @@ -0,0 +1,24 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.consumeAll +import java.util.function.BiConsumer + +object EnumModelProvider : ModelProvider { + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + description.parametersMap + .asSequence() + .filter { (classId, _) -> classId.isSubtypeOf(Enum::class.java.id) } + .forEach { (classId, indices) -> + consumer.consumeAll(indices, classId.jClass.enumConstants.filterIsInstance>().map { + UtEnumConstantModel(classId, it) + }) + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt index d2a1691ca1..9061efbede 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt @@ -13,11 +13,12 @@ import java.util.function.BiConsumer @Suppress("unused") // disabled until fuzzer breaks test with null/nonnull annotations object NullModelProvider : ModelProvider { override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parameters + description.parametersMap .asSequence() - .filter { classId -> classId.isRefType } - .forEachIndexed { index, classId -> - consumer.accept(index, UtNullModel(classId)) + .filter { (classId, _) -> classId.isRefType } + .forEach { (classId, indices) -> + val model = UtNullModel(classId) + indices.forEach { consumer.accept(it, model) } } } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 04b361f28f..6efae73e22 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -7,39 +7,72 @@ import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.exceptIsInstance import org.utbot.fuzzer.fuzz +import org.utbot.fuzzer.objectModelProviders import java.lang.reflect.Constructor import java.lang.reflect.Modifier -import java.lang.reflect.Parameter import java.util.function.BiConsumer -import java.util.function.ToIntFunction +import java.util.function.IntSupplier /** * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. */ -class ObjectModelProvider( - private val idGenerator: ToIntFunction -) : ModelProvider { +class ObjectModelProvider : ModelProvider { - var modelProvider: ModelProvider = ModelProvider.of(ConstantsModelProvider, StringConstantModelProvider, PrimitivesModelProvider) + var modelProvider: ModelProvider + + private val idGenerator: IntSupplier + private val recursion: Int + private val limit: Int + + constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE) + + constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1) + + private constructor(idGenerator: IntSupplier, limit: Int, recursion: Int) { + this.idGenerator = idGenerator + this.recursion = recursion + this.limit = limit + this.modelProvider = objectModelProviders(idGenerator) + } override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { val assembleModels = with(description) { parameters.asSequence() + .filterNot { it == stringClassId || it.isPrimitiveWrapper } .flatMap { classId -> collectConstructors(classId) { javaConstructor -> - isPublic(javaConstructor) && javaConstructor.parameters.all(Companion::isPrimitiveOrString) - } + isPublic(javaConstructor) + }.sortedWith( + primitiveParameterizedConstructorsFirstAndThenByParameterCount + ).take(limit) } - .associateWith { - fuzzParameters(it, modelProvider) + .associateWith { constructorId -> + val modelProviderWithoutRecursion = modelProvider.exceptIsInstance() + fuzzParameters( + constructorId, + if (recursion > 0) { + ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion) + } else { + modelProviderWithoutRecursion.withFallback(NullModelProvider) + } + ) } .flatMap { (constructorId, fuzzedParameters) -> - fuzzedParameters.map { params -> - assembleModel(idGenerator.applyAsInt(constructorId.classId), constructorId, params) + if (constructorId.parameters.isEmpty()) { + sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) + } + else { + fuzzedParameters.map { params -> + assembleModel(idGenerator.asInt, constructorId, params) + } } } } @@ -64,11 +97,6 @@ class ObjectModelProvider( return javaConstructor.modifiers and Modifier.PUBLIC != 0 } - private fun isPrimitiveOrString(parameter: Parameter): Boolean { - val parameterType = parameter.type - return parameterType.isPrimitive || String::class.java == parameterType - } - private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence> { val fuzzedMethod = FuzzedMethodDescription( executableId = constructorId, @@ -88,5 +116,14 @@ class ObjectModelProvider( instantiationChain += UtExecutableCallModel(null, constructorId, params, this) } } + + private val primitiveParameterizedConstructorsFirstAndThenByParameterCount = + compareByDescending { constructorId -> + constructorId.parameters.all { classId -> + classId.isPrimitive || classId == stringClassId + } + }.thenComparingInt { constructorId -> + constructorId.parameters.size + } } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt new file mode 100644 index 0000000000..a5983ed367 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt @@ -0,0 +1,45 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import java.util.function.BiConsumer + +/** + * Provides default values for primitive types. + */ +object PrimitiveDefaultsModelProvider : ModelProvider { + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + description.parametersMap.forEach { (classId, parameterIndices) -> + valueOf(classId)?.let { model -> + parameterIndices.forEach { index -> + consumer.accept(index, model) + } + } + } + } + + fun valueOf(classId: ClassId): UtPrimitiveModel? = when (classId) { + booleanClassId -> UtPrimitiveModel(false) + byteClassId -> UtPrimitiveModel(0.toByte()) + charClassId -> UtPrimitiveModel('\u0000') + shortClassId -> UtPrimitiveModel(0.toShort()) + intClassId -> UtPrimitiveModel(0) + longClassId -> UtPrimitiveModel(0L) + floatClassId -> UtPrimitiveModel(0.0f) + doubleClassId -> UtPrimitiveModel(0.0) + stringClassId -> UtPrimitiveModel("") + else -> null + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt new file mode 100644 index 0000000000..e54cf8d942 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt @@ -0,0 +1,64 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.plugin.api.util.wrapperByPrimitive +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.consumeAll +import java.util.function.BiConsumer + +object PrimitiveWrapperModelProvider: ModelProvider { + + private val constantModels = ModelProvider.of( + PrimitiveDefaultsModelProvider, + ConstantsModelProvider, + StringConstantModelProvider + ) + + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + val primitiveWrapperTypesAsPrimitiveTypes = description.parametersMap + .keys + .asSequence() + .filter { + it == stringClassId || it.isPrimitiveWrapper + } + .mapNotNull { classId -> + when { + classId == stringClassId -> stringClassId + classId.isPrimitiveWrapper -> primitiveByWrapper[classId] + else -> null + } + }.toList() + + if (primitiveWrapperTypesAsPrimitiveTypes.isEmpty()) { + return + } + + val constants = mutableMapOf>() + constantModels.generate(FuzzedMethodDescription( + name = this::class.simpleName + " constant generation ", + returnType = voidClassId, + parameters = primitiveWrapperTypesAsPrimitiveTypes, + concreteValues = description.concreteValues + )) { index, value -> + val primitiveWrapper = wrapperByPrimitive[primitiveWrapperTypesAsPrimitiveTypes[index]] + if (primitiveWrapper != null) { + constants.computeIfAbsent(primitiveWrapper) { mutableListOf() }.add(value) + } + } + + description.parametersMap + .asSequence() + .filter { (classId, _) -> classId == stringClassId || classId.isPrimitiveWrapper } + .forEach { (classId, indices) -> + constants[classId]?.let { models -> + consumer.consumeAll(indices, models) + } + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt index e5c57ea27a..082b5c18eb 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt @@ -8,7 +8,7 @@ import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer /** - * Creates models of primitives from the method parameters list. + * Produces bound values for primitive types. */ object PrimitivesModelProvider : ModelProvider { override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { @@ -70,6 +70,9 @@ object PrimitivesModelProvider : ModelProvider { ) stringClassId -> listOf( UtPrimitiveModel(""), + UtPrimitiveModel(" "), + UtPrimitiveModel("string"), + UtPrimitiveModel("\n\t\r"), ) else -> listOf() } diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index 17eb4fce0a..e86c26b7b2 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -23,6 +23,12 @@ import org.utbot.fuzzer.providers.PrimitivesModelProvider import org.utbot.fuzzer.providers.StringConstantModelProvider import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.primitiveWrappers +import org.utbot.framework.plugin.api.util.voidWrapperClassId +import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.providers.EnumModelProvider +import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import java.util.Date class ModelProviderTest { @@ -181,7 +187,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( ObjectModelProvider { 0 }.apply { - modelProvider = ModelProvider.of(ConstantsModelProvider, StringConstantModelProvider) + modelProvider = ModelProvider.of(PrimitiveDefaultsModelProvider) }, parameters = listOf(classId) ) @@ -212,7 +218,8 @@ class ModelProviderTest { parameters = listOf(classId) ) - assertEquals(0, models.size) + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) } } @@ -259,6 +266,170 @@ class ModelProviderTest { assertEquals(UtPrimitiveModel(-123), result[0]!![0]) } + @Test + fun `test collection model can produce basic values with assembled model`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + defaultModelProviders { 0 }, + parameters = listOf(java.util.List::class.java.id) + ) + + assertEquals(1, result.size) + } + } + + @Test + fun `test enum model provider`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect(EnumModelProvider, parameters = listOf(OneTwoThree::class.java.id)) + assertEquals(1, result.size) + assertEquals(3, result[0]!!.size) + OneTwoThree.values().forEachIndexed { index: Int, value -> + assertEquals(UtEnumConstantModel(OneTwoThree::class.java.id, value), result[0]!![index]) + } + } + } + + @Test + fun `test string value generates only primitive models`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect(defaultModelProviders { 0 }, parameters = listOf(stringClassId)) + assertEquals(1, result.size) + result[0]!!.forEach { + assertInstanceOf(UtPrimitiveModel::class.java, it) + assertEquals(stringClassId, it.classId) + } + } + } + + @Test + fun `test wrapper primitives generate only primitive models`() { + withUtContext(UtContext(this::class.java.classLoader)) { + primitiveWrappers.asSequence().filterNot { it == voidWrapperClassId }.forEach { classId -> + val result = collect(defaultModelProviders { 0 }, parameters = listOf(classId)) + assertEquals(1, result.size) + result[0]!!.forEach { + assertInstanceOf(UtPrimitiveModel::class.java, it) + val expectPrimitiveBecauseItShouldBeGeneratedByDefaultProviders = primitiveByWrapper[classId] + assertEquals(expectPrimitiveBecauseItShouldBeGeneratedByDefaultProviders, it.classId) + } + } + } + } + + @Test + fun `test at least one string is created if characters exist as constants`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + defaultModelProviders { 0 }, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(charClassId, 'a'), + FuzzedConcreteValue(charClassId, 'b'), + FuzzedConcreteValue(charClassId, 'c'), + ) + ) + assertEquals(1, result.size) + assertTrue(result[0]!!.any { + it is UtPrimitiveModel && it.value == "abc" + }) + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") + fun `test complex object is constructed and it is not null`() { + class A { + constructor(some: Any) + } + + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect(ObjectModelProvider { 0 }, parameters = listOf(A::class.java.id)) + assertEquals(1, result.size) + assertEquals(1, result[0]!!.size) + assertInstanceOf(UtAssembleModel::class.java, result[0]!![0]) + assertEquals(A::class.java.id, result[0]!![0].classId) + (result[0]!![0] as UtAssembleModel).instantiationChain.forEach { + assertTrue(it is UtExecutableCallModel) + assertEquals(1, (it as UtExecutableCallModel).params.size) + val objectParamInConstructor = it.params[0] + assertInstanceOf(UtAssembleModel::class.java, objectParamInConstructor) + val innerAssembledModel = objectParamInConstructor as UtAssembleModel + assertEquals(Any::class.java.id, innerAssembledModel.classId) + assertEquals(1, innerAssembledModel.instantiationChain.size) + val objectCreation = innerAssembledModel.instantiationChain.first() as UtExecutableCallModel + assertEquals(0, objectCreation.params.size) + assertInstanceOf(ConstructorId::class.java, objectCreation.executable) + } + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") + fun `test recursive constructor calls and can pass null into inner if no other values exist`() { + class MyA { + constructor(some: MyA?) + } + + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect(ObjectModelProvider { 0 }, parameters = listOf(MyA::class.java.id)) + assertEquals(1, result.size) + assertEquals(1, result[0]!!.size) + val outerModel = result[0]!![0] as UtAssembleModel + outerModel.instantiationChain.forEach { + val constructorParameters = (it as UtExecutableCallModel).params + assertEquals(1, constructorParameters.size) + val innerModel = (constructorParameters[0] as UtAssembleModel) + assertEquals(MyA::class.java.id, innerModel.classId) + assertEquals(1, innerModel.instantiationChain.size) + val innerConstructorParameters = innerModel.instantiationChain[0] as UtExecutableCallModel + assertEquals(1, innerConstructorParameters.params.size) + assertInstanceOf(UtNullModel::class.java, innerConstructorParameters.params[0]) + } + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") + fun `test complex object is constructed with the simplest inner object constructor`() { + + class Inner { + constructor(some: Inner?) + + constructor(some: Inner?, other: Any) + + // this constructor should be chosen + constructor(int: Int, double: Double) + + constructor(other: Any, int: Int) + + constructor(some: Inner?, other: Double) + } + + class Outer { + constructor(inner: Inner) + } + + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect(ObjectModelProvider { 0 }, parameters = listOf(Outer::class.java.id)) + assertEquals(1, result.size) + assertEquals(1, result[0]!!.size) + val outerModel = result[0]!![0] as UtAssembleModel + outerModel.instantiationChain.forEach { + val constructorParameters = (it as UtExecutableCallModel).params + assertEquals(1, constructorParameters.size) + val innerModel = (constructorParameters[0] as UtAssembleModel) + assertEquals(Inner::class.java.id, innerModel.classId) + assertEquals(1, innerModel.instantiationChain.size) + val innerConstructorParameters = innerModel.instantiationChain[0] as UtExecutableCallModel + assertEquals(2, innerConstructorParameters.params.size) + assertTrue(innerConstructorParameters.params.all { param -> param is UtPrimitiveModel }) + assertEquals(intClassId, innerConstructorParameters.params[0].classId) + assertEquals(doubleClassId, innerConstructorParameters.params[1].classId) + } + } + } + private fun collect( modelProvider: ModelProvider, name: String = "testMethod", @@ -273,4 +444,7 @@ class ModelProviderTest { } } + private enum class OneTwoThree { + ONE, TWO, THREE + } } \ No newline at end of file From c13be0631744c3a7fa4f725edf006fb058ce3ffd Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 6 Jun 2022 11:05:20 +0300 Subject: [PATCH 020/120] Exception types specified in UtilMethods --- .../codegen/model/visitor/UtilMethods.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 3c28c799f0..3e3ce86325 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -91,7 +91,7 @@ fun getStaticFieldValue(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getStaticFieldValue(Class clazz, String fieldName) throws Exception { + private static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { java.lang.reflect.Field field; Class originClass = clazz; do { @@ -141,7 +141,7 @@ fun getFieldValue(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getFieldValue(Object obj, String fieldName) throws Exception { + private static Object getFieldValue(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException { Class clazz = obj.getClass(); java.lang.reflect.Field field; do { @@ -191,7 +191,7 @@ fun setStaticField(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws Exception { + private static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { java.lang.reflect.Field field; do { @@ -242,7 +242,7 @@ fun setField(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static void setField(Object object, String fieldName, Object fieldValue) throws Exception { + private static void setField(Object object, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = object.getClass(); java.lang.reflect.Field field; @@ -327,7 +327,8 @@ fun createInstance(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object createInstance(String className) throws Exception { + private static Object createInstance(String className) + throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName(className); return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class.class) .invoke(getUnsafeInstance(), clazz); @@ -349,7 +350,7 @@ fun getUnsafeInstance(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getUnsafeInstance() throws Exception { + private static Object getUnsafeInstance() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { java.lang.reflect.Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); f.setAccessible(true); return f.get(null); @@ -766,7 +767,7 @@ private fun ClassId.regularImportsByUtilMethod(id: MethodId, codegenLanguage: Co val fieldClassId = Field::class.id return when (id) { getUnsafeInstanceMethodId -> listOf(fieldClassId) - createInstanceMethodId -> listOf() + createInstanceMethodId -> listOf(java.lang.reflect.InvocationTargetException::class.id) createArrayMethodId -> listOf(java.lang.reflect.Array::class.id) setFieldMethodId -> listOf(fieldClassId, Modifier::class.id) setStaticFieldMethodId -> listOf(fieldClassId, Modifier::class.id) From 41d3f0418ce984d08455ac7c2cf2baf51e04aa6e Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Sat, 4 Jun 2022 15:06:30 +0300 Subject: [PATCH 021/120] Register throw statements of the wrapper's methods in the global graph --- .../overrides/collections/UtArrayList.java | 3 +- .../overrides/collections/UtLinkedList.java | 3 +- .../utbot/engine/InterProceduralUnitGraph.kt | 122 ++++++++++++++++-- .../strategies/DistanceStatistics.kt | 6 +- .../strategies/EdgeVisitCountingStatistics.kt | 2 +- .../examples/strings/StringExamplesTest.kt | 4 +- 6 files changed, 121 insertions(+), 19 deletions(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java index 365c6d52c3..a88597ced5 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java @@ -82,7 +82,8 @@ void preconditionCheck() { int size = elementData.end; assume(elementData.begin == 0); - assume(size >= 0 & size <= MAX_LIST_SIZE); + assume(size >= 0); + assume(size <= MAX_LIST_SIZE); visit(this); } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java index 1e6be282dc..0d62cee67a 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java @@ -78,7 +78,8 @@ private void preconditionCheck() { parameter(elementData); parameter(elementData.storage); assume(elementData.begin == 0); - assume(elementData.end >= 0 & elementData.end <= MAX_LIST_SIZE); + assume(elementData.end >= 0); + assume(elementData.end <= MAX_LIST_SIZE); visit(this); } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt index 7485abe927..dd2f550852 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt @@ -7,6 +7,7 @@ import soot.jimple.Stmt import soot.jimple.SwitchStmt import soot.jimple.internal.JReturnStmt import soot.jimple.internal.JReturnVoidStmt +import soot.jimple.internal.JThrowStmt import soot.toolkits.graph.ExceptionalUnitGraph private const val EXCEPTION_DECISION_SHIFT = 100000 @@ -22,7 +23,16 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { private var attachAllowed = true val graphs = mutableListOf(graph) private val statistics = mutableListOf() - private val methods = mutableListOf(graph.body.method) + // All the methods in the InterProceduralUnitGraph + private val methods: MutableSet = mutableSetOf(graph.body.method) + // Methods with registered edges + private val registeredMethods: MutableSet = mutableSetOf(graph.body.method) + + // We use this field for exceptional statements of the methods from the [registeredMethods] map. + // Points of interest are statements of the graph that must be covered during the exploration. + // Therefore, there is no need to put here statements from registered methods since + // we try to cover all of their edges. + private val methodToUncoveredThrowStatements: MutableMap> = mutableMapOf() private val unexceptionalSuccsCache = mutableMapOf>>() private val exceptionalSuccsCache = mutableMapOf>>() @@ -41,9 +51,10 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { private val unexceptionalSuccs: MutableMap> = graph.unexceptionalSuccs.toMutableMap() private val stmtToGraph: MutableMap = stmts.associateWithTo(mutableMapOf()) { graph } + private val edgeToGraph: MutableMap = graph.edges.associateWithTo(mutableMapOf()) { graph } - val registeredEdgesCount: MutableMap = graph.outgoingEdgesCount - val outgoingEdgesCount: MutableMap = graph.outgoingEdgesCount + private val registeredEdgesCount: MutableMap = graph.outgoingEdgesCount + private val outgoingEdgesCount: MutableMap = graph.outgoingEdgesCount fun method(stmt: Stmt): SootMethod = stmtToGraph[stmt]?.body?.method ?: error("$stmt not in graph.") @@ -79,42 +90,65 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { private val ExceptionalUnitGraph.outgoingEdgesCount: MutableMap get() = stmts.associateWithTo(mutableMapOf()) { succ(it).count() } + /** + * Returns a method that the [edge] is belongs to. If the [edge] does not + * have such method, null will be returned. + * + * Note: for example, implicit edges and edges produced by an invocation + * does not have a method they could belong to. + */ + private fun methodByEdge(edge: Edge): SootMethod? = edgeToGraph[edge]?.body?.method /** - * join new graph to stmt. - * @param registerEdges - if true than edges + * Joins a new [graph] to the [stmt]. Depending on the [registerEdges], edges of the [graph] + * might be either registered in the global graph or not. + * + * If they are registered, path selector tries to cover all edges from the [graph]. */ fun join(stmt: Stmt, graph: ExceptionalUnitGraph, registerEdges: Boolean) { attachAllowed = false + val invokeEdge = Edge(stmt, graph.head, CALL_DECISION_NUM) - val alreadyJoined = graph.body.method in methods + val method = graph.body.method + val alreadyJoined = method in methods + if (!alreadyJoined) { graphs += graph - methods += graph.body.method + methods += method traps += graph.traps exceptionalSuccs += graph.exceptionalSuccs unexceptionalSuccs += graph.unexceptionalSuccs - val joinedStmts = graph.stmts - stmts += joinedStmts - joinedStmts.forEach { stmtToGraph[it] = graph } + graph.edges.forEach { edgeToGraph[it] = graph } + + val joinedStmts = graph.stmts.onEach { stmtToGraph[it] = graph } outgoingEdgesCount += graph.outgoingEdgesCount + stmts += joinedStmts + if (registerEdges) { + registeredMethods += method registeredEdgesCount += graph.outgoingEdgesCount registeredEdges += graph.edges registeredEdgesCount.computeIfPresent(stmt) { _, value -> value + 1 } } + + // it is important to have this method call after we register the given method + // because we find uncoveredPoints for unregistered methods only + method.uncoveredThrowStatements() } + invokeSuccessors.compute(stmt) { _, value -> value?.apply { add(graph.head) } ?: mutableSetOf(graph.head) } + registeredEdges += invokeEdge outgoingEdgesCount.computeIfPresent(stmt) { _, value -> value + 1 } + statistics.forEach { it.onJoin(stmt, graph, registerEdges) } @@ -129,9 +163,15 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { coveredOutgoingEdges.putIfAbsent(executionState.stmt, 0) for (edge in executionState.edges) { + markAsCoveredStmt(edge.src) + markAsCoveredStmt(edge.dst) + when (edge) { in implicitEdges -> coveredImplicitEdges += edge - !in registeredEdges -> coveredOutgoingEdges.putIfAbsent(edge.src, 0) + !in registeredEdges -> { + coveredOutgoingEdges.putIfAbsent(edge.src, 0) + coveredEdges += edge + } !in coveredEdges -> { coveredOutgoingEdges.compute(edge.src) { _, value -> (value ?: 0) + 1 } coveredEdges += edge @@ -143,6 +183,43 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { } } + /** + * Returns uncovered throw statements for [this] method. + */ + private fun SootMethod.uncoveredThrowStatements(): Set { + val throwStatements = methodToUncoveredThrowStatements[this] + + if (throwStatements != null) { + return throwStatements + } + + // We don't have to additionally specify throw statements + // since for the registeredMethods we try to cover all of their edges. + if (this in registeredMethods) { + return emptySet() + } + + // We don't want to cover all the exceptions in not overridden library classes + if (declaringClass.isLibraryClass && !declaringClass.isOverridden) { + return emptySet() + } + + val uncoveredThrowStatements = methodToUncoveredThrowStatements.getOrPut(this) { mutableSetOf() } + + if (!canRetrieveBody()) { + return uncoveredThrowStatements + } + + uncoveredThrowStatements += jimpleBody().units.filterIsInstance() + + return uncoveredThrowStatements + } + + private fun markAsCoveredStmt(stmt: Stmt) { + val method = methodByStmt(stmt) + methodToUncoveredThrowStatements[method]?.remove(stmt) + } + /** * register new implicit edge, that is not represented in graph */ @@ -215,11 +292,34 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { (edge in coveredEdges || edge !in registeredEdges) } + /** + * If the [edge] does not have associated method, returns a result of [isCovered] call. + * Otherwise, there are several cases: + * * If the [edge] in [coveredEdges], returns true; + * * If the [edge] belongs to a method of some overridden class and there is + * uncovered throw statements in it, returns false; + * * In all other cases, returns whether the [edge] absent in [registeredEdges]. + */ + fun isCoveredWithAllThrowStatements(edge: Edge): Boolean { + val method = methodByEdge(edge) ?: return isCovered(edge) + + if (edge in coveredEdges) { + return true + } + + if (method.declaringClass.isOverridden && method.uncoveredThrowStatements().isNotEmpty()) { + return false + } + + return edge !in registeredEdges + } /** * @return true if stmt has more than one outgoing edge */ fun isFork(stmt: Stmt): Boolean = (outgoingEdgesCount[stmt] ?: 0) > 1 + + private fun methodByStmt(stmt: Stmt) = stmtToGraph.getValue(stmt).body.method } private fun ExceptionalUnitGraph.succ(stmt: Stmt): Sequence = exceptionalSucc(stmt) + unexceptionalSucc(stmt) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt index e3981c6546..37e2647838 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt @@ -35,11 +35,11 @@ class DistanceStatistics( graph.stmts.associateWithTo(mutableMapOf()) { 0 } /** - * Drops executionState if all the edges on path are covered and - * there is no reachable uncovered statement + * Drops executionState if all the edges on path are covered (with respect to uncovered + * throw statements of the methods they belong to) and there is no reachable and uncovered statement. */ override fun shouldDrop(state: ExecutionState) = - state.edges.all { graph.isCovered(it) } && distanceToUncovered(state) == Int.MAX_VALUE + state.edges.all { graph.isCoveredWithAllThrowStatements(it) } && distanceToUncovered(state) == Int.MAX_VALUE fun isCovered(edge: Edge): Boolean = graph.isCovered(edge) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt index 25384d60fc..ad6aa1f5e2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt @@ -31,7 +31,7 @@ class EdgeVisitCountingStatistics( * - all statements are already covered and execution is complete */ override fun shouldDrop(state: ExecutionState): Boolean { - return state.edges.all { graph.isCovered(it) } && state.isComplete() + return state.edges.all { graph.isCoveredWithAllThrowStatements(it) } && state.isComplete() } /** diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt index de59143346..4c9283a278 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt @@ -328,7 +328,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( fun testSubstringWithEndIndex() { checkWithException( StringExamples::substringWithEndIndex, - between(6..8), + ignoreExecutionsNumber, { s, _, _, r -> s == null && r.isException() }, { s, b, e, r -> s != null && b < 0 || e > s.length || b > e && r.isException() }, { s, b, e, r -> r.getOrThrow() == s.substring(b, e) && s.substring(b, e) != "password" }, @@ -346,7 +346,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( fun testSubstringWithEndIndexNotEqual() { checkWithException( StringExamples::substringWithEndIndexNotEqual, - between(3..4), + ignoreExecutionsNumber, { s, _, r -> s == null && r.isException() }, { s, e, r -> s != null && e < 1 || e > s.length && r.isException() }, { s, e, r -> s != null && r.getOrThrow() == s.substring(1, e) }, From 7e521b9c17f68e4e311b730df68e99461ef9e83d Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 6 Jun 2022 15:51:58 +0300 Subject: [PATCH 022/120] Corrected order of methods under test --- .../org/utbot/intellij/plugin/generator/CodeGenerator.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt index 4c4ef7befe..a1576752b0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt @@ -57,7 +57,10 @@ class CodeGenerator( fun findMethodsInClassMatchingSelected(clazz: KClass<*>, selectedMethods: List): List> { val selectedSignatures = selectedMethods.map { it.signature() } - return clazz.functions.filter { it.signature().normalized() in selectedSignatures }.map { UtMethod(it, clazz) } + return clazz.functions + .sortedWith(compareBy { selectedSignatures.indexOf(it.signature()) }) + .filter { it.signature().normalized() in selectedSignatures } + .map { UtMethod(it, clazz) } } fun findMethodParams(clazz: KClass<*>, methods: List): Map, List> { From cab4799cb7ff61fbd00571125d6e35a1c23857d5 Mon Sep 17 00:00:00 2001 From: Nikita Stroganov <54814796+IdeaSeeker@users.noreply.github.com> Date: Tue, 7 Jun 2022 12:22:29 +0300 Subject: [PATCH 023/120] Fix wrong test resources directory in Android Studio (#78) * #70 Fix wrong test resources directory in Android Studio * #70 Change the order of suitable test folders * #70 Fix after review Co-authored-by: Nikita Stroganov --- .../plugin/generator/TestGenerator.kt | 4 +-- .../intellij/plugin/sarif/SarifReportIdea.kt | 2 +- .../plugin/ui/GenerateTestsDialogWindow.kt | 2 +- .../intellij/plugin/ui/utils/ModuleUtils.kt | 29 +++++++++++++------ .../intellij/plugin/ui/utils/RootUtils.kt | 8 ++--- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt index 581c27f24c..78f0f2a793 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt @@ -95,7 +95,7 @@ object TestGenerator { } private fun mergeSarifReports(model: GenerateTestsModel) { - val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath() + val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) val sarifReports = sarifReportsPath.toFile() .walkTopDown() .filter { it.extension == "sarif" } @@ -294,7 +294,7 @@ object TestGenerator { } private fun saveTestsReport(testsCodeWithTestReport: TestsCodeWithTestReport, model: GenerateTestsModel) { - val testResourcesDirPath = model.testModule.getOrCreateTestResourcesPath() + val testResourcesDirPath = model.testModule.getOrCreateTestResourcesPath(model.testSourceRoot) require(testResourcesDirPath.exists()) { "Test resources directory $testResourcesDirPath does not exist" diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt index d18326a6a8..3b26f89bff 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt @@ -21,7 +21,7 @@ object SarifReportIdea { ) { // building the path to the report file val classFqn = testCases.firstOrNull()?.method?.clazz?.qualifiedName ?: return - val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath() + val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}-utbot.sarif") // creating report related directory diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 72ec516f20..f8971422e2 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -575,7 +575,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } private fun configureStaticMocking() { - val testResourcesUrl = model.testModule.getOrCreateTestResourcesPath() + val testResourcesUrl = model.testModule.getOrCreateTestResourcesPath(model.testSourceRoot) configureMockitoResources(testResourcesUrl) val staticsMockingValue = staticsMocking.item diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt index 33bd3c1cdb..40b335e747 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt @@ -64,17 +64,16 @@ fun Module.suitableTestSourceFolders(): List = * * If no roots exist, our suggestion is a folder named "resources" in the entry root. */ -fun Module.getOrCreateTestResourcesPath(): Path { - val testResourcesUrl = getOrCreateTestResourcesUrl(this) +fun Module.getOrCreateTestResourcesPath(testSourceRoot: VirtualFile?): Path { + val testResourcesUrl = getOrCreateTestResourcesUrl(this, testSourceRoot) return VfsUtilCore.urlToPath(testResourcesUrl).toPath() } /** * Gets a path to Sarif reports directory or creates it. - * */ -fun Module.getOrCreateSarifReportsPath(): Path { - val testResourcesPath = this.getOrCreateTestResourcesPath() +fun Module.getOrCreateSarifReportsPath(testSourceRoot: VirtualFile?): Path { + val testResourcesPath = this.getOrCreateTestResourcesPath(testSourceRoot) return "$testResourcesPath/sarif/".toPath() } @@ -137,21 +136,33 @@ private fun Module.suitableTestSourceFolders(codegenLanguage: CodegenLanguage): return sourceFolders .filterNot { it.isForGeneratedSources() } - .filter { folder -> folder.rootType == codegenLanguage.testRootType() } + .filter { it.rootType == codegenLanguage.testRootType() } + // Heuristics: User is more likely to choose the shorter path + .sortedBy { it.url.length } } private const val resourcesSuffix = "/resources" -private fun getOrCreateTestResourcesUrl(module: Module): String { +private fun getOrCreateTestResourcesUrl(module: Module, testSourceRoot: VirtualFile?): String { val moduleInstance = ModuleRootManager.getInstance(module) val sourceFolders = moduleInstance.contentEntries.flatMap { it.sourceFolders.toList() } - val testResourcesFolder = sourceFolders.firstOrNull { it.rootType in testResourceRootTypes } + val testResourcesFolder = sourceFolders + .filter { sourceFolder -> + sourceFolder.rootType in testResourceRootTypes && !sourceFolder.isForGeneratedSources() + } + // taking the source folder that has the maximum common prefix + // with `testSourceRoot`, which was selected by the user + .maxBy { sourceFolder -> + val sourceFolderPath = sourceFolder.file?.path ?: "" + val testSourceRootPath = testSourceRoot?.path ?: "" + sourceFolderPath.commonPrefixWith(testSourceRootPath).length + } if (testResourcesFolder != null) { return testResourcesFolder.url } - val testFolder = sourceFolders.firstOrNull { f -> f.rootType in testSourceRootTypes } + val testFolder = sourceFolders.firstOrNull { it.rootType in testSourceRootTypes } val contentEntry = testFolder?.contentEntry ?: moduleInstance.contentEntries.first() val parentFolderUrl = testFolder?.let { getParentPath(testFolder.url) } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt index d14d770e4c..44d8b36876 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt @@ -13,9 +13,9 @@ import org.jetbrains.kotlin.config.TestResourceKotlinRootType import org.jetbrains.kotlin.config.TestSourceKotlinRootType val sourceRootTypes: Set> = setOf(JavaSourceRootType.SOURCE, SourceKotlinRootType) -val testSourceRootTypes: Set> = setOf(JavaSourceRootType.TEST_SOURCE, TestSourceKotlinRootType) -val resourceRootTypes: Set> = setOf(JavaResourceRootType.RESOURCE, ResourceKotlinRootType) -val testResourceRootTypes: Set> = setOf(JavaResourceRootType.TEST_RESOURCE, TestResourceKotlinRootType) +val testSourceRootTypes: Set> = setOf(JavaSourceRootType.TEST_SOURCE, TestSourceKotlinRootType) +val resourceRootTypes: Set> = setOf(JavaResourceRootType.RESOURCE, ResourceKotlinRootType) +val testResourceRootTypes: Set> = setOf(JavaResourceRootType.TEST_RESOURCE, TestResourceKotlinRootType) /** * Defines test root type for selected codegen language. @@ -43,4 +43,4 @@ fun SourceFolder.isForGeneratedSources(): Boolean { val resourceProperties = jpsElement.getProperties(resourceRootTypes + testResourceRootTypes) return properties?.isForGeneratedSources == true && resourceProperties?.isForGeneratedSources == true -} \ No newline at end of file +} From d1bb082ba5460bd5dc28081c7d38b1e1cba3ddd7 Mon Sep 17 00:00:00 2001 From: Denis Fokin Date: Tue, 7 Jun 2022 17:58:24 +0300 Subject: [PATCH 024/120] Test plan template --- .github/ISSUE_TEMPLATE/test_request.md | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/test_request.md diff --git a/.github/ISSUE_TEMPLATE/test_request.md b/.github/ISSUE_TEMPLATE/test_request.md new file mode 100644 index 0000000000..2a919e0bc3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test_request.md @@ -0,0 +1,55 @@ +--- +name: Manual testing plan +about: Checklist of testing process +title: '' +labels: '' +assignees: '' + +--- + +**Initial set-up** + +- [ ] Check that the IntelliJ Idea UTBot plugin can be successfully installed +- [ ] Choose appropriate workflow from the next list (by default, use the latest one) https://github.com/UnitTestBot/UTBotJava/actions/workflows/publish-plugin-and-cli.yml +- [ ] Open IntelliJ IDE +- [ ] Remove the latest version of the UTBot plugin +- [ ] Clone or reuse UTBot workspace (https://github.com/UnitTestBot/UTBotJava.git) +- [ ] Open the workspace in the IDE with the installed plugin +- [ ] Build workspace (Instruction is needed) +- [ ] Go through manual scenarios + + +**Manual scenario #1** + +- [ ] Use default plugin settings +- [ ] Open the utbot-sample/src/main/java/org/utbot/examples/algorithms/ArraysQuickSort.java file +- [ ] Try to generate tests for the class +- [ ] Remove results +- [ ] Try to generate tests for the methods + + +**Manual scenario #2** + +- [ ] Use default plugin settings +- [ ] Open the utbot-sample/src/main/java/org/utbot/examples/mock/CommonMocksExample.java file +- [ ] Try to generate tests with all available (mocking) options + + +**Manual scenario #3** + +- [ ] Create a new Gradle project +- [ ] Add a simple java file to test +- [ ] Try to generate a test with a new test root + + +**Manual scenario #4** + +- [ ] Create a new Idea project +- [ ] Add a simple java file to test +- [ ] Try to generate a test with a new test root + +**Manual scenario #4** + +- [ ] Create a new Idea project +- [ ] Add a simple java file to test +- [ ] Try to generate tests for several classes From 1ba5bccb60b948a0547e94e3457231ea0df18a8d Mon Sep 17 00:00:00 2001 From: Victoria <32179813+victoriafomina@users.noreply.github.com> Date: Wed, 8 Jun 2022 11:53:54 +0300 Subject: [PATCH 025/120] Make possible running chosen tests closes #76 (#77) --- .../build-and-run-tests-from-branch.yml | 3 +- .../workflows/publish-on-github-packages.yml | 25 +++++--- .../publish-plugin-and-cli-from-branch.yml | 3 +- .../run-chosen-tests-from-branch.yml | 60 +++++++++++++++++++ 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/run-chosen-tests-from-branch.yml diff --git a/.github/workflows/build-and-run-tests-from-branch.yml b/.github/workflows/build-and-run-tests-from-branch.yml index 0e581ee963..e6e8798bac 100644 --- a/.github/workflows/build-and-run-tests-from-branch.yml +++ b/.github/workflows/build-and-run-tests-from-branch.yml @@ -1,4 +1,4 @@ -name: "UTBot Java: build and run tests" +name: "[M] UTBot Java: build and run tests" on: workflow_dispatch @@ -13,6 +13,7 @@ jobs: java-version: '8' distribution: 'zulu' java-package: jdk+fx + cache: gradle - uses: gradle/gradle-build-action@v2 with: gradle-version: 6.8 diff --git a/.github/workflows/publish-on-github-packages.yml b/.github/workflows/publish-on-github-packages.yml index 23a1ba9811..ac27d081da 100644 --- a/.github/workflows/publish-on-github-packages.yml +++ b/.github/workflows/publish-on-github-packages.yml @@ -1,6 +1,12 @@ name: "[M] Publish on GitHub Packages" -on: workflow_dispatch +on: + workflow_dispatch: + inputs: + commit-sha: + type: string + required: true + description: "commit SHA: e.g. cab4799c" jobs: build_and_run_tests: @@ -38,7 +44,7 @@ jobs: name: utbot_framework_tests_report path: utbot-framework/build/reports/tests/test/* - publish_framework-api_and_dependencies: + publish_utbot: needs: build_and_run_tests runs-on: ubuntu-20.04 permissions: @@ -60,21 +66,22 @@ jobs: GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Set project version - run: - echo "VERSION="$(date +%Y).$(date +%-m)"" >> $GITHUB_ENV - + - name: Check out ${{ github.event.inputs.commit-sha }} commit + run: | + git fetch + git checkout ${{ github.event.inputs.commit-sha }} + - name: "utbot-framework-api: build and run tests" run: | cd utbot-framework-api - gradle clean build --no-daemon -PsemVer=${{ env.VERSION }}-SNAPSHOT + gradle clean build --no-daemon - name: "utbot-api: build" run: | cd utbot-api - gradle clean build --no-daemon -PsemVer=${{ env.VERSION }}-SNAPSHOT + gradle clean build --no-daemon - name: "utbot-core: build" run: | cd utbot-core - gradle clean build --no-daemon -PsemVer=${{ env.VERSION }}-SNAPSHOT + gradle clean build --no-daemon diff --git a/.github/workflows/publish-plugin-and-cli-from-branch.yml b/.github/workflows/publish-plugin-and-cli-from-branch.yml index 847442adf0..216e007579 100644 --- a/.github/workflows/publish-plugin-and-cli-from-branch.yml +++ b/.github/workflows/publish-plugin-and-cli-from-branch.yml @@ -1,4 +1,4 @@ -name: "Plugin and CLI: publish as archives" +name: "[M] Plugin and CLI: publish as archives" on: workflow_dispatch: @@ -24,6 +24,7 @@ jobs: java-version: '8' distribution: 'zulu' java-package: jdk+fx + cache: gradle - uses: gradle/gradle-build-action@v2 with: gradle-version: 6.8 diff --git a/.github/workflows/run-chosen-tests-from-branch.yml b/.github/workflows/run-chosen-tests-from-branch.yml new file mode 100644 index 0000000000..968a2982fe --- /dev/null +++ b/.github/workflows/run-chosen-tests-from-branch.yml @@ -0,0 +1,60 @@ + +name: "[M] Run chosen tests" + +on: + workflow_dispatch: + inputs: + project-name: + type: choice + description: "Project you want to run tests for." + required: true + default: utbot-framework + options: + - utbot-analytics + - utbot-cli + - utbot-framework-api + - utbot-framework + - utbot-fuzzers + - utbot-gradle + - utbot-instrumentation-tests + - utbot-instrumentation + - utbot-intellij + tests-bunch-name: + type: string + required: true + description: "{package-name}.{class-name}.{test-name-optional}" + +jobs: + run-chosen-tests: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'zulu' + java-package: jdk+fx + cache: gradle + - uses: gradle/gradle-build-action@v2 + with: + gradle-version: 6.8 + + - name: Run chosen tests + run: | + export KOTLIN_HOME="/usr" + gradle :${{ github.event.inputs.project-name }}:test --tests ${{ github.event.inputs.tests-bunch-name }} + + - name: Upload ${{ github.event.inputs.project-name }} tests report if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: ${{ github.event.inputs.project-name }}-tests-report + path: ${{ github.event.inputs.project-name }}/build/reports/tests/test/* + + - name: Upload utbot-framework logs if utbot-framework tests have failed + if: ${{ failure() || (github.event.inputs.project-name == 'utbot-framework') }} + uses: actions/upload-artifact@v2 + with: + name: utbot_framework_logs + path: utbot-framework/logs/* From 80ea481fc5f82a1120df47291c10ce1f7ecd8303 Mon Sep 17 00:00:00 2001 From: Victoria <32179813+victoriafomina@users.noreply.github.com> Date: Wed, 8 Jun 2022 12:25:44 +0300 Subject: [PATCH 026/120] Update issue-to-project.yml closes #163 (#172) --- .github/workflows/issue-to-project.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/issue-to-project.yml b/.github/workflows/issue-to-project.yml index b16ce37cca..07f44137e2 100644 --- a/.github/workflows/issue-to-project.yml +++ b/.github/workflows/issue-to-project.yml @@ -15,3 +15,8 @@ jobs: project-url: https://github.com/orgs/UnitTestBot/projects/2 github-token: ${{ secrets.COPY_ISSUE_TO_PROJECT }} + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/UnitTestBot/projects/5 + github-token: ${{ secrets.COPY_ISSUE_TO_PROJECT }} + From 816e736ad0a13415837b6925be91fd117b6c5cd0 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Tue, 7 Jun 2022 13:45:49 +0300 Subject: [PATCH 027/120] Added overridden classes in the visualization --- .../utbot/engine/InterProceduralUnitGraph.kt | 20 ++++++++++++++----- .../engine/selectors/strategies/GraphViz.kt | 14 +++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt index dd2f550852..7cb98adbdf 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt @@ -40,6 +40,8 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { val stmts: MutableSet = graph.stmts.toMutableSet() val registeredEdges: MutableSet = graph.edges.toMutableSet() + // this field is required for visualization + val allEdges: MutableSet = graph.edges.toMutableSet() /** * Used in [org.utbot.engine.selectors.nurs.InheritorsSelector] for a fast search of Virtual invoke successors. */ @@ -54,6 +56,7 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { private val edgeToGraph: MutableMap = graph.edges.associateWithTo(mutableMapOf()) { graph } private val registeredEdgesCount: MutableMap = graph.outgoingEdgesCount + private val allEdgesCount: MutableMap = graph.outgoingEdgesCount private val outgoingEdgesCount: MutableMap = graph.outgoingEdgesCount fun method(stmt: Stmt): SootMethod = stmtToGraph[stmt]?.body?.method ?: error("$stmt not in graph.") @@ -125,6 +128,12 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { outgoingEdgesCount += graph.outgoingEdgesCount stmts += joinedStmts + allEdges += graph.edges + allEdgesCount += graph.outgoingEdgesCount + allEdgesCount.computeIfPresent(stmt) { _, value -> + value + 1 + } + if (registerEdges) { registeredMethods += method registeredEdgesCount += graph.outgoingEdgesCount @@ -144,6 +153,7 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { } registeredEdges += invokeEdge + allEdges += invokeEdge outgoingEdgesCount.computeIfPresent(stmt) { _, value -> value + 1 @@ -168,11 +178,8 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { when (edge) { in implicitEdges -> coveredImplicitEdges += edge - !in registeredEdges -> { + !in registeredEdges, !in coveredEdges-> { coveredOutgoingEdges.putIfAbsent(edge.src, 0) - coveredEdges += edge - } - !in coveredEdges -> { coveredOutgoingEdges.compute(edge.src) { _, value -> (value ?: 0) + 1 } coveredEdges += edge } @@ -280,7 +287,10 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { * Statement is covered if all the outgoing edges are covered. */ fun isCovered(stmt: Stmt) = - stmt !in registeredEdgesCount || stmt in coveredOutgoingEdges && coveredOutgoingEdges[stmt]!! >= registeredEdgesCount[stmt]!! + stmt !in registeredEdgesCount || isCoveredIgnoringRegistration(stmt) + + fun isCoveredIgnoringRegistration(stmt: Stmt) = + stmt in coveredOutgoingEdges && coveredOutgoingEdges[stmt]!! >= allEdgesCount[stmt]!! /** * Edge is covered if we visited it in successfully completed execution at least once diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt index 34db525a66..94d87a1792 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt @@ -15,6 +15,7 @@ import java.nio.file.Files import java.nio.file.Paths import mu.KotlinLogging import org.apache.commons.io.FileUtils +import org.utbot.engine.isOverridden import soot.jimple.Stmt import soot.toolkits.graph.ExceptionalUnitGraph @@ -81,8 +82,8 @@ class GraphViz( uncompletedStack.last().addDotEdge(it) fullStack.last().addDotEdge(it) } - (graph.registeredEdges + graph.implicitEdges).forEach { - if (!libraryGraphs.contains(stmtToSubgraph[it.src]) && !libraryGraphs.contains(stmtToSubgraph[it.src])) { + (graph.allEdges + graph.implicitEdges).forEach { + if (!libraryGraphs.contains(stmtToSubgraph[it.src]) && !libraryGraphs.contains(stmtToSubgraph[it.dst])) { dotGlobalGraph.addDotEdge(it) } } @@ -152,7 +153,8 @@ class GraphViz( } override fun onJoin(stmt: Stmt, graph: ExceptionalUnitGraph, shouldRegister: Boolean) { - if (!shouldRegister) { + val declaringClass = graph.body?.method?.declaringClass + if (declaringClass?.isLibraryClass == true && !declaringClass.isOverridden) { libraryGraphs.add(graph.body?.method?.declaringClass?.shortName + "." + graph.body?.method?.name) } update() @@ -175,7 +177,7 @@ class GraphViz( } } - for (edge in graph.registeredEdges + graph.implicitEdges) { + for (edge in graph.allEdges + graph.implicitEdges) { subgraphEdges[stmtToSubgraph[edge.src]!!]!!.add(edge) } } @@ -185,7 +187,7 @@ class GraphViz( graph.dotNode(executionState.stmt)?.isLast = true // Node property: covered - globalGraph.stmts.forEach { graph.dotNode(it)?.covered = globalGraph.isCovered(it) } + globalGraph.stmts.forEach { graph.dotNode(it)?.covered = globalGraph.isCoveredIgnoringRegistration(it) } // Node property: In queue pathSelector.queue().forEach { graph.dotNode(it.first.stmt)?.inQueue = true } @@ -194,7 +196,7 @@ class GraphViz( if (!pathSelector.isEmpty()) graph.dotNode(pathSelector.peek()!!.stmt)?.headQueue = true // Edge property: covered - (globalGraph.registeredEdges + globalGraph.implicitEdges).forEach { + (globalGraph.allEdges + globalGraph.implicitEdges).forEach { graph.dotEdge(it)?.isCovered = globalGraph.isCovered(it) } From 8e6533435df337d80137e454c1da0b4cff6dd192 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Tue, 7 Jun 2022 15:18:58 +0300 Subject: [PATCH 028/120] Visualization refactorings --- .../kotlin/org/utbot/engine/Extensions.kt | 3 + .../utbot/engine/InterProceduralUnitGraph.kt | 18 +- .../engine/selectors/strategies/GraphViz.kt | 247 ++++++++++-------- 3 files changed, 155 insertions(+), 113 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index 0010195e18..d987f9924d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -429,6 +429,9 @@ val SootClass.isUtMock: Boolean val SootClass.isOverridden: Boolean get() = packageName.startsWith(UTBOT_OVERRIDE_PACKAGE_NAME) +val SootClass.isLibraryNonOverriddenClass: Boolean + get() = isLibraryClass && !isOverridden + /** * Returns a state from the list that has [UtSolverStatusSAT] status. * Inside it calls UtSolver.check if required. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt index 7cb98adbdf..ff533b1085 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt @@ -39,9 +39,12 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { private val edgesCache = mutableMapOf>() val stmts: MutableSet = graph.stmts.toMutableSet() - val registeredEdges: MutableSet = graph.edges.toMutableSet() + private val registeredEdges: MutableSet = graph.edges.toMutableSet() // this field is required for visualization - val allEdges: MutableSet = graph.edges.toMutableSet() + private val allExplicitEdges: MutableSet = graph.edges.toMutableSet() + + val allEdges: Set get() = allExplicitEdges + implicitEdges + /** * Used in [org.utbot.engine.selectors.nurs.InheritorsSelector] for a fast search of Virtual invoke successors. */ @@ -128,11 +131,9 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { outgoingEdgesCount += graph.outgoingEdgesCount stmts += joinedStmts - allEdges += graph.edges + allExplicitEdges += graph.edges allEdgesCount += graph.outgoingEdgesCount - allEdgesCount.computeIfPresent(stmt) { _, value -> - value + 1 - } + allEdgesCount.computeIfPresent(stmt) { _, value -> value + 1 } if (registerEdges) { registeredMethods += method @@ -153,7 +154,7 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { } registeredEdges += invokeEdge - allEdges += invokeEdge + allExplicitEdges += invokeEdge outgoingEdgesCount.computeIfPresent(stmt) { _, value -> value + 1 @@ -207,7 +208,7 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { } // We don't want to cover all the exceptions in not overridden library classes - if (declaringClass.isLibraryClass && !declaringClass.isOverridden) { + if (declaringClass.isLibraryNonOverriddenClass) { return emptySet() } @@ -257,6 +258,7 @@ class InterProceduralUnitGraph(graph: ExceptionalUnitGraph) { /** * attach new statistics to graph before any modifying operations */ + @Suppress("UNREACHABLE_CODE") fun attach(statistics: TraverseGraphStatistics) { if (!attachAllowed) { throw error("Cannot attach new statistics. Graph have modified.") diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt index 94d87a1792..057fc70977 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt @@ -15,6 +15,7 @@ import java.nio.file.Files import java.nio.file.Paths import mu.KotlinLogging import org.apache.commons.io.FileUtils +import org.utbot.engine.isLibraryNonOverriddenClass import org.utbot.engine.isOverridden import soot.jimple.Stmt import soot.toolkits.graph.ExceptionalUnitGraph @@ -28,8 +29,9 @@ class GraphViz( ) : TraverseGraphStatistics(globalGraph) { // Files - private val path = Files.createTempDirectory("Graph-vis") - private val graphJs = Paths.get(path.toString(), "graph.js").toFile() + private val graphVisDirectory = Files.createTempDirectory("Graph-vis") + private val graphVisPathString = graphVisDirectory.toString() + private val graphJs = Paths.get(graphVisPathString, "graph.js").toFile() // Subgraph data private val stmtToSubgraph = mutableMapOf() @@ -41,13 +43,17 @@ class GraphViz( init { // Prepare workspace - for (file in arrayOf( + val requiredFileNames = arrayOf( "full.render.js", "graph.js", "legend.png", "private_method.png", "public_method.png", "render_graph.js", "UseVisJs.html", "viz.js" - )) { + ) + + val classLoader = GraphViz::class.java.classLoader + + for (file in requiredFileNames) { FileUtils.copyInputStreamToFile( - GraphViz::class.java.classLoader.getResourceAsStream("html/$file"), - Paths.get(path.toString(), file).toFile() + classLoader.getResourceAsStream("html/$file"), + Paths.get(graphVisDirectory.toString(), file).toFile() ) } FileWriter(graphJs).use { @@ -59,7 +65,9 @@ class GraphViz( } update() - val path = Paths.get(path.toString(), "UseVisJs.html") + + val path = Paths.get(graphVisPathString, "UseVisJs.html") + logger.debug { "Debug visualization: $path" } if (copyVisualizationPathToClipboard) { @@ -71,72 +79,71 @@ class GraphViz( override fun onVisit(executionState: ExecutionState) { var src: Stmt = executionState.path.firstOrNull() ?: return + update() - val uncompletedStack = mutableListOf(DotGraph(stmtToSubgraph[src]!!, 0)) // Only uncompleted methods execution - val fullStack = mutableListOf(DotGraph(stmtToSubgraph[src]!!, 0)) + val subgraph = stmtToSubgraph[src] ?: error("No subgraph found for the $src statement") + + val uncompletedStack = mutableListOf(DotGraph(subgraph, 0)) // Only uncompleted methods execution + val fullStack = mutableListOf(DotGraph(subgraph, 0)) val dotGlobalGraph = DotGraph("GlobalGraph", 0) // Add edges to dot graph - subgraphEdges[stmtToSubgraph[src]!!]?.forEach { + subgraphEdges[subgraph]?.forEach { uncompletedStack.last().addDotEdge(it) fullStack.last().addDotEdge(it) } - (graph.allEdges + graph.implicitEdges).forEach { - if (!libraryGraphs.contains(stmtToSubgraph[it.src]) && !libraryGraphs.contains(stmtToSubgraph[it.dst])) { - dotGlobalGraph.addDotEdge(it) + + graph.allEdges.forEach { edge -> + val (edgeSrc, edgeDst, _) = edge + + if (stmtToSubgraph[edgeSrc] !in libraryGraphs && stmtToSubgraph[edgeDst] !in libraryGraphs) { + dotGlobalGraph.addDotEdge(edge) } } // Split execution stack - for (dst in executionState.path + executionState.stmt) { + val allStatements = executionState.path + executionState.stmt + + for (dst in allStatements) { val edge = executionState.edges.findLast { it.src == src && it.dst == dst } ?: executionState.lastEdge - if (edge == null || edge.src != src || edge.dst != dst) continue - val graph = stmtToSubgraph[dst]!! - uncompletedStack.last().dotNode(src)?.visited = true - uncompletedStack.last().dotNode(dst)?.visited = true - uncompletedStack.last().dotEdge(edge)?.isVisited = true + if (edge == null || edge.src != src || edge.dst != dst) { + continue + } + + val graphName = stmtToSubgraph[dst] ?: error("No subgraph found for the $dst statement") - fullStack.last().dotNode(src)?.visited = true - fullStack.last().dotNode(dst)?.visited = true - fullStack.last().dotEdge(edge)?.isVisited = true - dotGlobalGraph.dotNode(src)?.visited = true - dotGlobalGraph.dotNode(dst)?.visited = true - dotGlobalGraph.dotEdge(edge)?.isVisited = true + uncompletedStack.last().markAsVisited(src, dst, edge) + fullStack.last().markAsVisited(src, dst, edge) + dotGlobalGraph.markAsVisited(src, dst, edge) // Check if we returned to previous method - if (uncompletedStack.last().name != graph) { - if (uncompletedStack.size > 1 && uncompletedStack[uncompletedStack.size - 2].name == graph) { + if (uncompletedStack.last().name != graphName) { + if (uncompletedStack.size > 1 && uncompletedStack[uncompletedStack.size - 2].name == graphName) { uncompletedStack.removeLast() } else { - uncompletedStack += DotGraph(graph, uncompletedStack.size) - subgraphEdges[graph]?.forEach { - uncompletedStack.last().addDotEdge(it) - } + uncompletedStack.addGraphWithEdges(graphName, uncompletedStack.size) } } // Check if we change execution method - if (graph != stmtToSubgraph[src]) { - fullStack += DotGraph(graph, uncompletedStack.size - 1) - subgraphEdges[graph]?.forEach { - fullStack.last().addDotEdge(it) - } + if (graphName != stmtToSubgraph[src]) { + fullStack.addGraphWithEdges(graphName, uncompletedStack.size - 1) } src = dst } // Filter library methods - uncompletedStack.removeIf { libraryGraphs.contains(it.name) } - fullStack.removeIf { libraryGraphs.contains(it.name) } + uncompletedStack.removeIf { it.name in libraryGraphs } + fullStack.removeIf { it.name in libraryGraphs } // Update nodes and edges properties - updateProperties(dotGlobalGraph, executionState) - uncompletedStack.forEach { updateProperties(it, executionState) } - fullStack.forEach { updateProperties(it, executionState) } + dotGlobalGraph.updateProperties(executionState) + uncompletedStack.forEach { it.updateProperties(executionState) } + fullStack.forEach { it.updateProperties(executionState) } // Write result FileWriter(graphJs).use { writer -> @@ -152,58 +159,89 @@ class GraphViz( } } + private fun MutableList.addGraphWithEdges(graphName: String, depth: Int) { + this += DotGraph(graphName, depth) + subgraphEdges[graphName]?.forEach { + last().addDotEdge(it) + } + } + + private fun DotGraph.markAsVisited(src: Stmt, dst: Stmt, edge: Edge) { + dotNode(src)?.visited = true + dotNode(dst)?.visited = true + dotEdge(edge)?.isVisited = true + } + + // Note that we do not use `shouldRegister` here, because visualization + // does not depend on the fact of registration. Otherwise, we'd + // lose overridden classes here and don't join them at the visualization. override fun onJoin(stmt: Stmt, graph: ExceptionalUnitGraph, shouldRegister: Boolean) { - val declaringClass = graph.body?.method?.declaringClass - if (declaringClass?.isLibraryClass == true && !declaringClass.isOverridden) { - libraryGraphs.add(graph.body?.method?.declaringClass?.shortName + "." + graph.body?.method?.name) + val method = graph.body?.method + val declaringClass = method?.declaringClass + + if (declaringClass?.isLibraryNonOverriddenClass == true) { + libraryGraphs += declaringClass.shortName + "." + method.name } + update() } override fun onTraversed(executionState: ExecutionState) { - if (executionState.exception != null) - exceptionalEdges[executionState.lastEdge ?: return] = executionState.exception.concrete.toString() + if (executionState.lastEdge == null) { + return + } + + if (executionState.exception != null) { + exceptionalEdges[executionState.lastEdge] = executionState.exception.concrete.toString() + } } private fun update() { graph.graphs.forEachIndexed { _, graph -> - val declaringClassName = - if (graph.body.method.isDeclared) graph.body?.method?.declaringClass?.shortName else "UnknownClass" - val name = declaringClassName + "." + graph.body?.method?.name - subgraphVisibility.putIfAbsent(name, if (graph.body?.method?.isPrivate == true) "private" else "public") - subgraphEdges.putIfAbsent(name, mutableSetOf()) + val method = graph.body?.method + val declaringClass = if (method?.isDeclared == true) method.declaringClass?.shortName else "UnknownClass" + val methodName = declaringClass + "." + method?.name + val visibility = if (method?.isPrivate == true) "private" else "public" + + subgraphVisibility.putIfAbsent(methodName, visibility) + subgraphEdges.putIfAbsent(methodName, mutableSetOf()) + graph.stmts.forEach { stmt -> - stmtToSubgraph[stmt] = name + stmtToSubgraph[stmt] = methodName } } - for (edge in graph.allEdges + graph.implicitEdges) { - subgraphEdges[stmtToSubgraph[edge.src]!!]!!.add(edge) + for (edge in graph.allEdges) { + val subgraph = stmtToSubgraph[edge.src] ?: error("No subgraph found for the ${edge.src} statement") + val edges = subgraphEdges[subgraph] ?: error("No subgraph edges found for the $subgraph") + edges += edge } } - private fun updateProperties(graph: DotGraph, executionState: ExecutionState) { + private fun DotGraph.updateProperties(executionState: ExecutionState) { // Node property: Last execution state - graph.dotNode(executionState.stmt)?.isLast = true + dotNode(executionState.stmt)?.isLast = true // Node property: covered - globalGraph.stmts.forEach { graph.dotNode(it)?.covered = globalGraph.isCoveredIgnoringRegistration(it) } + globalGraph.stmts.forEach { dotNode(it)?.covered = globalGraph.isCoveredIgnoringRegistration(it) } + + val queue = pathSelector.queue() // Node property: In queue - pathSelector.queue().forEach { graph.dotNode(it.first.stmt)?.inQueue = true } + queue.forEach { (state, _) -> dotNode(state.stmt)?.inQueue = true } // Node property: Head of queue - if (!pathSelector.isEmpty()) graph.dotNode(pathSelector.peek()!!.stmt)?.headQueue = true + pathSelector.peek()?.let { dotNode(it.stmt)?.headQueue = true } // Edge property: covered - (globalGraph.allEdges + globalGraph.implicitEdges).forEach { - graph.dotEdge(it)?.isCovered = globalGraph.isCovered(it) + globalGraph.allEdges.forEach { + dotEdge(it)?.isCovered = globalGraph.isCovered(it) } // Edge property: Edge to queue stmt property - pathSelector.queue().filter { it.first.lastEdge != null && it.first.lastEdge !in globalGraph.implicitEdges } + queue.filter { (state, _) -> state.lastEdge != null && state.lastEdge !in globalGraph.implicitEdges } .forEach { (state, weight) -> - graph.dotEdge(state.lastEdge!!)?.apply { + dotEdge(state.lastEdge!!)?.apply { isToQueueStmt = true label = "%.2f".format(weight) } @@ -211,15 +249,14 @@ class GraphViz( // Edge property: implicit edge to exception exceptionalEdges.keys.forEach { - graph.dotEdge(it)?.apply { + dotEdge(it)?.apply { isExceptional = true - toException = exceptionalEdges[it]!! + toException = exceptionalEdges.getValue(it) } } } } - /** * Represents graph node in dot format */ @@ -231,7 +268,6 @@ data class DotNode(val id: Int, val label: String) { var visited: Boolean = false var invoke: Boolean = false var isLast: Boolean = false - var isImplicit: Boolean = false private var toolTip: String = "" private val fillColor: String @@ -243,17 +279,17 @@ data class DotNode(val id: Int, val label: String) { return mixtureColors(colors, "white") } - val color: String + private val color: String get() = if (visited) "red" else "black" - val shape: String + private val shape: String get() = when { returned -> "circle" invoke -> "cds" else -> "rectangle" } - val width: String + private val width: String get() = when { isLast -> "5.0" invoke -> "2.0" @@ -276,7 +312,7 @@ data class DotEdge(val id: Int, val srcId: Int, val dstId: Int, var label: Strin var isCaller = false var toException = "" - val color: String + private val color: String get() { val colors = mutableListOf() if (isVisited) colors += "red" @@ -285,7 +321,7 @@ data class DotEdge(val id: Int, val srcId: Int, val dstId: Int, var label: Strin return mixtureColors(colors, "black") } - val style: String + private val style: String get() = when { isCaller -> "dotted" isExceptional -> "dashed" @@ -302,30 +338,29 @@ data class DotEdge(val id: Int, val srcId: Int, val dstId: Int, var label: Strin * Represents graph in dot format */ class DotGraph(val name: String, val depth: Int) { - var nodeId = 0 - var edgeId = 0 - val nodes = mutableMapOf() + private var nodeId = 0 + private var edgeId = 0 + private val nodes = mutableMapOf() val edges = mutableMapOf() fun dotNode(stmt: Stmt): DotNode? = nodes[stmt] - fun dotEdge(edge: Edge): DotEdge? { - return edges[edge] - } + fun dotEdge(edge: Edge): DotEdge? = edges[edge] - fun addDotEdge(edge: Edge): DotEdge { - val srcNode = nodes.getOrPut(edge.src) { DotNode(nodeId++, edge.src.toDotName()) } - val dstNode = nodes.getOrPut(edge.dst) { DotNode(nodeId++, edge.dst.toDotName()) } + fun addDotEdge(edge: Edge): DotEdge = with(edge) { + val srcNode = nodes.getOrPut(src) { DotNode(nodeId++, src.toDotName()) } + val dstNode = nodes.getOrPut(dst) { DotNode(nodeId++, dst.toDotName()) } - srcNode.returned = edge.src.isReturn - srcNode.invoke = edge.src.containsInvokeExpr() - dstNode.returned = edge.dst.isReturn - dstNode.invoke = edge.dst.containsInvokeExpr() + srcNode.returned = src.isReturn + srcNode.invoke = src.containsInvokeExpr() + + dstNode.returned = dst.isReturn + dstNode.invoke = dst.containsInvokeExpr() val dotEdge = edges.getOrPut(edge) { DotEdge(edgeId++, srcNode.id, dstNode.id) } - dotEdge.isCaller = edge.decisionNum == CALL_DECISION_NUM + dotEdge.isCaller = decisionNum == CALL_DECISION_NUM - return dotEdge + dotEdge } // Remove special characters in dot format @@ -349,25 +384,27 @@ class DotGraph(val name: String, val depth: Int) { edges.filter { !it.value.isExceptional }.forEach { append(it.value.toString()) } // Add implicit edge and node (exception case) - edges.values.filter { it.isExceptional }.forEach { - val implicitId = nodeId++ - val color = if (it.isCovered) "azure3" else "black" - val shortName = it.toException.split(":")[0].filter { it2 -> it2.isUpperCase() } - - if (it.isCovered) { - append( - "\"$implicitId\" [label=\"${shortName}\",tooltip=\"${it.toException}\"," + - "shape=circle,style=\"filled,dashed\",fillcolor=azure3];\n" - ) - } else { - append( - "\"$implicitId\" [label=\"${shortName}\", tooltip=\"${it.toException}\"," + - "shape=circle, style=dashed];\n" - ) - } + edges.values + .filter { it.isExceptional } + .forEach { + val implicitId = nodeId++ + val color = if (it.isCovered) "azure3" else "black" + val shortName = it.toException.split(":")[0].filter { it2 -> it2.isUpperCase() } + + if (it.isCovered) { + append( + "\"$implicitId\" [label=\"${shortName}\",tooltip=\"${it.toException}\"," + + "shape=circle,style=\"filled,dashed\",fillcolor=azure3];\n" + ) + } else { + append( + "\"$implicitId\" [label=\"${shortName}\", tooltip=\"${it.toException}\"," + + "shape=circle, style=dashed];\n" + ) + } - append("\"${it.srcId}\" -> \"$implicitId\" [style=dashed,color=\"$color\"];\n") - } + append("\"${it.srcId}\" -> \"$implicitId\" [style=dashed,color=\"$color\"];\n") + } // Close dot graph append("}") From efad45cef5b45b357a70a6e8910292fc17cb2bda Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 8 Jun 2022 15:40:03 +0300 Subject: [PATCH 029/120] Turn on disabled tests --- .../org/utbot/examples/mock/MockWithSideEffectExampleTest.kt | 1 - .../org/utbot/examples/objects/ModelMinimizationExamplesTest.kt | 1 - .../examples/objects/SimpleClassMultiInstanceExampleTest.kt | 1 - 3 files changed, 3 deletions(-) diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt index 3d00f6c570..a8aeb24a9c 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt @@ -8,7 +8,6 @@ import org.utbot.examples.isException import org.utbot.framework.plugin.api.MockStrategyApi import org.junit.Test -@Ignore internal class MockWithSideEffectExampleTest : AbstractTestCaseGeneratorTest(testClass = MockWithSideEffectExample::class) { @Test fun testSideEffect() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt index 5be33620bf..db84fb2da0 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt @@ -6,7 +6,6 @@ import org.utbot.examples.DoNotCalculate import org.utbot.examples.eq import org.junit.Test -@Ignore internal class ModelMinimizationExamplesTest : AbstractTestCaseGeneratorTest(testClass = ModelMinimizationExamples::class) { @Test fun singleValueComparisonTest() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt index 40f0e7c173..7b552ada8b 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt @@ -6,7 +6,6 @@ import org.utbot.examples.DoNotCalculate import org.utbot.examples.eq import org.junit.Test -@Ignore internal class SimpleClassMultiInstanceExampleTest : AbstractTestCaseGeneratorTest(testClass = SimpleClassMultiInstanceExample::class) { @Test fun singleObjectChangeTest() { From 67cdd208a5b9bde21d539280ff6e9c9328141b89 Mon Sep 17 00:00:00 2001 From: nikitavlaev Date: Mon, 6 Jun 2022 14:04:28 +0300 Subject: [PATCH 030/120] #151: macOS support Created macOS binaries with independent libz3.dylib and libz3java.dylib. Changed Z3Initializer to support macOS binaries. Changed utbot-analytics/build.gradle to use correct os name for macOS. --- utbot-analytics/build.gradle | 5 ++++- utbot-framework/build.gradle | 1 + .../dist/z3-native-osx-4.8.9.1.zip | Bin 0 -> 18844039 bytes .../org/utbot/engine/z3/Z3initializer.kt | 13 +++++++------ 4 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 utbot-framework/dist/z3-native-osx-4.8.9.1.zip diff --git a/utbot-analytics/build.gradle b/utbot-analytics/build.gradle index 3c06d3cc17..fd34a6eb97 100644 --- a/utbot-analytics/build.gradle +++ b/utbot-analytics/build.gradle @@ -6,7 +6,10 @@ configurations { } -String classifier = System.getProperty('os.name').toLowerCase().split()[0] + "-x86_64" +def osName = System.getProperty('os.name').toLowerCase().split()[0] +if (osName == "mac") osName = "macosx" +String classifier = osName + "-x86_64" + evaluationDependsOn(':utbot-framework') compileTestJava.dependsOn tasks.getByPath(':utbot-framework:testClasses') dependencies { diff --git a/utbot-framework/build.gradle b/utbot-framework/build.gradle index a558f2daf5..25ab536dc4 100644 --- a/utbot-framework/build.gradle +++ b/utbot-framework/build.gradle @@ -53,6 +53,7 @@ dependencies { z3native group: 'com.microsoft.z3', name: 'z3-native-win64', version: z3_version, ext: 'zip' z3native group: 'com.microsoft.z3', name: 'z3-native-linux64', version: z3_version, ext: 'zip' + z3native group: 'com.microsoft.z3', name: 'z3-native-osx', version: z3_version, ext: 'zip' } processResources { diff --git a/utbot-framework/dist/z3-native-osx-4.8.9.1.zip b/utbot-framework/dist/z3-native-osx-4.8.9.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..9b42561fc925ebec372bb76a755723d844ae4ec7 GIT binary patch literal 18844039 zcmXV%byQSO8^`bN(z&!WEZtH9O0!Ezw={^fvF zbh9tNbKX1Wp80R?xiioCe4pn#GkT99IMe{aT@!!LuMhnHahJ3M5d&<1yNi9GxUfUO zU2kke3gC-5ncJ|hy|cHY&$EBwPYi%?&OrchHvjk#(i7mi^{)##-`fj-}cVr z_phc`UhFEr`hBup>8q1lS-VGX(Aj=%tynx}?k2(T=r~{ea)c%~Cc^|CflXD#YPXC# zU!Gxm>-t0(PCC0;%zgV>4nuKQCvL9r@wLfJ?i+T@DF16J=!i>W`Sq{uL6*z49rl(g zTqPv?UD?8)q5|JpbD4EUSCQ>m!oZtoym%%?iG{ZdIY|Z6-8wQYjWb`^A^4}{S+e(? z-Mz3&HlgBEkA?9AWu|jy8w#8b>q4<%RN;N(jB=cbw|OC^d7GqF&xM#KLf_A+dk$_A zew}Y9H|;cdW`JE)Fn{S@+<#?*y=5=vA!U>+$JUQU^ks~_?kpps@Y!)2N)S4s>$4I= zJ+jHS32Fs1c)C>&vRDjYXPaKev&@Ee?-z$KiZ^0smJR~`&ieNKv=n`L7Fd|C@*Rh` zuPCphI^D5`Df{np=-E%QeK1Q`cG9i zN_lEedIOi)wx z7iG2{AiObUzS(f~30bXQymdBvHM+maBU4*6rd1`f-J3e*ygY0&{XTWv*-@^tcB*jc zadM>_7bj&|O<~6@&LQ~cj?u^WE!|e}@5lOBo2`Z1oS7sv-;#6?Vt=dDFEYokr*ZFx zo-AG$$ho-U9M@F`*yqUd8N@J)5j&ml%*n+M+`YFvx3bnAXh zW1-fKv>u;N2dEe=KU-to9GtwP_!nvwk`HB)sPteQ7A&z2uFcc_>RjW}5%$4#>Lf)V z05ctbT3MKTVpLqg&idJ;yy9e;@wgLvd$x$Zy$U_8tJ1+vmVMU!D4ctJvZ%W>CMa36 zS3b2x<)FEtIn?96NaL;gFP+BR7tk0dm>h|qS=zn-p1y`{Q=y*1_VWi|#JK3_SWLhxB?oG%%r5wz9$F_HLxXT=;5EoaO+JI2IKN`TLC?k7 zg;F6{=n7%<#ue%i{-oAex{C6cNeYCCD4JBOCo)!x4*rGjcLN65aW#o{^mQ)?%PJwU~M>})-M8>`~ zfP;(Lb&DrJmwcT0F|ZJ=5)LYjLfBMfT8^IixF+@3eTqlihY7K=)?edfjp0Nnn3`od zrvHbL1MQ0gekBLWblplK7*!@FUu%_>f7}q7`E`v-zb6@W-33=QL@$S9JT2uZ5Z2O$ z=#Q!87<2xlwj4T?!G9B#orxjiZj6Z?q=h^FH%qHh++r!H!BSXAIL0OxbHN|39>8so ziV`eKExae-J5>6_OBG`V~dc%8Zf!G}15?DIF9@@#sp^VMPg5n8pEL;pN zqs5sUOF{(~7JxzE6s+9id8nK+n8)LzdJQ3c&c3i%Gf|3ck|&(OsQaP(u-Q}`bcEKb zSpfgCIwDQzkD>WjlC*K1SlCJ?#(~=)KGx65o`LPCcP`#+*Ri@K?WZu;@k-NpVn`8Y zTiIw;MaKB8we?_JyD{7~24<0|E=a|vZb8Wv6J!8iFr|ivXJ81p?bQ%|;TS2z>Kk4I z6w3cTg$2KTZaT(Ye^p0`|FB+=ra~8yAhL|8bh?kOEyBRK8^ckp`shVmbZo3Tmwt&j z)z&clhap-uT-^oHW2OZ4c7P!=W8`Q8I^NM;)XuCTd6OJ;5yB~jFjkiwcN%yeFKhyV zrq^7E8n2XCd;*mziqWD)xZXzxYH=pSN)y2tY0xc27^+A7 zNn4r-T0?ZnMSxb$a1q9pyAh4jdJhxFL4S?K)cAy}i*Sn}QA`D}g>VcR!Xbj!ARLu) z_n~r3S*Sm{BNlT{pK~ZJ)=~t4NYh7mgku(C)y4IDy5Q@g=xig0W;P!LMFS0DP81Dr z`-H_#p7SKu>k0_OIA^MZxEmv4Wi$KYs!iZ&aWJ*#<(P^jBL@i06QLME6F6QREIC7+ zmnu^i5l9I0rJ>N(F9A^<*uw((4V0mol`*R+6mV;EL-Z@Ia*RKJ675jjTRnIZ9DR?w zF&X7o2E$Y0RHV32qo&8eqeRi(MkQ9(LR$i{Mz$~ytvAnOUH;>M`V0NlG#VNW5gi1J z9=bU62v!k``8*zu0dqILMa{p5CF7u}5v#4dhg4KsKj0qvXuJT_CIYRjRdSz7<}t#0 zcnz^kjkbZK#m)JBYVXm=Xd&!z&^58@2z>_zDwG3^(8!^Htx=bv-;V~KB~FoVUpZ*P zUYE8*hiH^E;wKFbt(RFOXz_id@csoo1yN)y{v|4ByCscK=f!(e>%^NwG`4qFzBOy zhpQVSRvk5z;3M!`N0@O1MqR0-9M)(}eeev%oT0AIZ=ekw) z*$^d(yp$Mt+kNz6xq7ftNixhpo4UaXHmtR(#&4g~AJ?o0U!bLeQ-0AyQ>*bCWX8Hv z!Y!%MPm3@%TAVqkAb;+-YyOz_fbVce6iho)U4YvsF*bz?Uds!!f}_v!D=>8UFRxS) z9&nT}0?m%<{yxilfc{rp*w; zs%X#^MVJU(qtz$u{FHH<04HU^Na`{Nk!$JNPN#=H&K*Rdpu}lLao_~2 zBi5r3(cyfCNBIPRP2r#eV`E6EW`DqAy2YaeiQp#pvLJ@G37b|EF zQ;|kDn3YJy_|d^@6P;kcGSr>8DSJ_q-LPH7Rb3^AJeXJpN?HsZpMz;~QSYIG(-Oe$ zMtsJd}TpaBBS ztG~^-ldjWZ-#WrMmc9D7-4%$x# zzu(673BYzUF^@@U=!a39+Dfp_F!fNSk_4E2FWg>Yz43-ba5?4a_xf>lZl4_*nmgiO z`ob2>RwqnhhskV>))bk8aQ^$~pE;N^g?E^zP#4~qV?#KD7~0|!S);n7j)Ms8)^s>$ zY2$w~y*f3Z&0sa-aD_$TmIp3&VxK;V8);X`eufKj+B) zgCaRtzQ3yMg2S}lVgjZI(?xtMu4E4A+SpubV0V>XNJ`e)b#Zy6E_7Ji0r?xHU@Srxtz_5rQ@OMIW%t z-4L*d6L=~klPz4j;QO;p`k!gZDM_wSOK6<`{O&Q+7^_Y1VG?su&uz#wabLt)-1W() zu_A@eA);L~_|XdFIkycAqkl&iIUa;6r5Z?eCWq zC;PE|+OeqXu&4H)`c`w74}3iJL(|MU9p3m~S2y|R?0On3Xv?z9Pp-Ch`sYaZmULM4 zJpCNPy{4jlteRmNibQpUNdCdk+{8uou|)K)Ah`RuqRr)pJw)a{mgwBApbwKwUk^*1 z_MB6o`*ObargYZzw?2cj8D(=9-B%J{Yv1~eYBqhRfhYI=I*4qJC=9}=_?>{siR>lBjjY9Ko?1{_4pi`~^vn|e@oQJ!ntDE+$Zi~( z_1^7Laz=v=&A3x7PL0_$`t7V1V$z)$-jijsuQb$rd-2nf?`KdJfh66U%~6AyZ?>O#p;#a73Y#yIXK|7?i;-E6j_NLqs{_<)>2pH0^YN93I@hv> z4(ntLt7k20EBZUvaT^@JgUT~!q!zP!S%@UX_Z5d^E~a<*ev+iR-R6W2P1z5%_-IdG zyUEkI(Wba9>?eB;nJ2!A@@&{l>}TqUE7i`5VYBcW9GfjCxUpI3Jz2W7Dnm_jhc!?Be$s173L4rBa!bpP z6``CuhkP<=YbLr9{qcH=(B}bNZYqhFt02;xQ%F0?5VABWrG@*oV$Z9R{(VT4;Z3Vy z&5}{W@6{A3d1FQ$N&HF~d1D5hsVKvm1HMPnCz}j1lJxn3)5$qm4beGsiMnz~!_Ymh zvVbes-A8s$= zhc1?nTF*-@EtQSiA#|NdaZMr_v?IN}b!*FvmCK^7=kbFN^Wi*L{L_r7aS$C{XmZ?dh|_#J=4pn=4V01eegM;HEtjmZb^!Bs){|3%2xmBb3kC+ zASv8}j)NHW>SC_ceY}~C59lr3dFUQh#Y&;i0@jwNowVbemhE4Ek{&9N_AgoGq}!De z`YTA28kd$yD~ZMoCC6f%4Sk#^ZSO6bml}m1rH*9n z{T#dM#aZK=S1?gLD45sH>slOPOO;LB|B<6w^5@f6mZ?9dtj|9uHN6a`8r-&$?c08_ z<=34ji^U-0ZG)30TV-iIug_?5u5;F{zg~5>G^|D&)<`6=k-PODedoW9;+*d+Vz`y! zcX?r_ahi9~_FiFb+cGHm-(LN={Hsa%XPstKU)nY%Z%(AuF1Kx4I#~^bPS0+i_}&Dw zHQZjNa@=(IWFDXWc{aWnJGzK88n+o4FzEdeH!%4To zx{}k21Oqq0srI#mgE{;E&vP66I9b zrEW$Qxn(_@zs33vVLF!#;742V*fn*KNOQF7DP`#^_jdQsji zIz+OXJ}x5SP8TvAghTA}#Vpc!Ne@=ySq(B!`8G#eiH{apcXab@+BT1E(CqnT6eiVyWmz(m0=BDK@_SF96M&w!kA?ZQVg0`s4$+u&RPQCG>5AENY~MH;XMlvm^JL6-24 zpBbd_kUW4wc^D1g2P~xvypP}6ororBd`n0$=-X~7Tf-uPD(m;3eK|bu|HOZI+5h74 zsLCuM-r%H~dzUPq(p<2ysBmnBys3s?sMfmjHi1J!iz(seV}f6ZX=!^t=<^ona-G{7 z-jKYH@HA?YQF))UWk05Zm1&vG{ID4%k8e54wNyn5+6m?4@Qds><=)BANKfxKzVu1I zFgN6$B4r!tf{EC8C!rB6q2oFhXPNce6W?1x8MlC2g=Y^#3xXi>ZAZw9@)zFYi&3MC z-%C2<6LYv$T0AK|7p1~3M-0&8IIJY+=h^&A@dAV5z#x0z-aZYnLf5Dak_9-PFY&`q zxtxcUwprZA>JnV~oJ;*u-D_NO^_OsfZ`*Tx0l9aBA9T<2=;S0i6vdDi1 z)#ItGSB5zQv49@+{5l)SZIG}9E|bB{71g&`?QZ2QGdMiCxRuVE3SQy{X1OnS z3!4Xm4IW;6(OiTeVOu!m?`p?C;ug>VVO#u>JduQ>#Kx4=ATDW1WZP_Ymn$Ln>$U#9>d3p9EFa#^d#w-o4XgBI~Xi>D|* zg3Rz0cOb?bWT1bz$YpY4qQ}7I!8_oFrIMN(bpIaG+qHlZ@CBI_<1s+~L6JP<8VUol zl)k!lNOu+r%mrF=?SnYT9pn`RHv(%L4TD!n^&WlrK?}?Qqt>K@;DSd$@MISh2*GDN zME@w-1u_V`f+Uy7x|D&IauGX-A{a28BudOBct_Ud_Mw{(SOC4dQfDe+ENDJ{dM_-| z_|gvW2S%3~G_g_u7}zYv3j#90iYW}`ICn$5tO&DI1DAmQt&1L<{cf7yY{iEQLsRpi z%NsoQ@E*|Q9${FUyuOu_C_n{d)ZlT0>xq$1T?+(&*MRg9{MoQ7kONuz!oZ7%Jmmwq z=T#y=;5a*Q>(6u4?cQQbD4o1&rdgx+U#y_IXaGBGiaNnbA zvXsb~qG{VXpk}_X0+<5Izxo>cUV@;2;yaB;<|0 z#{s~g%@Vwypiu~t-PQIXzyV~G?Ck--Ett)`W|Rmxhgp=>XnOvB%+<&y0D)fk+^2S+ zx5rtfM!w)m2^rCoTqXk~#R*3R4RM+9x>%95nd`Ej2{LTLGOK9`K}-CA?qrt;kaE8} zL%b3h1^9ys3?T^6KTtsx1Ew*;miF^ zL6_Y+KBN{t;zPe}s?IuJX3b*fH1-zvnIv9eL)Ht{Zt8N^_ZGMFozC`&eRBmDI)CM2 zoUkVakymGP_%YA^va&Tz>8`4+r0M@7h{~uhA$5?wZ%2IpkEOi#myg_VL$)l6?%=_Y z)XouzDP!y9o1@{`y2HPCOYLE9RWD!P10bM##R=7mfElFg<%e)`fDsgm_786y^A+|#-s>#>m>IX&z$X||w`P{G7^ikLi=Fpm4;cuHtWq|+OlFNI z-(5b)>CMAA@>8yeuY%bpcxi zpzn{XsA*4C4S179@r=;OX@f-+8j8OuplGASw+)q&{tU6h4Rbs*n9AS=Qg8|eABzRP z+Bb`J;}Ej8+~3lg5w@&^^*F>KBGBATJ!Gp=;zUAPZh9Ww?jeMF$Xd z8dxdgD`uMFl;3JfIBD}fCn%M#iy4Yuq+qu|UV-Uy9P>gwxK}!UviPoa^so$!C`e^9?oSNYAKV@%uN}yr zzgs)FwR>f~PkbJx5lGqD`e+vx5xn(4V&)U{-gSJ22=&bU?m&^bQZe7hOD&L(YP->g zfF*b-iviQm-em}gVN%`YYz!E-OhWIZ`sCx{Hyfo&nOG%zJt5vJn=Pr?A_2e!X=bD1L+uv|ns=+;wxC{Zc%g zor~4wKf+!G9V*G+1hk3Q1uc=R3nAv$I-D2}vO>fM@S)gzROuD)E3SSoy`{3KUDzuW zB&E%~rUO*t+I9ryw4cL~HNcLZKn{eB5GmpOBezD-4w|YB{KntRH0J^%VbF)hiv&}% zdfV9kt;HJrSGC4{-`;=VWFdJp_$9RAo4xF@g_|%7UXJa7*LuyGe{1K6kF4Ksw5jYM zkWFuUUi(YAd-*J0kOkA_dWO#M#_}8Hk_hG<{@y4UR^MGv&FXD{7sys!)%=#cug$a=K3e_NBb7;5ISeiNPQh5axjhUgj5z z1)7^z3x~w@T3(oTGY{qqawruWJd}t1Z-{GzhH`&W6hw-sl@R)4`+>LSL$*_pH(rdX z>>m)kP*9nA#6WVI7HJMl_KQ8oW!!(&bUWl<_jz9HS|s{>(f+~hQ^7uLWBbq7uU5>o z&fD37V{pZH*c|_{e9V*9`)EG3R`djCk?@?Q-h5tl*wvE_6@&*<`+$>X04gUnsVaN0 ztpl8XT`87ZJwQK?e<7RBwY8X0OEk65!@T%RChu{N8B=~$TIEfHGhcP5RJK*9=UtM@ znx4g&pGziYvoq2xE6=k&kuRQo#E`F06B*zu*+IYG$YC~p;mhUk+mF>1b2<|5}(oK*wQP(qd znz?(z&}cv(58QCgm9>EDo);UrViA_4WSscl~(pl=VU8@6Z_s z(Y!ArVsyG$H-dTLobtO10Hd54V*Mj};L}A-@8hPUU-QgqE4y>5aVuquuot19gXDVaWcf#m`H#PW{df6WMaQ% z$8Ty}#pnN`>7<{Fb^ewB$BZdz;n`mhc@w#$yZn`0koQM*3gw^Mc*@*vWTUVbQ0(^5 zcWmImfx_avzes!b1^+5)?V{L1{sv{{`R5{h#m9c2dq6{{PAx>@izMhhG)cIL{kH_9 zQxbPX^~YXu_C-37g`>x-2Yk@vQbxuhNjHPJA7TCgHMq{{8e6u!a^$7ye++8U zz3rBXd$ig11Wq(UWebiaQ$moJ!-tz?Qy=xApEWkNtEKz=TuJZ|7e(upIr)xZ27i37_w-EyOj|WI2qM%tILTm*B9g?J1CbD!e=CtJ*)s;P=SM)?^O+L zx}_+$7%sxbpR*cMtT>1R1IPH?@(&0Fse1gh}X9HyoG^sj2u(!VRz{b+%r{F4kI7ua+qdN(~M42V-? z=$#n1K>pzafs(d>ttjBO$p5uLjw<}=J*ZlkdI0hE%BviWUyuXZm{*FeHERT~*uDVh zDvv1AJ7uHjI>@Dbr2h9$h_l8ehd(z=Ip>ErKm^fKC9TFOAOa+(y7GZ$yn+sfO;W{2 zKuAcLkA*l8VgR(;K!wPnD1l&{O-9AMeVMq9*970z z{e9?mz%mOo4rvDI5zmtb{})sfFo*QEftgV&oKZTYk3xAM6&UIJ02J#~g|q>9i527B zMcE&Ojk6XuJ3XkjX+&($fOzi}c%4Wtl&Cm?%u4VKK>pw^70G%kBsmcD!{n{E7>Ztn zT(2+@$LF%#fSyRv1<N>i@$sp?@k$gwc zWdNjAg~bCo0GkHjv-(z!IfL4^mVl%kOQ`q|NSriv!Tm2;3W0en$__Z(;%}De;xGWC z7rM*sw|`i|$UrH_D4F4_Zl6-1v}#?PAnM+Y@pIzN%g>rF8Ri%6$D+60r{%1{a5VNon7@;Mc}>vlvEA}iStVz zXahk@Ow7o-B!HqRqEMio70LNQSr=FU+a9IP6m6T1(Z&O=N2!V+;5nB6O?dxf9&;nE z#GAYj3dcPx40B_1gjnCh&eJ{lf{nxYVxM+2V!dL#nj-R8|b@q^I2&JEr9A4__!2>XGZrTV-Hk-2e7!>vH&cA z5tr%FONsLFh5JjDVACf;C6F$BfEBhXulOW2+0K}zzz(=mFEh}6X5>E+`x7214HuG> z@4@;X#6U_4c?SfG_f&`C%`RugmX}L-+ek{?I&GFm>EeAH(Rql(-?J( zlj16%e}=1;0)}x$L!+2l>2tqPuzEm?2BnlIv8u4b19{bV-`|6pi3)-jmwam%WioRW zjV^Qu>h|AA`z3O$0~Gi@2(}_bwq@!D z5qPPx0vQ&29!;SBE+zzgXNn9GITcDUXl_(gd4g;o0k6l%Cwq(#T#vH|;b5rK6lDac zm7t6H9q;=!&Y-aRG0JZJs-$uv#V}crz+EtU8TYW3m&I;R3wGw4y{gRI2sQk3u_)U# zf-SmRj;n#4qkWnmNBa^37yr(af=(5Rg;u|@+n%C(cJ{t;XvoXnR}4ak{^1v0w6%@s zLWpMfewgM*(qi&qc;%~>!sb{UkG1iLIUh8@Z$K7{oV~O zo9B5#*n^L~~*ZHKl?%t|s%9b@lQB=cxpwe+)*nt83ha6sz zJC5Tpv|zW>^n|SIH+YYpcQ;`vMQnWOOEuuI0WNSLmf}Ti7b=y3e&Ax}tk+IY0P02_ zMbXzkdG>R~XPBpKfKL#k5o?J7fcGSFT<5f;f@%Yixo77f1m&#FgM@oB#(Xqa5!c z_)aA97|QQ#p*w>8(t{#Z082f{IAX?}9}Yw-S3c$h=|Pa5SN*V(Kz3mPcbppwHXl9U9eYc32BCgU$4{ZE9p6oW1=spv4N$QisI>ksffT(CQ??Z=28lxMiMo#ub; z0gph^<#;AQJvq_=pVsKb%U!3ZK9q_hpiAYp<=rLT?>|R?XAq+qef>bGT)rs~<`-Hq zSDVwapqV)3^u?<0o87mawhm5tJbE_B?*f#o;Eh5*yp&oa^FMiC5_i3x<2f@c@=z1b zV8h3W^)3g|(tG*QIV(}X0Yyx=&JT5W$?W)^2Y|6ROplbOA=kr_Pd@%#0xxEjdq&BH ztK6bbLp`lCTFiGr0Y8k)(vW8?=RofvttO95MYoeA5C_bxa?aU3Oks&0_f_VY|M*7C z?G$H^-|H-L0NO=-=byKmKuQ4&sGz=y2qXU(cUC#tahp zE4C2G^4e2x_FuRKDB#A}%w_LNq3~0lVh;$dXErt84cv=`F6F~kAC4ge2tEpa=LAR6 zMxrXpHP;01JUUDM%EoJ2AP;P7Brpi>D!r$b&}($(LH>m-8EU;iz4YM#nH89L;otd0 zQjh94XDzX^5uYSg@NrUA~%pau++wofu|To`R;{gx$qb(vJlV` zRANdqP<~tI2r%PpIud&Kq(_XO>6w?7m|fnG@I5z7N+ByQb*`_yjUfGL0rZ0Xe!-Us zQcU;}nm3xLQh`E0+LT(s(!b03Ko0(r5??Ah5Mm2itefjGE*&KSas~7^-{WaQ?lU6e zYky$8vvD{!R$P>GfaNyx<%hsRDPe&?jr#dORaCIz8<5>njEuu4&$eR91pm{sf0I|N z#gkWj(`sLDuOynT_9q1Q$_8nUXU=}#KDTA$-%m_FHH|)foyJ{wccRukxcS~ljrK5? zq9a~C57H)bdaJQS8#n>+5){;LedM&YISG(IMDFy%Tg8v{uH)!k`b+-}wwoj3n(pBA-pG!YlD z0P*}n>)`=<4;eL!|G!>Bgix!`!t8I!nnM*;nn}U=904I^Qr(qZLNSL!jsONaLgFpCXwR>Fqo@CDq!s z{k~3?yph}OeA>SKn?lFKMLmD)v2N3T{;i7)mQ+$Iq?jYKMuDkOHe}O(&@SNbV(Tu84i^{v;wz*D0~T_<75hN|uNOkSEiHT4Vfj~s{_C?{D5 zb>RUm6(9>B0qmtkDjT+X1VuXieZUEL@uGW958$WYW1c5NeTnlf)N&4QL$Ib1gF)C6 zHnD|Aw;rcgb)g@xZ>A#-f7A&$@-Bu3BaaJs{2Ev@xfxz3z#@)U^rp6P(#ffUpi9L# zab0INtPix`YTmZW3+0a}Xt!jLi;zhi-LOLJZZq9q-n|!B3V2xq(G%|l7O@Q?`9T@F zp~~DF4S8rfXoqc-4~vqI-5&H{|8+aMK`+c*>K7gupqsVM3&&=tliQm-2(ax`5!za-FVnFoI@dfS-Np?a7ktr@qzRKg1Sp|3BtMG71*Hy59#7l(8rUv3>2Hheu9L-~r77yGW1 z=MP*l_3R!^DNJm6;%|zVp!Q6%%xcDHmGTZim-?b#E=Q$17WIcW9H(beo&5%#%e!1{W)yW~WIMqq^c`f^W zRSz-qu0he|t{q0V+LP0Z<}Wq0YR)Tmn25HpUb?Mj&kG%+QY+r%S9Gf;#Z_F7CH0$L zL~-~#)1M}xBOf7mTsI;ZkPqK22Rw1TX2RacNH~srkG>#pn${wYpTLnilxp_+CgsYt zwUlKT{k90WdF29U6>H6T>=WT;#9Yl9e%ut1eSYt9Z4c4<;2y7>Th2FcL71C)zOO7gcC*DC-t7j`em{OTsMvGQbLGL}kN%-v1(Cx1+si5p zNrO4{=W>t+{h=9)Q%*R_D&{^dNOkX-I{nkv(n@cGcPzQLL*?eMVy)QL*Ht5HQ{HC^ zOkRhZ+&1t0I(?(&gY)Z|p2zU8{N`xrCoOE4(Madeb3CWXRg4f`IeoPmLA9llw7Q5t z{VZ(5&z`(GZ}?k4bR{>pt4p&iO6r~XZic8wu~j81L#i~o1T+>w*? zxIJtatvh|T^!{_Md)oxzgcbPIp5zO8+B0Rp@A(G?WJ96HT2xQnq{Vadyv2V;ia*`V zEc))_^SSmN|Sx_~V5P@a|W#nNvu}1`GY;71bgNL`}BVr7=@^C zv-*ogX%j2h?I@)L?b@+J0621-#r3vph+x^zrx-TyeNJE?3VH?S6#$2**`A=Una(me0k7^8(fG{F&TO z_|nyqG$9We%-fS_9iJs#^teBjpZdc)YVZNJhs9 zS+9_;uvN+i8lEUCAA4I;*4x&spIk*kZ7)?Bj}=A+4p%KU*%b~)bnQ4otuy9RqDHB& z2When$8t$8wmZA>eZ1D^e5mMF6jT_|jF!;fh~OkLEw8R4^+q4ZZ#eh_uiIHV4hZVh z1Tq;fG{%3D-=C`HP#6PCQ6xrAl+Pup9oFwXV{)i=7W5R1TcN&Zbp1%k!{hjyRl73w zp|BeqVce9%{Vt$kjYC*YBKXb3HAbX`M}ir9-Gt~9hE0eu5=*_7FT*6 zmcD-V%j`qLI>-l8s0+m?X|3_VfZ=#)9UraRyvRO@sJS~deW+15@j z)3BJKoGahS_SZT2MLmUA5Xnnit8*h>MeuwvI*M?xu4XA9S4tk>bzn_t(pp!IB>tii zal3@WmM<`OOz<3GF>l z9+zY_{tdG;xsIuwies?Txi15mCEWuP_9h4WwjFaiKQBt=dqwrHFz^dCCyyd6d#W{u zDL5e+;vj!Gst=lR5>?VP+a!>)l{M@*(&F|0`NM2Exj%26?mB_)%-A6D0k5GRDraKZ zX^HF1w@~}CQr=Sasn9g1(T?0!`er^d&i#Blybm|@j-#atWBQE(0#eh1XOP2{QZ*p! z_o!TH;_bt?bP{E4x*ay!)<0L+53hnB!gZ>mF)p$RW6*=CPJ~q2rU{p7+lMz~=m|em z>xiHIvz6i;iaN@hpKO`;`XiZg;Qtt8QSay-DDs>oIe(A5LA~V5bk=vy%ycgGVR283 z$o$dGX;HzDJw2-FN0{k6-38xHf9raIaQOU2mxF5ewZg~_V8qi_TsVRse?j=N=Cw7| zPCl!I!M8yzf{+F-;TeKBx${{T*JZr(4Q{*#!4DSLbR#h0y8B7LCb`OQTXWV|HcRC& zrI3kHjT^>E;$~~Uka7FP-?BZpsuH^&MINpszaBSDd}~keR(vPQg*o0)#mQ8*%7%=E z)z0@QciUb2J~Y|kPH41Y)F)=|zrqQ9eV2!gL|o~K!BAz<{j$$bh`2jM zL@ojXSu4-iTXGZw?}e%~HODtJH&ADn*3A!lrrid=arqqemaXYH>DRoV9JXct;e*Hf zL`IHVl*xKzb5M0h7tesSy=%&l%g#4X9>SoGTjR_ww-sk%*z4kn{E5P?VVis^=+w;K z107i*eZd-z*ZeJu9%H|&)8%=bm-!yIH05oRus5ulET#F`Hl*U!{0%;J3r_0(?VN>b z-JxW8>lZX;^tXzkao*PdvX&q#0FP3n}gm<6b8Ql^k& z7^kC~9oU(CCdn1Z+Z^GYiOMM2C$bc4=94ZCwTx0@Z6nY?4FjO z)rU6;XVx~$`ZJVwm&R3!AGHd`?Floh`#D+oj{W{1*k9&WLKA(UP0lJS87%{4jUL(GDxgww;|qm(~e_dS** zYtt_pk~1>1-2@YF;%>A)dOs&$c-I$|*ue4YmlX#Wt9>vLpS@q|<2O2&q+A>h%cLG} z-hK{K!x-OVKk;5j_^YU@F8ko}Q8Qk$qaPbb$!A&Rzt6h&hGWR;KPUgnOL$9}8BlE# zo%A#4%}o|3bEn5ptFY92vWNaYxuhfy*wd8`bYHrD**^YQdT_MGvj12X*VA-=yx@NT zoIqp0NpBWjFf4gY=HO^~n;&m)$lLsQb0+U?{@BY#O_JXd8C*ke0i*n)O(S~=-cGTi zQSC$iFYQLv(Gqc%yp2N69lOHcOidllZl-)ELJ;Bg;e%^*eb`D%V`+mci|4TRB$zz3 zTrfqrl6YPsoj;ffPZJUj${YNL$uG8sYn-biuVGhjC+o&G?YVVh99uUIRXtB2J7uV{ zkE|5(ld-3#r2KNSWqsJ8q$WLlOhk`n`)iM^1 zdlF-0p*Xu8w@}pKg<^3XJbSp?9>Q^~9}UOI=O|H|jYn5G7^7^5({|Lx??YWR^EjzA zJWj%U&9)2jdY0sGSOsgp&@?4r(oARd!!|f~q@ZbE4wkEFBv{hq`w4gp-23{KYFbHu ziI16%BU#K84u%I$^8I(w-12g;Oe)EOrJEo80i*Bbm0;LHi8$R=yjg%J zNS&{+6@P75-?zHLR{Uk)Or0y-zi5L5Bc>J|%$6UArEt{kk?XU}tbMT_Remhvy zpKeQib^9qlsj}y%crGO?_^-(KbA4&zc#t%cT0X#V5Jo*-U z-xQxfMES+jL4V|;QF$Kd7Y6`xVT|dRM|ZFvRb&3xMdHRa&|MhwDXI8bMxwk}GYq%K z0T7_imO72MhmlZfUnZ>Xo%hLu#1{Wzu-(*_#`uE}=K!?yB~t@&kK}MZ6H0 z=1WEu?!)s@$cq!vv1h#T5?&e&_l2d=cOUA-;!p4d(AwmEAlRu#f&DE_-qoy$;^k8s z3^va@%7$jCy)e{XNy`?%HYQqM;?by#G;u@O*S8{0akTLtYYfY{7?2+00q}c01Drc|cR7>{iWyxN3p7HitonvCJ z)GyRtjfygR74sqXI`D5wgG#sX+`aI z;9;KmoIE_1Q~KhI+#3S_T+I6o0bf{Bj9HX-R_7N9=oVH<1&8fuD)-ZWMy{%Yt|N6l z$LH+DgIUh*Gg`>m{XSEJClqEmdtEDHaQw|O%-|aea|TbkkYCPTdBHS88!m7eI`V>; zp*OT<=668(Nm|yLWaw4c`g2=@^{cd`*7tgV$F`WC&Dy`z1e|Mx{|N9@Q{oA@Q`{SOXvNIn!`@6Yt_HTbaKl_(IZ)$&s^PK(d z&WrYUYsKtu7>ev)rj=rU=T>08ef8l+ZA>RD@T7Qo7M)6tnY~tXFna=a)(w1Sk~eeKeKfG8 zQQp{DsB_iNLXvig2*FQL{vdZr*!M27cE|?fcI?vTsD{yiTmJH{xPxGBJiUV{;B1OK z1>p7RnG|K;fXxA=;r<9)sl+0f1nkHi>=V&o>(j#pO%|s!6M2WF{K_0QcNn|< z#tw^qPaXEbfjR7IGvcsG?>UEEci+7lDNlheQ7&Ct)Rc3|eSGFobLJF( zG}@(`&9PI+)U(34L7sX-4jE5k`yWAt#?9pYkURO