From 21393aba78b5933994a052d556a6640807aad157 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 15 Jul 2022 01:15:24 +0300 Subject: [PATCH 01/30] Move all util methods into a separate module This commit moves all util methods that have previously been in the test class into a separate module. This module is built into a shadow jar which needs to be added into the user's project dependencies in order for our generated tests to be able to use its functionality. --- settings.gradle | 1 + utbot-codegen-utils/build.gradle | 16 + .../framework/codegen/utils/MockUtils.java | 10 + .../framework/codegen/utils/UtUtils.java | 376 ++++++++++++++++++ .../framework/plugin/api/util/CodegenUtil.kt | 14 + .../framework/codegen/model/CodeGenerator.kt | 1 + .../constructor/builtin/UtilMethodBuiltins.kt | 293 +++++++++----- .../model/constructor/context/CgContext.kt | 76 ++-- .../tree/CgCallableAccessManager.kt | 55 +-- .../constructor/tree/CgFieldStateManager.kt | 4 +- .../constructor/tree/CgMethodConstructor.kt | 12 +- .../tree/CgTestClassConstructor.kt | 12 +- .../constructor/tree/CgVariableConstructor.kt | 4 +- .../constructor/tree/TestFrameworkManager.kt | 27 +- .../util/CgStatementConstructor.kt | 6 +- .../constructor/util/ConstructorUtils.kt | 25 +- .../framework/codegen/model/tree/CgElement.kt | 18 +- .../codegen/model/util/DependencyPatterns.kt | 6 +- .../codegen/model/util/DependencyUtils.kt | 1 + .../framework/codegen/model/util/DslUtil.kt | 15 +- .../model/visitor/CgAbstractRenderer.kt | 18 +- .../codegen/model/visitor/UtilMethods.kt | 52 ++- .../plugin/models/GenerateTestsModel.kt | 1 + .../plugin/ui/GenerateTestsDialogWindow.kt | 3 + .../plugin/ui/utils/LibraryMatcher.kt | 11 +- 25 files changed, 800 insertions(+), 257 deletions(-) create mode 100644 utbot-codegen-utils/build.gradle create mode 100644 utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java create mode 100644 utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java create mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt diff --git a/settings.gradle b/settings.gradle index c6ea552e41..b9b97ed841 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ pluginManagement { rootProject.name = 'utbot' +include 'utbot-codegen-utils' include 'utbot-core' include 'utbot-framework' include 'utbot-framework-api' diff --git a/utbot-codegen-utils/build.gradle b/utbot-codegen-utils/build.gradle new file mode 100644 index 0000000000..78bb2fa992 --- /dev/null +++ b/utbot-codegen-utils/build.gradle @@ -0,0 +1,16 @@ +plugins { + id "com.github.johnrengelman.shadow" version "6.1.0" +} + +apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" + +shadowJar { + configurations = [project.configurations.compileClasspath] + archiveVersion.set(codegen_utils_version) + archiveClassifier.set('') + minimize() +} + +dependencies { + compileOnly group: 'org.mockito', name: 'mockito-core', version: mockito_version +} diff --git a/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java new file mode 100644 index 0000000000..706659392e --- /dev/null +++ b/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java @@ -0,0 +1,10 @@ +package org.utbot.framework.codegen.utils; + +final class MockUtils { + private MockUtils() {} + + // TODO: for now we have only Mockito but it can be changed in the future + public static boolean isMock(Object obj) { + return org.mockito.Mockito.mockingDetails(obj).isMock(); + } +} diff --git a/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java new file mode 100644 index 0000000000..230b31dfa3 --- /dev/null +++ b/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java @@ -0,0 +1,376 @@ +package org.utbot.framework.codegen.utils; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.Objects; + +public final class UtUtils { + private UtUtils() {} + + public static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { + java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); + for (java.lang.reflect.Field field : fields) { + String fieldName = field.getName(); + if (field.isEnumConstant() && fieldName.equals(name)) { + field.setAccessible(true); + + return field.get(null); + } + } + + return null; + } + + public static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { + java.lang.reflect.Field field; + Class originClass = clazz; + do { + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + return field.get(null); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } while (clazz != null); + + throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + originClass); + } + + public static Object getFieldValue(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException { + Class clazz = obj.getClass(); + java.lang.reflect.Field field; + do { + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + return field.get(obj); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } while (clazz != null); + + throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + obj.getClass()); + } + + public static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field field; + + do { + try { + field = clazz.getDeclaredField(fieldName); + } catch (Exception e) { + clazz = clazz.getSuperclass(); + field = null; + } + } while (field == null); + + java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + field.setAccessible(true); + field.set(null, fieldValue); + } + + public static void setField(Object object, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { + Class clazz = object.getClass(); + java.lang.reflect.Field field; + + do { + try { + field = clazz.getDeclaredField(fieldName); + } catch (Exception e) { + clazz = clazz.getSuperclass(); + field = null; + } + } while (field == null); + + java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + field.setAccessible(true); + field.set(object, fieldValue); + } + + public static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { + Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); + + for (int i = 0; i < values.length; i++) { + java.lang.reflect.Array.set(array, i, values[i]); + } + + return (Object[]) array; + } + + public 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); + } + + + public 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); + } + + static class FieldsPair { + final Object o1; + final Object o2; + + public FieldsPair(Object o1, Object o2) { + this.o1 = o1; + this.o2 = o2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldsPair that = (FieldsPair) o; + return Objects.equals(o1, that.o1) && Objects.equals(o2, that.o2); + } + + @Override + public int hashCode() { + return Objects.hash(o1, o2); + } + } + + public static boolean deepEquals(Object o1, Object o2, boolean mockFrameworkUsed) { + return deepEquals(o1, o2, new java.util.HashSet<>(), mockFrameworkUsed); + } + + private static boolean deepEquals(Object o1, Object o2, java.util.Set visited, boolean mockFrameworkUsed) { + visited.add(new FieldsPair(o1, o2)); + + if (o1 == o2) { + return true; + } + + if (o1 == null || o2 == null) { + return false; + } + + if (o1 instanceof Iterable) { + if (!(o2 instanceof Iterable)) { + return false; + } + + return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited, mockFrameworkUsed); + } + + if (o2 instanceof Iterable) { + return false; + } + + if (o1 instanceof java.util.stream.Stream) { + if (!(o2 instanceof java.util.stream.Stream)) { + return false; + } + + return streamsDeepEquals((java.util.stream.Stream) o1, (java.util.stream.Stream) o2, visited, mockFrameworkUsed); + } + + if (o2 instanceof java.util.stream.Stream) { + return false; + } + + if (o1 instanceof java.util.Map) { + if (!(o2 instanceof java.util.Map)) { + return false; + } + + return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited, mockFrameworkUsed); + } + + if (o2 instanceof java.util.Map) { + return false; + } + + Class firstClass = o1.getClass(); + if (firstClass.isArray()) { + if (!o2.getClass().isArray()) { + return false; + } + + // Primitive arrays should not appear here + return arraysDeepEquals(o1, o2, visited, mockFrameworkUsed); + } + + // common classes + + // Check if class has custom equals method (including wrappers and strings) + // It is very important to check it here but not earlier because iterables and maps also have custom equals + // based on elements equals. + // + // Class MockUtils uses Mockito and will only be loaded if mockFrameworkUsed == true. + // In this case we know that mockito-core is on the runtime classpath, so MockUtils#isMock will work fine. + // Otherwise, call to MockUtils#isMock will not be performed, so MockUtils class will not be loaded at all. + if (hasCustomEquals(firstClass) && (!mockFrameworkUsed || !MockUtils.isMock(o1))) { + return o1.equals(o2); + } + + // common classes without custom equals, use comparison by fields + final java.util.List fields = new java.util.ArrayList<>(); + while (firstClass != Object.class) { + fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); + // Interface should not appear here + firstClass = firstClass.getSuperclass(); + } + + for (java.lang.reflect.Field field : fields) { + field.setAccessible(true); + try { + final Object field1 = field.get(o1); + final Object field2 = field.get(o2); + if (!visited.contains(new FieldsPair(field1, field2)) && !deepEquals(field1, field2, visited, mockFrameworkUsed)) { + return false; + } + } catch (IllegalArgumentException e) { + return false; + } catch (IllegalAccessException e) { + // should never occur because field was set accessible + return false; + } + } + + return true; + } + + public static boolean arraysDeepEquals(Object arr1, Object arr2, boolean mockFrameworkUsed) { + return arraysDeepEquals(arr1, arr2, new HashSet<>(), mockFrameworkUsed); + } + + private static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited, boolean mockFrameworkUsed) { + 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++) { + Object item1 = java.lang.reflect.Array.get(arr1, i); + Object item2 = java.lang.reflect.Array.get(arr2, i); + if (!deepEquals(item1, item2, visited, mockFrameworkUsed)) { + return false; + } + } + + return true; + } + + public static boolean iterablesDeepEquals(Iterable i1, Iterable i2, boolean mockFrameworkUsed) { + return iterablesDeepEquals(i1, i2, new HashSet<>(), mockFrameworkUsed); + } + + private static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited, boolean mockFrameworkUsed) { + 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, mockFrameworkUsed)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + + public static boolean streamsDeepEquals( + java.util.stream.Stream s1, + java.util.stream.Stream s2, + boolean mockFrameworkUsed + ) { + return streamsDeepEquals(s1, s2, new HashSet<>(), mockFrameworkUsed); + } + + private static boolean streamsDeepEquals( + java.util.stream.Stream s1, + java.util.stream.Stream s2, + java.util.Set visited, + boolean mockFrameworkUsed + ) { + final java.util.Iterator firstIterator = s1.iterator(); + final java.util.Iterator secondIterator = s2.iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited, mockFrameworkUsed)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + + public static boolean mapsDeepEquals( + java.util.Map m1, + java.util.Map m2, + boolean mockFrameworkUsed + ) { + return mapsDeepEquals(m1, m2, new HashSet<>(), mockFrameworkUsed); + } + + private static boolean mapsDeepEquals( + java.util.Map m1, + java.util.Map m2, + java.util.Set visited, + boolean mockFrameworkUsed + ) { + final java.util.Iterator> firstIterator = m1.entrySet().iterator(); + final java.util.Iterator> secondIterator = m2.entrySet().iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + final java.util.Map.Entry firstEntry = firstIterator.next(); + final java.util.Map.Entry secondEntry = secondIterator.next(); + + if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited, mockFrameworkUsed)) { + return false; + } + + if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited, mockFrameworkUsed)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + + public static boolean hasCustomEquals(Class clazz) { + while (!Object.class.equals(clazz)) { + try { + clazz.getDeclaredMethod("equals", Object.class); + return true; + } catch (Exception e) { + // Interface should not appear here + clazz = clazz.getSuperclass(); + } + } + + return false; + } + + public static int getArrayLength(Object arr) { + return java.lang.reflect.Array.getLength(arr); + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt new file mode 100644 index 0000000000..fedb9855fd --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt @@ -0,0 +1,14 @@ +package org.utbot.framework.plugin.api.util + +data class Patterns( + val moduleLibraryPatterns: List, + val libraryPatterns: List, +) + +val codegenUtilsLibraryPatterns: Patterns + get() = Patterns( + moduleLibraryPatterns = listOf(CODEGEN_UTILS_JAR_PATTERN), + libraryPatterns = emptyList() + ) + +private val CODEGEN_UTILS_JAR_PATTERN = Regex("utbot-codegen-utils-[0-9](\\.[0-9]+){2}") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index bf6fd56194..4db4212f8c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -22,6 +22,7 @@ import org.utbot.framework.codegen.model.constructor.TestClassModel class CodeGenerator( private val classUnderTest: ClassId, paramNames: MutableMap> = mutableMapOf(), + codegenUtilsLibraryUsed: Boolean = false, testFramework: TestFramework = TestFramework.defaultItem, mockFramework: MockFramework? = MockFramework.defaultItem, staticsMocking: StaticsMocking = StaticsMocking.defaultItem, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index 9169e42c44..e86aea33e2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -3,10 +3,12 @@ package org.utbot.framework.codegen.model.constructor.builtin import org.utbot.framework.codegen.MockitoStaticMocking import org.utbot.framework.codegen.model.constructor.util.utilMethodId import org.utbot.framework.codegen.model.tree.CgClassId +import org.utbot.framework.codegen.utils.UtUtils import org.utbot.framework.plugin.api.BuiltinClassId 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.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.jClass @@ -14,137 +16,238 @@ 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 kotlin.reflect.jvm.javaMethod /** * Set of ids of all possible util methods for a given class * The class may actually not have some of these methods if they * are not required in the process of code generation */ -internal val ClassId.possibleUtilMethodIds: Set - get() = setOf( - getUnsafeInstanceMethodId, - createInstanceMethodId, - createArrayMethodId, - setFieldMethodId, - setStaticFieldMethodId, - getFieldValueMethodId, - getStaticFieldValueMethodId, - getEnumConstantByNameMethodId, - deepEqualsMethodId, - arraysDeepEqualsMethodId, - iterablesDeepEqualsMethodId, - streamsDeepEqualsMethodId, - mapsDeepEqualsMethodId, - hasCustomEqualsMethodId, - getArrayLengthMethodId - ) - -internal val ClassId.getUnsafeInstanceMethodId: MethodId - get() = utilMethodId( +internal abstract class UtilMethodProvider(val utilClassId: ClassId) { + val utilMethodIds: Set + get() = setOf( + getUnsafeInstanceMethodId, + createInstanceMethodId, + createArrayMethodId, + setFieldMethodId, + setStaticFieldMethodId, + getFieldValueMethodId, + getStaticFieldValueMethodId, + getEnumConstantByNameMethodId, + deepEqualsMethodId, + arraysDeepEqualsMethodId, + iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, + mapsDeepEqualsMethodId, + hasCustomEqualsMethodId, + getArrayLengthMethodId + ) + + abstract val getUnsafeInstanceMethodId: MethodId + + /** + * Method that creates instance using Unsafe + */ + abstract val createInstanceMethodId: MethodId + + abstract val createArrayMethodId: MethodId + + abstract val setFieldMethodId: MethodId + + abstract val setStaticFieldMethodId: MethodId + + abstract val getFieldValueMethodId: MethodId + + abstract val getStaticFieldValueMethodId: MethodId + + abstract val getEnumConstantByNameMethodId: MethodId + + abstract val deepEqualsMethodId: MethodId + + abstract val arraysDeepEqualsMethodId: MethodId + + abstract val iterablesDeepEqualsMethodId: MethodId + + abstract val streamsDeepEqualsMethodId: MethodId + + abstract val mapsDeepEqualsMethodId: MethodId + + abstract val hasCustomEqualsMethodId: MethodId + + abstract val getArrayLengthMethodId: MethodId +} + +internal object LibraryUtilMethodProvider : UtilMethodProvider(UtUtils::class.id) { + override val getUnsafeInstanceMethodId: MethodId = UtUtils::getUnsafeInstance.javaMethod!!.executableId + + override val createInstanceMethodId: MethodId = UtUtils::createInstance.javaMethod!!.executableId + + override val createArrayMethodId: MethodId = UtUtils::createArray.javaMethod!!.executableId + + override val setFieldMethodId: MethodId = UtUtils::setField.javaMethod!!.executableId + + override val setStaticFieldMethodId: MethodId = UtUtils::setStaticField.javaMethod!!.executableId + + override val getFieldValueMethodId: MethodId = UtUtils::getFieldValue.javaMethod!!.executableId + + override val getStaticFieldValueMethodId: MethodId = UtUtils::getStaticFieldValue.javaMethod!!.executableId + + override val getEnumConstantByNameMethodId: MethodId = UtUtils::getEnumConstantByName.javaMethod!!.executableId + + override val deepEqualsMethodId: MethodId = UtUtils::deepEquals.javaMethod!!.executableId + + override val arraysDeepEqualsMethodId: MethodId = UtUtils::arraysDeepEquals.javaMethod!!.executableId + + override val iterablesDeepEqualsMethodId: MethodId = UtUtils::iterablesDeepEquals.javaMethod!!.executableId + + override val streamsDeepEqualsMethodId: MethodId = UtUtils::streamsDeepEquals.javaMethod!!.executableId + + override val mapsDeepEqualsMethodId: MethodId = UtUtils::mapsDeepEquals.javaMethod!!.executableId + + override val hasCustomEqualsMethodId: MethodId = UtUtils::hasCustomEquals.javaMethod!!.executableId + + override val getArrayLengthMethodId: MethodId = UtUtils::getArrayLength.javaMethod!!.executableId + +// fun patterns(): Patterns { +// return Patterns( +// moduleLibraryPatterns = emptyList(), +// libraryPatterns = listOf(CODEGEN_UTILS_JAR_PATTERN) +// ) +// } +} + +internal class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId) { + override val getUnsafeInstanceMethodId: MethodId + get() = utilClassId.utilMethodId( name = "getUnsafeInstance", returnType = Unsafe::class.id, - ) + ) -/** - * Method that creates instance using Unsafe - */ -internal val ClassId.createInstanceMethodId: MethodId - get() = utilMethodId( + override val createInstanceMethodId: MethodId + get() = utilClassId.utilMethodId( name = "createInstance", returnType = CgClassId(objectClassId, isNullable = true), arguments = arrayOf(stringClassId) - ) + ) -internal val ClassId.createArrayMethodId: MethodId - get() = utilMethodId( + override val createArrayMethodId: MethodId + get() = utilClassId.utilMethodId( name = "createArray", returnType = Array::class.id, arguments = arrayOf(stringClassId, intClassId, Array::class.id) - ) + ) -internal val ClassId.setFieldMethodId: MethodId - get() = utilMethodId( + override val setFieldMethodId: MethodId + get() = utilClassId.utilMethodId( name = "setField", returnType = voidClassId, arguments = arrayOf(objectClassId, stringClassId, objectClassId) - ) + ) -internal val ClassId.setStaticFieldMethodId: MethodId - get() = utilMethodId( + override val setStaticFieldMethodId: MethodId + get() = utilClassId.utilMethodId( name = "setStaticField", returnType = voidClassId, arguments = arrayOf(Class::class.id, stringClassId, objectClassId) - ) + ) -internal val ClassId.getFieldValueMethodId: MethodId - get() = utilMethodId( + override val getFieldValueMethodId: MethodId + get() = utilClassId.utilMethodId( name = "getFieldValue", returnType = objectClassId, arguments = arrayOf(objectClassId, stringClassId) - ) + ) -internal val ClassId.getStaticFieldValueMethodId: MethodId - get() = utilMethodId( + override val getStaticFieldValueMethodId: MethodId + get() = utilClassId.utilMethodId( name = "getStaticFieldValue", returnType = objectClassId, arguments = arrayOf(Class::class.id, stringClassId) - ) + ) -internal val ClassId.getEnumConstantByNameMethodId: MethodId - get() = utilMethodId( + override val getEnumConstantByNameMethodId: MethodId + get() = utilClassId.utilMethodId( name = "getEnumConstantByName", returnType = objectClassId, arguments = arrayOf(Class::class.id, stringClassId) - ) - -internal val ClassId.deepEqualsMethodId: MethodId - get() = utilMethodId( - name = "deepEquals", - returnType = booleanClassId, - arguments = arrayOf(objectClassId, objectClassId) - ) - -internal val ClassId.arraysDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "arraysDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(objectClassId, objectClassId) - ) - -internal val ClassId.iterablesDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "iterablesDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(java.lang.Iterable::class.id, java.lang.Iterable::class.id) - ) - -internal val ClassId.streamsDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "streamsDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id) - ) - -internal val ClassId.mapsDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "mapsDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(java.util.Map::class.id, java.util.Map::class.id) - ) - -internal val ClassId.hasCustomEqualsMethodId: MethodId - get() = utilMethodId( - name = "hasCustomEquals", - returnType = booleanClassId, - arguments = arrayOf(Class::class.id) - ) - -internal val ClassId.getArrayLengthMethodId: MethodId - get() = utilMethodId( - name = "getArrayLength", - returnType = intClassId, - arguments = arrayOf(objectClassId) - ) + ) + + override val deepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "deepEquals", + returnType = booleanClassId, + arguments = arrayOf(objectClassId, objectClassId) + ) + + override val arraysDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "arraysDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(objectClassId, objectClassId) + ) + + override val iterablesDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "iterablesDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(java.lang.Iterable::class.id, java.lang.Iterable::class.id) + ) + + override val streamsDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "streamsDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id) + ) + + override val mapsDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "mapsDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(java.util.Map::class.id, java.util.Map::class.id) + ) + + override val hasCustomEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "hasCustomEquals", + returnType = booleanClassId, + arguments = arrayOf(Class::class.id) + ) + + override val getArrayLengthMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getArrayLength", + returnType = intClassId, + arguments = arrayOf(objectClassId) + ) + + //WARN: if you make changes in the following sets of exceptions, + //don't forget to change them in hardcoded [UtilMethods] as well + internal fun findExceptionTypesOf(methodId: MethodId): Set { + if (methodId !in utilMethodIds) return emptySet() + + with(this) { + return when (methodId) { + getEnumConstantByNameMethodId -> setOf(java.lang.IllegalAccessException::class.id) + getStaticFieldValueMethodId, + getFieldValueMethodId, + setStaticFieldMethodId, + setFieldMethodId -> setOf(java.lang.IllegalAccessException::class.id, java.lang.NoSuchFieldException::class.id) + createInstanceMethodId -> setOf(Exception::class.id) + getUnsafeInstanceMethodId -> setOf(java.lang.ClassNotFoundException::class.id, java.lang.NoSuchFieldException::class.id, java.lang.IllegalAccessException::class.id) + createArrayMethodId -> setOf(java.lang.ClassNotFoundException::class.id) + deepEqualsMethodId, + arraysDeepEqualsMethodId, + iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, + mapsDeepEqualsMethodId, + hasCustomEqualsMethodId, + getArrayLengthMethodId -> emptySet() + else -> error("Unknown util method $this") + } + } + } +} /** * [MethodId] for [AutoCloseable.close]. diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 17512357d6..3c01709131 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -7,21 +7,6 @@ import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.StaticsMocking import org.utbot.framework.codegen.TestFramework -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.getArrayLengthMethodId -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.getUnsafeInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId -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.possibleUtilMethodIds -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.tree.Block import org.utbot.framework.codegen.model.constructor.util.EnvironmentFieldStateCache import org.utbot.framework.codegen.model.constructor.util.importIfNeeded @@ -41,6 +26,9 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf import org.utbot.framework.codegen.model.constructor.CgMethodTestSet +import org.utbot.framework.codegen.model.constructor.builtin.LibraryUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider import org.utbot.framework.codegen.model.constructor.TestClassContext import org.utbot.framework.codegen.model.constructor.TestClassModel import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId @@ -85,6 +73,15 @@ internal interface CgContextOwner { // test class currently being generated (if series of nested classes is generated, it is the innermost one) var currentTestClass: ClassId + // Flag indicating if there is a dependency on UtBot codegen utils library in the user's project. + // The sources of this library can be found in the module utbot-codegen-utils. + // If the library is in the dependencies, code generation will use util methods from it. + // Otherwise, util methods will be generated in the test class directly. + val codegenUtilsLibraryUsed: Boolean + + // provider of util methods used for the test class currently being generated + val currentUtilMethodProvider: UtilMethodProvider + // current executable under test var currentExecutable: ExecutableId? @@ -323,6 +320,14 @@ internal interface CgContextOwner { } } + /** + * [ClassId] of a class that contains util methods. + * For example, it can be the current test class, or it can be a `UtUtils` class from module `utbot-codegen-utils`. + * The latter is used when our codegen utils library is included in the user's project dependencies. + */ + val utilsClassId: ClassId + get() = currentUtilMethodProvider.utilClassId + /** * Check whether a method is an util method of the current class */ @@ -334,56 +339,57 @@ internal interface CgContextOwner { * When this method is used with type cast in Kotlin, this type cast have to be safety */ val MethodId.isGetFieldUtilMethod: Boolean - get() = isUtil && (name == getFieldValue.name || name == getStaticFieldValue.name) + get() = this == currentUtilMethodProvider.getFieldValueMethodId + || this == currentUtilMethodProvider.getStaticFieldValueMethodId val testClassThisInstance: CgThisInstance // util methods of current test class val getUnsafeInstance: MethodId - get() = outerMostTestClass.getUnsafeInstanceMethodId + get() = currentUtilMethodProvider.getUnsafeInstanceMethodId val createInstance: MethodId - get() = outerMostTestClass.createInstanceMethodId + get() = currentUtilMethodProvider.createInstanceMethodId val createArray: MethodId - get() = outerMostTestClass.createArrayMethodId + get() = currentUtilMethodProvider.createArrayMethodId val setField: MethodId - get() = outerMostTestClass.setFieldMethodId + get() = currentUtilMethodProvider.setFieldMethodId val setStaticField: MethodId - get() = outerMostTestClass.setStaticFieldMethodId + get() = currentUtilMethodProvider.setStaticFieldMethodId val getFieldValue: MethodId - get() = outerMostTestClass.getFieldValueMethodId + get() = currentUtilMethodProvider.getFieldValueMethodId val getStaticFieldValue: MethodId - get() = outerMostTestClass.getStaticFieldValueMethodId + get() = currentUtilMethodProvider.getStaticFieldValueMethodId val getEnumConstantByName: MethodId - get() = outerMostTestClass.getEnumConstantByNameMethodId + get() = currentUtilMethodProvider.getEnumConstantByNameMethodId val deepEquals: MethodId - get() = outerMostTestClass.deepEqualsMethodId + get() = currentUtilMethodProvider.deepEqualsMethodId val arraysDeepEquals: MethodId - get() = outerMostTestClass.arraysDeepEqualsMethodId + get() = currentUtilMethodProvider.arraysDeepEqualsMethodId val iterablesDeepEquals: MethodId - get() = outerMostTestClass.iterablesDeepEqualsMethodId + get() = currentUtilMethodProvider.iterablesDeepEqualsMethodId val streamsDeepEquals: MethodId - get() = outerMostTestClass.streamsDeepEqualsMethodId + get() = currentUtilMethodProvider.streamsDeepEqualsMethodId val mapsDeepEquals: MethodId - get() = outerMostTestClass.mapsDeepEqualsMethodId + get() = currentUtilMethodProvider.mapsDeepEqualsMethodId val hasCustomEquals: MethodId - get() = outerMostTestClass.hasCustomEqualsMethodId + get() = currentUtilMethodProvider.hasCustomEqualsMethodId val getArrayLength: MethodId - get() = outerMostTestClass.getArrayLengthMethodId + get() = currentUtilMethodProvider.getArrayLengthMethodId } /** @@ -391,6 +397,7 @@ internal interface CgContextOwner { */ internal data class CgContext( override val classUnderTest: ClassId, + override val codegenUtilsLibraryUsed: Boolean, override var currentExecutable: ExecutableId? = null, override val collectedExceptions: MutableSet = mutableSetOf(), override val collectedMethodAnnotations: MutableSet = mutableSetOf(), @@ -460,6 +467,13 @@ internal data class CgContext( ) } + override val currentUtilMethodProvider: UtilMethodProvider by lazy { + when { + codegenUtilsLibraryUsed -> LibraryUtilMethodProvider + else -> TestClassUtilMethodProvider(currentTestClass) + } + } + override lateinit var currentTestClass: ClassId override fun withTestClassFileScope(block: () -> R): R { 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 83b506f889..e3e4c13235 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 @@ -3,36 +3,23 @@ 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.TestClassUtilMethodProvider 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.builtin.streamsDeepEqualsMethodId 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 import org.utbot.framework.codegen.model.constructor.util.classCgClassId import org.utbot.framework.codegen.model.constructor.util.getAmbiguousOverloadsOf import org.utbot.framework.codegen.model.constructor.util.importIfNeeded +import org.utbot.framework.codegen.model.constructor.util.isTestClassUtil import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAssignment @@ -50,7 +37,6 @@ 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 @@ -121,13 +107,15 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA } private fun newMethodCall(methodId: MethodId) { - if (methodId.isUtil) requiredUtilMethods += methodId + if (isTestClassUtil(methodId)) 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 { addExceptionIfNeeded(it) } + if (isTestClassUtil(methodId)) { + (currentUtilMethodProvider as TestClassUtilMethodProvider) + .findExceptionTypesOf(methodId) + .forEach { addExceptionIfNeeded(it) } return } @@ -155,33 +143,6 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA } } - //WARN: if you make changes in the following sets of exceptions, - //don't forget to change them in hardcoded [UtilMethods] as well - private fun BuiltinMethodId.findExceptionTypes(): Set { - if (!this.isUtil) return emptySet() - - with(outerMostTestClass) { - return when (this@findExceptionTypes) { - getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id) - getStaticFieldValueMethodId, - getFieldValueMethodId, - setStaticFieldMethodId, - setFieldMethodId -> setOf(IllegalAccessException::class.id, NoSuchFieldException::class.id) - createInstanceMethodId -> setOf(Exception::class.id) - getUnsafeInstanceMethodId -> setOf(ClassNotFoundException::class.id, NoSuchFieldException::class.id, IllegalAccessException::class.id) - createArrayMethodId -> setOf(ClassNotFoundException::class.id) - deepEqualsMethodId, - arraysDeepEqualsMethodId, - iterablesDeepEqualsMethodId, - streamsDeepEqualsMethodId, - 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. @@ -265,7 +226,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA * @return true if a method can be called with the given arguments without reflection */ private fun MethodId.canBeCalledWith(caller: CgExpression?, args: List): Boolean = - (isUtil || isAccessibleFrom(testClassPackageName)) + (isTestClassUtil(this) || isAccessibleFrom(testClassPackageName)) && caller canBeReceiverOf this && args canBeArgsOf this diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt index ea2a141db4..82b3fbb70c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt @@ -231,7 +231,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext) val expression = when (val newElement = path[index++]) { is FieldAccess -> { val field = newElement.field - testClassThisInstance[getFieldValue](prev, stringLiteral(field.name)) + utilsClassId[getFieldValue](prev, stringLiteral(field.name)) } is ArrayElementAccess -> { Array::class.id[getArrayElement](prev, newElement.index) @@ -256,7 +256,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext) } else { newVar(classCgClassId) { Class::class.id[forName](owner.name) } } - newVar(objectClassId) { testClassThisInstance[getStaticFieldValue](ownerClass, stringLiteral(firstField.name)) } + newVar(objectClassId) { utilsClassId[getStaticFieldValue](ownerClass, stringLiteral(firstField.name)) } } val path = fieldPath.elements val remainingPath = fieldPath.copy(elements = path.drop(1)) 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 26262b988f..fee6dc125d 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 @@ -231,7 +231,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val declaringClassVar = newVar(classCgClassId) { Class::class.id[forName](declaringClass.name) } - testClassThisInstance[getStaticFieldValue](declaringClassVar, field.name) + utilsClassId[getStaticFieldValue](declaringClassVar, field.name) } } // remember the previous value of a static field to recover it at the end of the test @@ -257,7 +257,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val declaringClassVar = newVar(classCgClassId) { Class::class.id[forName](declaringClass.name) } - +testClassThisInstance[setStaticField](declaringClassVar, field.name, fieldValue) + +utilsClassId[setStaticField](declaringClassVar, field.name, fieldValue) } } } @@ -268,7 +268,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c field.declaringClass[field] `=` prevValue } else { val declaringClass = getClassOf(field.declaringClass) - +testClassThisInstance[setStaticField](declaringClass, field.name, prevValue) + +utilsClassId[setStaticField](declaringClass, field.name, prevValue) } } } @@ -721,12 +721,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val cgGetLengthDeclaration = CgDeclaration( intClassId, variableConstructor.constructVarName("${expected.name}Size"), - expected.length(this, testClassThisInstance, getArrayLength) + expected.length(this@CgMethodConstructor) ) currentBlock += cgGetLengthDeclaration currentBlock += assertions[assertEquals]( cgGetLengthDeclaration.variable, - actual.length(this, testClassThisInstance, getArrayLength) + actual.length(this@CgMethodConstructor) ).toStatement() return cgGetLengthDeclaration @@ -1048,7 +1048,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c if (variable.type.hasField(this) && isAccessibleFrom(testClassPackageName)) { if (jField.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this) } else { - testClassThisInstance[getFieldValue](variable, stringLiteral(name)) + utilsClassId[getFieldValue](variable, stringLiteral(name)) } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index 815110caa4..1d5e570a43 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -91,16 +91,14 @@ internal class CgTestClassConstructor(val context: CgContext) : testMethodRegions += executableUnderTestCluster } - val utilMethods = if (currentTestClass == outerMostTestClass) - createUtilMethods() - else - emptyList() - - val additionalMethods = currentTestClassContext.cgDataProviderMethods + utilMethods +// val utilMethods = if (currentTestClass == outerMostTestClass) +// createUtilMethods() +// else +// emptyList() dataProvidersAndUtilMethodsRegion += CgStaticsRegion( "Data providers and utils methods", - additionalMethods + currentTestClassContext.cgDataProviderMethods ) } // It is important that annotations, superclass and interfaces assignment is run after 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 baa62c3890..6154bee0fa 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 @@ -118,7 +118,7 @@ internal class CgVariableConstructor(val context: CgContext) : val obj = if (model.isMock) { mockFrameworkManager.createMockFor(model, baseName) } else { - newVar(model.classId, baseName) { testClassThisInstance[createInstance](model.classId.name) } + newVar(model.classId, baseName) { utilsClassId[createInstance](model.classId.name) } } valueByModelId[model.id] = obj @@ -146,7 +146,7 @@ internal class CgVariableConstructor(val context: CgContext) : fieldAccess `=` variableForField } else { // composite models must not have info about static fields, hence only non-static fields are set here - +testClassThisInstance[setField](obj, fieldId.name, variableForField) + +utilsClassId[setField](obj, fieldId.name, variableForField) } } return obj 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 157328aef6..ce62d18750 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 @@ -3,14 +3,11 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.framework.codegen.Junit4 import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.TestNg +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.codegen.model.constructor.TestClassContext import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId 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.hasCustomEqualsMethodId -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.streamsDeepEqualsMethodId 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 @@ -122,19 +119,25 @@ internal abstract class TestFrameworkManager(val context: CgContext) } open fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall { - requiredUtilMethods += outerMostTestClass.deepEqualsMethodId - requiredUtilMethods += outerMostTestClass.arraysDeepEqualsMethodId - requiredUtilMethods += outerMostTestClass.iterablesDeepEqualsMethodId - requiredUtilMethods += outerMostTestClass.streamsDeepEqualsMethodId - requiredUtilMethods += outerMostTestClass.mapsDeepEqualsMethodId - requiredUtilMethods += outerMostTestClass.hasCustomEqualsMethodId - + // If an util method provider is not TestClassUtilMethodProvider, then we are using util methods from library. + // In this case we don't need to add required util methods to the test class, + // because they are all already in a library. + if (currentUtilMethodProvider is TestClassUtilMethodProvider) { + requiredUtilMethods += setOf( + currentUtilMethodProvider.deepEqualsMethodId, + currentUtilMethodProvider.arraysDeepEqualsMethodId, + currentUtilMethodProvider.iterablesDeepEqualsMethodId, + currentUtilMethodProvider.streamsDeepEqualsMethodId, + currentUtilMethodProvider.mapsDeepEqualsMethodId, + currentUtilMethodProvider.hasCustomEqualsMethodId + ) + } // TODO we cannot use common assertEquals because of using custom deepEquals // For this reason we have to use assertTrue here // Unfortunately, if test with assertTrue fails, it gives non informative message false != true // Thus, we need to provide custom message to assertTrue showing compared objects correctly // SAT-1345 - return assertions[assertTrue](testClassThisInstance[deepEquals](expected, actual)) + return assertions[assertTrue](utilsClassId[deepEquals](expected, actual, mockFrameworkUsed)) } @Suppress("unused") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt index 0ccc973ce3..f3e1ae412a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt @@ -429,7 +429,7 @@ internal class CgStatementConstructorImpl(context: CgContext) : Class::class.id[forName](enumClass.name) } - ExpressionWithType(objectClassId, testClassThisInstance[getEnumConstantByName](enumClassVariable, constant)) + ExpressionWithType(objectClassId, utilsClassId[getEnumConstantByName](enumClassVariable, constant)) } } @@ -448,7 +448,7 @@ internal class CgStatementConstructorImpl(context: CgContext) : } else { ExpressionWithType( objectArrayClassId, - testClassThisInstance[createArray](arrayType.elementClassId!!.name, arraySize) + utilsClassId[createArray](arrayType.elementClassId!!.name, arraySize) ) } } @@ -500,7 +500,7 @@ internal class CgStatementConstructorImpl(context: CgContext) : } expression.type isNotSubtypeOf baseType && !typeAccessible -> { type = if (expression.type.isArray) objectArrayClassId else objectClassId - expr = if (expression is CgMethodCall && expression.executableId.isUtil) { + expr = if (expression is CgMethodCall && isUtil(expression.executableId)) { CgErrorWrapper("${expression.executableId.name} failed", expression) } else { expression diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index 179e9272e0..eb737920a5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -48,6 +48,7 @@ import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.framework.plugin.api.util.arrayLikeName +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.plugin.api.util.builtinStaticMethodId import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectArrayClassId @@ -129,6 +130,20 @@ internal data class CgFieldState(val variable: CgVariable, val model: UtModel) data class ExpressionWithType(val type: ClassId, val expression: CgExpression) +/** + * Check if a method is an util method of the current class + */ +internal fun CgContextOwner.isUtil(method: MethodId): Boolean { + return method in currentUtilMethodProvider.utilMethodIds +} + +/** + * Check if a method is an util method that is declared in the test class (not taken from the codegen utils library) + */ +internal fun CgContextOwner.isTestClassUtil(method: MethodId): Boolean { + return currentUtilMethodProvider is TestClassUtilMethodProvider && isUtil(method) +} + val classCgClassId = CgClassId(Class::class.id, typeParameters = WildcardTypeParameter(), isNullable = false) /** @@ -391,8 +406,14 @@ internal infix fun UtModel.isNotDefaultValueOf(type: ClassId): Boolean = !this.i internal operator fun UtArrayModel.get(index: Int): UtModel = stores[index] ?: constModel -internal fun ClassId.utilMethodId(name: String, returnType: ClassId, vararg arguments: ClassId): MethodId = - BuiltinMethodId(this, name, returnType, arguments.toList()) +internal fun ClassId.utilMethodId( + name: String, + returnType: ClassId, + vararg arguments: ClassId, + // usually util methods are static, so this argument is true by default + isStatic: Boolean = true +): MethodId = + BuiltinMethodId(this, name, returnType, arguments.toList(), isStatic = isStatic) fun ClassId.toImport(): RegularImport = RegularImport(packageName, simpleNameWithEnclosings) 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 4c84ce2a8c..32d57a30b7 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 @@ -131,9 +131,18 @@ data class CgTestClass( val simpleName = id.simpleName } +/** + * Body of the test class. + * @property testMethodRegions regions containing the test methods + * @property staticDeclarationRegions regions containing static declarations. + * This is usually util methods and data providers. + * In Kotlin all static declarations must be grouped together in a companion object. + * In Java there is no such restriction, but for uniformity we are grouping + * Java static declarations together as well. It can also improve code readability. + */ data class CgTestClassBody( val testMethodRegions: List, - val utilsRegion: List>, + val staticDeclarationRegions: List, val nestedClassRegions: List> ) : CgElement { val regions: List> @@ -157,7 +166,10 @@ open class CgSimpleRegion( ) : CgRegion() /** - * Stores data providers for parametrized tests and util methods + * A region that stores some static declarations, e.g. data providers or util methods. + * There may be more than one static region in a class and they all are stored + * in a [CgTestClassBody.staticDeclarationRegions]. + * In case of Kotlin, they all will be rendered inside of a companion object. */ class CgStaticsRegion( override val header: String?, @@ -184,7 +196,7 @@ data class CgExecutableUnderTestCluster( * This is because util methods are hardcoded. On the rendering stage their text * is retrieved by their [MethodId]. * - * [id] identifier of the util method. + * @property id identifier of the util method. */ data class CgUtilMethod(val id: MethodId) : CgElement diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt index 14245d10c0..9958815e1e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt @@ -5,11 +5,7 @@ import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.TestFramework import org.utbot.framework.codegen.TestNg import org.utbot.framework.plugin.api.MockFramework - -data class Patterns( - val moduleLibraryPatterns: List, - val libraryPatterns: List, -) +import org.utbot.framework.plugin.api.util.Patterns fun TestFramework.patterns(): Patterns { val moduleLibraryPatterns = when (this) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt index 4d0e05b39a..fc65e064c5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt @@ -6,6 +6,7 @@ import org.utbot.framework.plugin.api.MockFramework import java.io.File import java.util.jar.JarFile import mu.KotlinLogging +import org.utbot.framework.plugin.api.util.Patterns private val logger = KotlinLogging.logger {} 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 5edbf60ec6..b351855f7a 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 @@ -1,6 +1,7 @@ package org.utbot.framework.codegen.model.util -import org.utbot.framework.codegen.model.constructor.tree.CgCallableAccessManager +import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider +import org.utbot.framework.codegen.model.constructor.tree.CgMethodConstructor import org.utbot.framework.codegen.model.tree.CgArrayElementAccess import org.utbot.framework.codegen.model.tree.CgDecrement import org.utbot.framework.codegen.model.tree.CgEqualTo @@ -15,12 +16,10 @@ import org.utbot.framework.codegen.model.tree.CgIncrement import org.utbot.framework.codegen.model.tree.CgLessThan import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess -import org.utbot.framework.codegen.model.tree.CgThisInstance import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.plugin.api.ClassId 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 @@ -82,19 +81,15 @@ operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess = // Get array length /** - * Returns length field access for array type variable and [getArrayLengthMethodId] call otherwise. + * Returns length field access for array type variable and [UtilMethodProvider.getArrayLengthMethodId] call otherwise. */ -fun CgVariable.length( - cgCallableAccessManager: CgCallableAccessManager, - thisInstance: CgThisInstance, - getArrayLengthMethodId: MethodId -): CgExpression { +internal fun CgVariable.length(methodConstructor: CgMethodConstructor): CgExpression { val thisVariable = this return if (type.isArray) { CgGetLength(thisVariable) } else { - with(cgCallableAccessManager) { thisInstance[getArrayLengthMethodId](thisVariable) } + with(methodConstructor) { utilsClassId[getArrayLength](thisVariable) } } } 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 77b739d174..c838ab1a42 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 @@ -6,6 +6,7 @@ import org.utbot.common.workaround import org.utbot.framework.codegen.Import import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment @@ -197,10 +198,23 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } override fun visit(element: CgUtilMethod) { - context.outerMostTestClass - .utilMethodById(element.id, context) + // If we are rendering a CgUtilMethod, then we know that context.currentUtilMethodProvider + // must be the test class being generated, i.e. it must be TestClassUtilMethodProvider. + // So, the following type cast must always succeed. + // However, in order to avoid unexpected rendering failures (which will cause whole test class generation to fail), + // we make sure that we always have a correct provider by creating it ourselves in case of a failed type cast. + val utilMethodProvider = context.currentUtilMethodProvider as? TestClassUtilMethodProvider + ?: TestClassUtilMethodProvider(context.outerMostTestClass) + utilMethodProvider.utilMethodTextById( + element.id, + context.mockFrameworkUsed, + context.mockFramework, + context.codegenLanguage + ).onSuccess { utilMethodText -> + utilMethodText .split("\n") .forEach { line -> println(line) } + } } // Methods 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 2b403f8730..bc1bba9e73 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 @@ -1,22 +1,7 @@ package org.utbot.framework.codegen.model.visitor import org.utbot.framework.codegen.StaticImport -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.getArrayLengthMethodId -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.getUnsafeInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId -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.setFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.setStaticFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.context.CgContext +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.importIfNeeded import org.utbot.framework.plugin.api.ClassId @@ -29,8 +14,13 @@ import java.lang.reflect.Modifier import java.util.Arrays import java.util.Objects -internal fun ClassId.utilMethodById(id: MethodId, context: CgContext): String = - with(context) { +internal fun TestClassUtilMethodProvider.utilMethodTextById( + id: MethodId, + mockFrameworkUsed: Boolean, + mockFramework: MockFramework, + codegenLanguage: CodegenLanguage +): Result = runCatching { + with(this) { when (id) { getUnsafeInstanceMethodId -> getUnsafeInstance(codegenLanguage) createInstanceMethodId -> createInstance(codegenLanguage) @@ -50,6 +40,7 @@ internal fun ClassId.utilMethodById(id: MethodId, context: CgContext): String = else -> error("Unknown util method for class $this: $id") } } +} fun getEnumConstantByName(language: CodegenLanguage): String = when (language) { @@ -408,11 +399,11 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } } - private boolean deepEquals(Object o1, Object o2) { + private static boolean deepEquals(Object o1, Object o2) { return deepEquals(o1, o2, new java.util.HashSet<>()); } - private boolean deepEquals(Object o1, Object o2, java.util.Set visited) { + private static boolean deepEquals(Object o1, Object o2, java.util.Set visited) { visited.add(new FieldsPair(o1, o2)); if (o1 == o2) { @@ -587,7 +578,7 @@ fun arraysDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { + private static 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; @@ -629,7 +620,7 @@ fun iterablesDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { + private static 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()) { @@ -669,7 +660,7 @@ fun streamsDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean streamsDeepEquals( + private static boolean streamsDeepEquals( java.util.stream.Stream s1, java.util.stream.Stream s2, java.util.Set visited @@ -713,7 +704,7 @@ fun mapsDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean mapsDeepEquals( + private static boolean mapsDeepEquals( java.util.Map m1, java.util.Map m2, java.util.Set visited @@ -769,7 +760,7 @@ fun hasCustomEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private boolean hasCustomEquals(Class clazz) { + private static boolean hasCustomEquals(Class clazz) { while (!Object.class.equals(clazz)) { try { clazz.getDeclaredMethod("equals", Object.class); @@ -818,15 +809,18 @@ fun getArrayLength(codegenLanguage: CodegenLanguage) = } internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { - for (classId in outerMostTestClass.regularImportsByUtilMethod(id, codegenLanguage)) { + // if util methods come from codegen utils library and not from the test class, + // then we don't need to import any other methods, hence we return from method + val utilMethodProvider = currentUtilMethodProvider as? TestClassUtilMethodProvider ?: return + for (classId in utilMethodProvider.regularImportsByUtilMethod(id, codegenLanguage)) { importIfNeeded(classId) } - for (methodId in outerMostTestClass.staticImportsByUtilMethod(id)) { + for (methodId in utilMethodProvider.staticImportsByUtilMethod(id)) { collectedImports += StaticImport(methodId.classId.canonicalName, methodId.name) } } -private fun ClassId.regularImportsByUtilMethod(id: MethodId, codegenLanguage: CodegenLanguage): List { +private fun TestClassUtilMethodProvider.regularImportsByUtilMethod(id: MethodId, codegenLanguage: CodegenLanguage): List { val fieldClassId = Field::class.id return when (id) { getUnsafeInstanceMethodId -> listOf(fieldClassId) @@ -876,4 +870,4 @@ private fun ClassId.regularImportsByUtilMethod(id: MethodId, codegenLanguage: Co // Note: for now always returns an empty list, because no util method // requires static imports, but this may change in the future @Suppress("unused", "unused_parameter") -private fun ClassId.staticImportsByUtilMethod(id: MethodId): List = emptyList() \ No newline at end of file +private fun TestClassUtilMethodProvider.staticImportsByUtilMethod(id: MethodId): List = emptyList() \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt index 66c0a41df7..cf858d4d37 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -52,6 +52,7 @@ data class GenerateTestsModel( lateinit var staticsMocking: StaticsMocking lateinit var parametrizedTestSource: ParametrizedTestSource lateinit var codegenLanguage: CodegenLanguage + var codegenUtilsLibraryInstalled: Boolean = false lateinit var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour lateinit var hangingTestsTimeout: HangingTestsTimeout lateinit var forceStaticMocking: ForceStaticMocking 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 fd2a024f88..80c8b01e61 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 @@ -124,6 +124,7 @@ import org.utbot.intellij.plugin.ui.components.TestFolderComboWithBrowseButton import org.utbot.intellij.plugin.ui.utils.LibrarySearchScope import org.utbot.intellij.plugin.ui.utils.addSourceRootIfAbsent import org.utbot.intellij.plugin.ui.utils.allLibraries +import org.utbot.intellij.plugin.ui.utils.findCodegenUtilsLibrary import org.utbot.intellij.plugin.ui.utils.findFrameworkLibrary import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle @@ -204,6 +205,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m it.isConfigured = staticsMockingConfigured() } + model.codegenUtilsLibraryInstalled = findCodegenUtilsLibrary(model.project, model.testModule) != null + // Configure notification urls callbacks TestsReportNotifier.urlOpeningListener.callbacks[TestReportUrlOpeningListener.mockitoSuffix]?.plusAssign { configureMockFramework() diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt index af19f7f275..e4f13b3d36 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt @@ -1,12 +1,13 @@ package org.utbot.intellij.plugin.ui.utils import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.model.util.Patterns import org.utbot.framework.codegen.model.util.patterns import org.utbot.framework.plugin.api.MockFramework import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.roots.LibraryOrderEntry +import org.utbot.framework.plugin.api.util.Patterns +import org.utbot.framework.plugin.api.util.codegenUtilsLibraryPatterns fun findFrameworkLibrary( project: Project, @@ -24,6 +25,14 @@ fun findFrameworkLibrary( scope: LibrarySearchScope = LibrarySearchScope.Module, ): LibraryOrderEntry? = findMatchingLibrary(project, testModule, mockFramework.patterns(), scope) +fun findCodegenUtilsLibrary( + project: Project, + testModule: Module, + scope: LibrarySearchScope = LibrarySearchScope.Module, +): LibraryOrderEntry? { + return findMatchingLibrary(project, testModule, codegenUtilsLibraryPatterns, scope) +} + private fun findMatchingLibrary( project: Project, testModule: Module, From e6137026b955b39906686eade8eed7b0ff8a3ec4 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 21 Jul 2022 17:42:42 +0300 Subject: [PATCH 02/30] Split util methods and data providers into two separate regions. All util methods and data provider methods are static. In Kotlin they will be placed into a companion object. --- .../tree/CgTestClassConstructor.kt | 20 +++++---- .../framework/codegen/model/tree/Builders.kt | 6 +-- .../model/visitor/CgAbstractRenderer.kt | 10 ----- .../codegen/model/visitor/CgJavaRenderer.kt | 11 +++++ .../codegen/model/visitor/CgKotlinRenderer.kt | 45 +++++++++++++++---- 5 files changed, 61 insertions(+), 31 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index 1d5e570a43..42c9018073 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -91,15 +91,17 @@ internal class CgTestClassConstructor(val context: CgContext) : testMethodRegions += executableUnderTestCluster } -// val utilMethods = if (currentTestClass == outerMostTestClass) -// createUtilMethods() -// else -// emptyList() - - dataProvidersAndUtilMethodsRegion += CgStaticsRegion( - "Data providers and utils methods", - currentTestClassContext.cgDataProviderMethods - ) + val currentTestClassDataProviderMethods = currentTestClassContext.cgDataProviderMethods + if (currentTestClassDataProviderMethods.isNotEmpty()) { + staticDeclarationRegions += CgStaticsRegion("Data providers", currentTestClassDataProviderMethods) + } + + if (currentTestClass == outerMostTestClass) { + val utilMethods = createUtilMethods() + if (utilMethods.isNotEmpty()) { + staticDeclarationRegions += CgStaticsRegion("Util methods", utilMethods) + } + } } // It is important that annotations, superclass and interfaces assignment is run after // all methods are generated so that all necessary info is already present in the context diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt index 57a91672a3..9ac81c3f08 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt @@ -38,12 +38,10 @@ fun buildTestClass(init: CgTestClassBuilder.() -> Unit) = CgTestClassBuilder().a class CgTestClassBodyBuilder : CgBuilder { val testMethodRegions: MutableList = mutableListOf() - - val dataProvidersAndUtilMethodsRegion: MutableList> = mutableListOf() - + val staticDeclarationRegions: MutableList = mutableListOf() val nestedClassRegions: MutableList> = mutableListOf() - override fun build() = CgTestClassBody(testMethodRegions, dataProvidersAndUtilMethodsRegion, nestedClassRegions) + override fun build() = CgTestClassBody(testMethodRegions, staticDeclarationRegions, nestedClassRegions) } fun buildTestClassBody(init: CgTestClassBodyBuilder.() -> Unit) = CgTestClassBodyBuilder().apply(init).build() 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 c838ab1a42..13d6dbf71f 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 @@ -67,7 +67,6 @@ import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgStaticRunnable import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestClassBody import org.utbot.framework.codegen.model.tree.CgTestClassFile import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster @@ -137,15 +136,6 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: element.testClass.accept(this) } - override fun visit(element: CgTestClassBody) { - // render regions for test methods and utils - for ((i, region) in (element.regions + element.nestedClassRegions + element.utilsRegion).withIndex()) { - if (i != 0) println() - - region.accept(this) - } - } - /** * Render the region only if it is not empty. */ 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 b1f00fa728..3e2b5e3d37 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 @@ -32,6 +32,7 @@ import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall import org.utbot.framework.codegen.model.tree.CgSwitchCase import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel import org.utbot.framework.codegen.model.tree.CgTestClass +import org.utbot.framework.codegen.model.tree.CgTestClassBody import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTypeCast import org.utbot.framework.codegen.model.tree.CgVariable @@ -80,6 +81,16 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter println("}") } + override fun visit(element: CgTestClassBody) { + // render regions for test methods and utils + val allRegions = element.testMethodRegions + element.staticDeclarationRegions + for ((i, region) in allRegions.withIndex()) { + if (i != 0) println() + + region.accept(this) + } + } + override fun visit(element: CgArrayAnnotationArgument) { print("{") element.values.renderSeparated() 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 8a25ad5a7f..ef7995f4e4 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 @@ -36,6 +36,7 @@ import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgSwitchCase import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel import org.utbot.framework.codegen.model.tree.CgTestClass +import org.utbot.framework.codegen.model.tree.CgTestClassBody import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTypeCast import org.utbot.framework.codegen.model.tree.CgVariable @@ -100,6 +101,38 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint println("}") } + override fun visit(element: CgTestClassBody) { + // render regions for test methods + for ((i, region) in (element.testMethodRegions).withIndex()) { + if (i != 0) println() + + region.accept(this) + } + + if (element.staticDeclarationRegions.isEmpty()) { + return + } + // render static declaration regions inside a companion object + println() + renderCompanionObject { + for ((i, staticsRegion) in element.staticDeclarationRegions.withIndex()) { + if (i != 0) println() + + staticsRegion.accept(this) + } + } + } + + /** + * Build a companion object. + * @param body a lambda that contains the logic of construction of a companion object's body + */ + private fun renderCompanionObject(body: () -> Unit) { + println("companion object {") + withIndent(body) + println("}") + } + override fun visit(element: CgStaticsRegion) { if (element.content.isEmpty()) return @@ -107,15 +140,11 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint element.header?.let { print(" $it") } println() - println("companion object {") - withIndent { - for (item in element.content) { - println() - println("@JvmStatic") - item.accept(this) - } + for (item in element.content) { + println() + println("@JvmStatic") + item.accept(this) } - println("}") println(regionEnd) } From d9ff922553f3cb7330b2b82dcdd1cc258ab8c88b Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 5 Aug 2022 13:42:23 +0300 Subject: [PATCH 03/30] Create CgElements for regular classes (not just test classes) Regular classes will be used for construction of util classes. --- .../framework/codegen/model/CodeGenerator.kt | 5 +- .../tree/CgUtilClassConstructor.kt | 30 ++++++ .../framework/codegen/model/tree/Builders.kt | 33 ++++++- .../framework/codegen/model/tree/CgElement.kt | 85 +++++++++++++---- .../model/visitor/CgAbstractRenderer.kt | 93 +++++++++++++------ .../codegen/model/visitor/CgJavaRenderer.kt | 40 ++++++-- .../codegen/model/visitor/CgKotlinRenderer.kt | 59 +++++++++--- .../model/visitor/CgRendererContext.kt | 58 ++++++++++++ .../codegen/model/visitor/CgVisitor.kt | 12 +++ 9 files changed, 346 insertions(+), 69 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 4db4212f8c..91a1fe5f27 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -10,7 +10,8 @@ import org.utbot.framework.codegen.model.constructor.CgMethodTestSet import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.constructor.tree.CgTestClassConstructor import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport -import org.utbot.framework.codegen.model.tree.CgTestClassFile +import org.utbot.framework.codegen.model.tree.AbstractCgClassFile +import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.visitor.CgAbstractRenderer import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage @@ -93,7 +94,7 @@ class CodeGenerator( } } - private fun renderClassFile(file: CgTestClassFile): String { + private fun renderClassFile(file: AbstractCgClassFile<*>): String { val renderer = CgAbstractRenderer.makeRenderer(context) file.accept(renderer) return renderer.toString() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt new file mode 100644 index 0000000000..018e587fb3 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -0,0 +1,30 @@ +package org.utbot.framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.model.CodeGenerator +import org.utbot.framework.codegen.model.UtilClassKind +import org.utbot.framework.codegen.model.constructor.builtin.UtUtilMethodProvider +import org.utbot.framework.codegen.model.tree.CgRegularClassFile +import org.utbot.framework.codegen.model.tree.CgUtilMethod +import org.utbot.framework.codegen.model.tree.buildRegularClass +import org.utbot.framework.codegen.model.tree.buildRegularClassBody +import org.utbot.framework.codegen.model.tree.buildRegularClassFile + +/** + * This class is used to construct a file containing an util class UtUtils. + * The util class is constructed when the argument `generateUtilClassFile` in the [CodeGenerator] is true. + */ +internal object CgUtilClassConstructor { + fun constructUtilsClassFile(utilClassKind: UtilClassKind): CgRegularClassFile { + val utilMethodProvider = utilClassKind.utilMethodProvider + return buildRegularClassFile { + // imports are empty, because we use fully qualified classes and static methods, + // so they will be imported once IDEA reformatting action has worked + declaredClass = buildRegularClass { + id = UtUtilMethodProvider.utUtilsClassId + body = buildRegularClassBody { + content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } + } + } + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt index 9ac81c3f08..f7399e6770 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt @@ -12,16 +12,37 @@ interface CgBuilder { // Code entities +class CgRegularClassFileBuilder : CgBuilder { + val imports: MutableList = mutableListOf() + lateinit var declaredClass: CgRegularClass + + override fun build() = CgRegularClassFile(imports, declaredClass) +} + +fun buildRegularClassFile(init: CgRegularClassFileBuilder.() -> Unit) = CgRegularClassFileBuilder().apply(init).build() + class CgTestClassFileBuilder : CgBuilder { val imports: MutableList = mutableListOf() - lateinit var testClass: CgTestClass + lateinit var declaredClass: CgTestClass lateinit var testsGenerationReport: TestsGenerationReport - override fun build() = CgTestClassFile(imports, testClass, testsGenerationReport) + override fun build() = CgTestClassFile(imports, declaredClass, testsGenerationReport) } fun buildTestClassFile(init: CgTestClassFileBuilder.() -> Unit) = CgTestClassFileBuilder().apply(init).build() +class CgRegularClassBuilder : CgBuilder { + lateinit var id: ClassId + val annotations: MutableList = mutableListOf() + var superclass: ClassId? = null + val interfaces: MutableList = mutableListOf() + lateinit var body: CgRegularClassBody + + override fun build() = CgRegularClass(id, annotations, superclass, interfaces, body) +} + +fun buildRegularClass(init: CgRegularClassBuilder.() -> Unit) = CgRegularClassBuilder().apply(init).build() + class CgTestClassBuilder : CgBuilder { lateinit var id: ClassId val annotations: MutableList = mutableListOf() @@ -46,6 +67,14 @@ class CgTestClassBodyBuilder : CgBuilder { fun buildTestClassBody(init: CgTestClassBodyBuilder.() -> Unit) = CgTestClassBodyBuilder().apply(init).build() +class CgRegularClassBodyBuilder : CgBuilder { + val content: MutableList = mutableListOf() + + override fun build() = CgRegularClassBody(content) +} + +fun buildRegularClassBody(init: CgRegularClassBodyBuilder.() -> Unit) = CgRegularClassBodyBuilder().apply(init).build() + // Methods interface CgMethodBuilder : CgBuilder { 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 32d57a30b7..c139d64d27 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 @@ -5,7 +5,9 @@ import org.utbot.common.workaround import org.utbot.framework.codegen.Import import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.codegen.model.util.CgExceptionHandler +import org.utbot.framework.codegen.model.visitor.CgRendererContext import org.utbot.framework.codegen.model.visitor.CgVisitor +import org.utbot.framework.codegen.model.visitor.utilMethodTextById import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId @@ -32,8 +34,11 @@ interface CgElement { // TODO: order of cases is important here due to inheritance between some of the element types fun accept(visitor: CgVisitor): R = visitor.run { when (val element = this@CgElement) { + is CgRegularClassFile -> visit(element) is CgTestClassFile -> visit(element) + is CgRegularClass -> visit(element) is CgTestClass -> visit(element) + is CgRegularClassBody -> visit(element) is CgTestClassBody -> visit(element) is CgStaticsRegion -> visit(element) is CgSimpleRegion<*> -> visit(element) @@ -112,25 +117,61 @@ interface CgElement { // Code entities +sealed class AbstractCgClassFile> : CgElement { + abstract val imports: List + abstract val declaredClass: T +} + +data class CgRegularClassFile( + override val imports: List, + override val declaredClass: CgRegularClass +) : AbstractCgClassFile() + data class CgTestClassFile( - val imports: List, - val testClass: CgTestClass, + override val imports: List, + override val declaredClass: CgTestClass, val testsGenerationReport: TestsGenerationReport -) : CgElement +) : AbstractCgClassFile() -data class CgTestClass( - val id: ClassId, - val annotations: List, - val superclass: ClassId?, - val interfaces: List, - val body: CgTestClassBody, - val isStatic: Boolean, - val isNested: Boolean -) : CgElement { - val packageName = id.packageName - val simpleName = id.simpleName +sealed class AbstractCgClass : CgElement { + abstract val id: ClassId + abstract val annotations: List + abstract val superclass: ClassId? + abstract val interfaces: List + abstract val body: T + abstract val isStatic: Boolean + abstract val isNested: Boolean + + val packageName + get() = id.packageName + + val simpleName + get() = id.simpleName } +class CgRegularClass( + override val id: ClassId, + override val annotations: List, + override val superclass: ClassId?, + override val interfaces: List, + override val body: CgRegularClassBody +) : AbstractCgClass() + +data class CgTestClass( + override val id: ClassId, + override val annotations: List, + override val superclass: ClassId?, + override val interfaces: List, + override val body: CgTestClassBody, + override val isStatic: Boolean, + override val isNested: Boolean +) : AbstractCgClass() + + +sealed class AbstractCgClassBody : CgElement + +data class CgRegularClassBody(val content: List) : AbstractCgClassBody() + /** * Body of the test class. * @property testMethodRegions regions containing the test methods @@ -144,7 +185,8 @@ data class CgTestClassBody( val testMethodRegions: List, val staticDeclarationRegions: List, val nestedClassRegions: List> -) : CgElement { +) : AbstractCgClassBody() { + val regions: List> get() = testMethodRegions } @@ -198,7 +240,18 @@ data class CgExecutableUnderTestCluster( * * @property id identifier of the util method. */ -data class CgUtilMethod(val id: MethodId) : CgElement +data class CgUtilMethod(val id: MethodId) : CgElement { + internal fun getText(rendererContext: CgRendererContext): String { + // we should not throw an exception on failure here, + // because this function is used during rendering and + // exceptions can crash rendering, so we use an empty string if the text is not found + return with(rendererContext) { + rendererContext.utilMethodProvider + .utilMethodTextById(id, mockFrameworkUsed, mockFramework, codegenLanguage) + .getOrDefault("") + } + } +} // Methods 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 13d6dbf71f..b6c86f8250 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 @@ -6,8 +6,11 @@ import org.utbot.common.workaround import org.utbot.framework.codegen.Import import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.constructor.context.CgContext +import org.utbot.framework.codegen.model.tree.AbstractCgClass +import org.utbot.framework.codegen.model.tree.AbstractCgClassBody +import org.utbot.framework.codegen.model.tree.AbstractCgClassFile import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment import org.utbot.framework.codegen.model.tree.CgArrayElementAccess @@ -56,6 +59,8 @@ import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgRegion +import org.utbot.framework.codegen.model.tree.CgRegularClassBody +import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation @@ -66,7 +71,6 @@ import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgStaticRunnable import org.utbot.framework.codegen.model.tree.CgStaticsRegion -import org.utbot.framework.codegen.model.tree.CgTestClass import org.utbot.framework.codegen.model.tree.CgTestClassFile import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster @@ -94,7 +98,10 @@ import org.utbot.framework.plugin.api.util.isRefType 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, +internal abstract class CgAbstractRenderer( + val context: CgRendererContext, + val printer: CgPrinter = CgPrinterImpl() +) : CgVisitor, CgPrinter by printer { protected abstract val statementEnding: String @@ -122,7 +129,7 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } private val MethodId.accessibleByName: Boolean - get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.outerMostTestClass + get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.generatedClass override fun visit(element: CgElement) { val error = @@ -130,10 +137,33 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: throw IllegalArgumentException(error) } - override fun visit(element: CgTestClassFile) { - renderClassPackage(element.testClass) + override fun visit(element: AbstractCgClassFile<*>) { + renderClassPackage(element.declaredClass) renderClassFileImports(element) - element.testClass.accept(this) + element.declaredClass.accept(this) + } + + override fun visit(element: CgRegularClassFile) { + visit(element as AbstractCgClassFile<*>) + } + + override fun visit(element: CgTestClassFile) { + visit(element as AbstractCgClassFile<*>) + } + + override fun visit(element: AbstractCgClassBody) { + visit(element as CgElement) + } + + override fun visit(element: CgRegularClassBody) { + val content = element.content + for ((index, item) in content.withIndex()) { + item.accept(this) + println() + if (index < content.lastIndex) { + println() + } + } } /** @@ -188,23 +218,9 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } override fun visit(element: CgUtilMethod) { - // If we are rendering a CgUtilMethod, then we know that context.currentUtilMethodProvider - // must be the test class being generated, i.e. it must be TestClassUtilMethodProvider. - // So, the following type cast must always succeed. - // However, in order to avoid unexpected rendering failures (which will cause whole test class generation to fail), - // we make sure that we always have a correct provider by creating it ourselves in case of a failed type cast. - val utilMethodProvider = context.currentUtilMethodProvider as? TestClassUtilMethodProvider - ?: TestClassUtilMethodProvider(context.outerMostTestClass) - utilMethodProvider.utilMethodTextById( - element.id, - context.mockFrameworkUsed, - context.mockFramework, - context.codegenLanguage - ).onSuccess { utilMethodText -> - utilMethodText - .split("\n") - .forEach { line -> println(line) } - } + val utilMethodText = element.getText(context) + utilMethodText.split("\n") + .forEach { line -> println(line) } } // Methods @@ -801,7 +817,7 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } protected open fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean = - classId in context.importedClasses || classId.packageName == context.testClassPackageName + classId in context.importedClasses || classId.packageName == context.classPackageName protected abstract fun escapeNamePossibleKeywordImpl(s: String): String @@ -814,14 +830,14 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: return if (this.isAccessibleBySimpleName()) simpleNameWithEnclosings else canonicalName } - private fun renderClassPackage(element: CgTestClass) { + private fun renderClassPackage(element: AbstractCgClass<*>) { if (element.packageName.isNotEmpty()) { println("package ${element.packageName}${statementEnding}") println() } } - private fun renderClassFileImports(element: CgTestClassFile) { + private fun renderClassFileImports(element: AbstractCgClassFile<*>) { val regularImports = element.imports.filterIsInstance() val staticImports = element.imports.filterIsInstance() @@ -840,6 +856,10 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } } + protected abstract fun renderClassVisibility(classId: ClassId) + + protected abstract fun renderClassModality(aClass: AbstractCgClass<*>) + private fun renderMethodDocumentation(element: CgMethod) { element.documentation.accept(this) } @@ -896,11 +916,26 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: fun makeRenderer( context: CgContext, printer: CgPrinter = CgPrinterImpl() - ): CgAbstractRenderer = - when (context.codegenLanguage) { + ): CgAbstractRenderer { + val rendererContext = CgRendererContext.fromCgContext(context) + return makeRenderer(rendererContext, printer) + } + + fun makeRenderer( + utilClassKind: UtilClassKind, + codegenLanguage: CodegenLanguage, + printer: CgPrinter = CgPrinterImpl() + ): CgAbstractRenderer { + val rendererContext = CgRendererContext.fromUtilClassKind(utilClassKind, codegenLanguage) + return makeRenderer(rendererContext, printer) + } + + private fun makeRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer { + return when (context.codegenLanguage) { CodegenLanguage.JAVA -> CgJavaRenderer(context, printer) CodegenLanguage.KOTLIN -> CgKotlinRenderer(context, printer) } + } /** * @see [LONG_CODE_FRAGMENTS] 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 3e2b5e3d37..3f8efddc71 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 @@ -3,7 +3,7 @@ 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.AbstractCgClass import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction @@ -26,6 +26,7 @@ import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.model.tree.CgRegularClass import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgStatement import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall @@ -44,7 +45,7 @@ import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.TypeParameters import org.utbot.framework.plugin.api.util.wrapperByPrimitive -internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : +internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { override val statementEnding: String = ";" @@ -59,18 +60,22 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter override val langPackage: String = "java.lang" - override fun visit(element: CgTestClass) { + override fun visit(element: AbstractCgClass<*>) { for (annotation in element.annotations) { annotation.accept(this) } - print("public ") + + renderClassVisibility(element.id) + renderClassModality(element) if (element.isStatic) { print("static ") } print("class ") print(element.simpleName) - if (element.superclass != null) { - print(" extends ${element.superclass.asString()}") + + val superclass = element.superclass + if (superclass != null) { + print(" extends ${superclass.asString()}") } if (element.interfaces.isNotEmpty()) { print(" implements ") @@ -81,6 +86,14 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter println("}") } + override fun visit(element: CgRegularClass) { + visit(element as AbstractCgClass<*>) + } + + override fun visit(element: CgTestClass) { + visit(element as AbstractCgClass<*>) + } + override fun visit(element: CgTestClassBody) { // render regions for test methods and utils val allRegions = element.testMethodRegions + element.staticDeclarationRegions @@ -346,6 +359,21 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter override fun escapeNamePossibleKeywordImpl(s: String): String = s + override fun renderClassVisibility(classId: ClassId) { + when { + classId.isPublic -> print("public ") + classId.isProtected -> print("protected ") + classId.isPrivate -> print("private ") + } + } + + override fun renderClassModality(aClass: AbstractCgClass<*>) { + when (aClass) { + is CgTestClass -> Unit + is CgRegularClass -> if (aClass.id.isFinal) print("final ") + } + } + private fun renderExceptions(method: CgMethod) { method.exceptions.takeIf { it.isNotEmpty() }?.let { exceptions -> print(" throws ") 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 ef7995f4e4..b378816b29 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 @@ -6,7 +6,7 @@ import org.utbot.common.workaround import org.utbot.framework.codegen.RegularImport 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.AbstractCgClass import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction @@ -31,6 +31,7 @@ import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.model.tree.CgRegularClass import org.utbot.framework.codegen.model.tree.CgSpread import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgSwitchCase @@ -56,7 +57,7 @@ import org.utbot.framework.plugin.api.util.kClass import org.utbot.framework.plugin.api.util.voidClassId //TODO rewrite using KtPsiFactory? -internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { +internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { override val statementEnding: String = "" override val logicalAnd: String @@ -69,30 +70,35 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint override val langPackage: String = "kotlin" - override fun visit(element: CgTestClass) { + override fun visit(element: AbstractCgClass<*>) { for (annotation in element.annotations) { annotation.accept(this) } + + renderClassVisibility(element.id) + renderClassModality(element) if (!element.isStatic && element.isNested) { print("inner ") } print("class ") print(element.simpleName) + if (element.superclass != null || element.interfaces.isNotEmpty()) { print(" :") } val supertypes = mutableListOf() - .apply { - // Here we do not consider constructors with arguments, but for now they are not needed. - // Also, we do not yet support type parameters in code generation, so generic - // superclasses or interfaces are not supported. Although, they are not needed for now. - if (element.superclass != null) { - add("${element.superclass.asString()}()") - } - element.interfaces.forEach { - add(it.asString()) - } - }.joinToString() + .apply { + // Here we do not consider constructors with arguments, but for now they are not needed. + // Also, we do not yet support type parameters in code generation, so generic + // superclasses or interfaces are not supported. Although, they are not needed for now. + val superclass = element.superclass + if (superclass != null) { + add("${superclass.asString()}()") + } + element.interfaces.forEach { + add(it.asString()) + } + }.joinToString() if (supertypes.isNotEmpty()) { print(" $supertypes") } @@ -101,6 +107,14 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint println("}") } + override fun visit(element: CgRegularClass) { + visit(element as AbstractCgClass<*>) + } + + override fun visit(element: CgTestClass) { + visit(element as AbstractCgClass<*>) + } + override fun visit(element: CgTestClassBody) { // render regions for test methods for ((i, region) in (element.testMethodRegions).withIndex()) { @@ -479,6 +493,23 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint override fun escapeNamePossibleKeywordImpl(s: String): String = if (isLanguageKeyword(s, context.codegenLanguage)) "`$s`" else s + override fun renderClassVisibility(classId: ClassId) { + when { + // Kotlin classes are public by default + classId.isPublic -> Unit + classId.isProtected -> print("protected ") + classId.isPrivate -> print("private ") + } + } + + override fun renderClassModality(aClass: AbstractCgClass<*>) { + when (aClass) { + is CgTestClass -> Unit + // Kotlin classes are final by default + is CgRegularClass -> if (!aClass.id.isFinal) print("open ") + } + } + private fun getKotlinClassString(id: ClassId): String = if (id.isArray) { getKotlinArrayClassOfString(id) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt new file mode 100644 index 0000000000..8554e8b435 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt @@ -0,0 +1,58 @@ +package org.utbot.framework.codegen.model.visitor + +import org.utbot.framework.codegen.model.UtilClassKind +import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_PACKAGE_NAME +import org.utbot.framework.codegen.model.constructor.builtin.UtUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider +import org.utbot.framework.codegen.model.constructor.context.CgContext +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.MockFramework + +/** + * Information from [CgContext] that is relevant for the renderer. + * Not all the information from [CgContext] is required to render a class, + * so this more lightweight context is created for this purpose. + */ +internal class CgRendererContext( + val shouldOptimizeImports: Boolean, + val importedClasses: Set, + val importedStaticMethods: Set, + val classPackageName: String, + val generatedClass: ClassId, + val utilMethodProvider: UtilMethodProvider, + val codegenLanguage: CodegenLanguage, + val mockFrameworkUsed: Boolean, + val mockFramework: MockFramework, +) { + companion object { + fun fromCgContext(context: CgContext): CgRendererContext { + return CgRendererContext( + shouldOptimizeImports = context.shouldOptimizeImports, + importedClasses = context.importedClasses, + importedStaticMethods = context.importedStaticMethods, + classPackageName = context.testClassPackageName, + generatedClass = context.currentTestClass, + utilMethodProvider = context.utilMethodProvider, + codegenLanguage = context.codegenLanguage, + mockFrameworkUsed = context.mockFrameworkUsed, + mockFramework = context.mockFramework + ) + } + + fun fromUtilClassKind(utilClassKind: UtilClassKind, language: CodegenLanguage): CgRendererContext { + return CgRendererContext( + shouldOptimizeImports = false, + importedClasses = emptySet(), + importedStaticMethods = emptySet(), + classPackageName = UT_UTILS_PACKAGE_NAME, + generatedClass = UtUtilMethodProvider.utUtilsClassId, + utilMethodProvider = utilClassKind.utilMethodProvider, + codegenLanguage = language, + mockFrameworkUsed = utilClassKind.mockFrameworkUsed, + mockFramework = utilClassKind.mockFramework + ) + } + } +} \ No newline at end of file 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 30c269e88c..839c611dc4 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 @@ -1,5 +1,8 @@ package org.utbot.framework.codegen.model.visitor +import org.utbot.framework.codegen.model.tree.AbstractCgClass +import org.utbot.framework.codegen.model.tree.AbstractCgClassBody +import org.utbot.framework.codegen.model.tree.AbstractCgClassFile import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment import org.utbot.framework.codegen.model.tree.CgAllocateArray @@ -58,6 +61,9 @@ import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.model.tree.CgRegularClass +import org.utbot.framework.codegen.model.tree.CgRegularClassBody +import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation @@ -87,10 +93,16 @@ import org.utbot.framework.codegen.model.tree.CgWhileLoop interface CgVisitor { fun visit(element: CgElement): R + fun visit(element: AbstractCgClassFile<*>): R + fun visit(element: CgRegularClassFile): R fun visit(element: CgTestClassFile): R + fun visit(element: AbstractCgClass<*>): R + fun visit(element: CgRegularClass): R fun visit(element: CgTestClass): R + fun visit(element: AbstractCgClassBody): R + fun visit(element: CgRegularClassBody): R fun visit(element: CgTestClassBody): R fun visit(element: CgStaticsRegion): R From 06ed384b17f1c3afbd0bc0b3e1f651544768022f Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 5 Aug 2022 18:56:15 +0300 Subject: [PATCH 04/30] Update util method providers Rewrite util method provider classes and their usage. There are 3 kinds of providers: library, test class, util class file. --- .../constructor/builtin/UtilMethodBuiltins.kt | 140 ++++++------------ .../model/constructor/context/CgContext.kt | 81 ++++++---- .../tree/CgCallableAccessManager.kt | 6 +- .../tree/CgUtilClassConstructor.kt | 4 +- .../constructor/tree/TestFrameworkManager.kt | 16 +- .../constructor/util/ConstructorUtils.kt | 4 +- .../model/visitor/CgRendererContext.kt | 4 +- .../codegen/model/visitor/UtilMethods.kt | 5 +- 8 files changed, 115 insertions(+), 145 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index e86aea33e2..a3545c664e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -3,12 +3,10 @@ package org.utbot.framework.codegen.model.constructor.builtin import org.utbot.framework.codegen.MockitoStaticMocking import org.utbot.framework.codegen.model.constructor.util.utilMethodId import org.utbot.framework.codegen.model.tree.CgClassId -import org.utbot.framework.codegen.utils.UtUtils import org.utbot.framework.plugin.api.BuiltinClassId 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.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.jClass @@ -16,12 +14,12 @@ 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 kotlin.reflect.jvm.javaMethod /** - * Set of ids of all possible util methods for a given class + * Set of ids of all possible util methods for a given class. + * * The class may actually not have some of these methods if they - * are not required in the process of code generation + * are not required in the process of code generation (this is the case for [TestClassUtilMethodProvider]). */ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { val utilMethodIds: Set @@ -43,178 +41,107 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { getArrayLengthMethodId ) - abstract val getUnsafeInstanceMethodId: MethodId - - /** - * Method that creates instance using Unsafe - */ - abstract val createInstanceMethodId: MethodId - - abstract val createArrayMethodId: MethodId - - abstract val setFieldMethodId: MethodId - - abstract val setStaticFieldMethodId: MethodId - - abstract val getFieldValueMethodId: MethodId - - abstract val getStaticFieldValueMethodId: MethodId - - abstract val getEnumConstantByNameMethodId: MethodId - - abstract val deepEqualsMethodId: MethodId - - abstract val arraysDeepEqualsMethodId: MethodId - - abstract val iterablesDeepEqualsMethodId: MethodId - - abstract val streamsDeepEqualsMethodId: MethodId - - abstract val mapsDeepEqualsMethodId: MethodId - - abstract val hasCustomEqualsMethodId: MethodId - - abstract val getArrayLengthMethodId: MethodId -} - -internal object LibraryUtilMethodProvider : UtilMethodProvider(UtUtils::class.id) { - override val getUnsafeInstanceMethodId: MethodId = UtUtils::getUnsafeInstance.javaMethod!!.executableId - - override val createInstanceMethodId: MethodId = UtUtils::createInstance.javaMethod!!.executableId - - override val createArrayMethodId: MethodId = UtUtils::createArray.javaMethod!!.executableId - - override val setFieldMethodId: MethodId = UtUtils::setField.javaMethod!!.executableId - - override val setStaticFieldMethodId: MethodId = UtUtils::setStaticField.javaMethod!!.executableId - - override val getFieldValueMethodId: MethodId = UtUtils::getFieldValue.javaMethod!!.executableId - - override val getStaticFieldValueMethodId: MethodId = UtUtils::getStaticFieldValue.javaMethod!!.executableId - - override val getEnumConstantByNameMethodId: MethodId = UtUtils::getEnumConstantByName.javaMethod!!.executableId - - override val deepEqualsMethodId: MethodId = UtUtils::deepEquals.javaMethod!!.executableId - - override val arraysDeepEqualsMethodId: MethodId = UtUtils::arraysDeepEquals.javaMethod!!.executableId - - override val iterablesDeepEqualsMethodId: MethodId = UtUtils::iterablesDeepEquals.javaMethod!!.executableId - - override val streamsDeepEqualsMethodId: MethodId = UtUtils::streamsDeepEquals.javaMethod!!.executableId - - override val mapsDeepEqualsMethodId: MethodId = UtUtils::mapsDeepEquals.javaMethod!!.executableId - - override val hasCustomEqualsMethodId: MethodId = UtUtils::hasCustomEquals.javaMethod!!.executableId - - override val getArrayLengthMethodId: MethodId = UtUtils::getArrayLength.javaMethod!!.executableId - -// fun patterns(): Patterns { -// return Patterns( -// moduleLibraryPatterns = emptyList(), -// libraryPatterns = listOf(CODEGEN_UTILS_JAR_PATTERN) -// ) -// } -} - -internal class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId) { - override val getUnsafeInstanceMethodId: MethodId + open val getUnsafeInstanceMethodId: MethodId get() = utilClassId.utilMethodId( name = "getUnsafeInstance", returnType = Unsafe::class.id, ) - override val createInstanceMethodId: MethodId + /** + * Method that creates instance using Unsafe + */ + open val createInstanceMethodId: MethodId get() = utilClassId.utilMethodId( name = "createInstance", returnType = CgClassId(objectClassId, isNullable = true), arguments = arrayOf(stringClassId) ) - override val createArrayMethodId: MethodId + open val createArrayMethodId: MethodId get() = utilClassId.utilMethodId( name = "createArray", returnType = Array::class.id, arguments = arrayOf(stringClassId, intClassId, Array::class.id) ) - override val setFieldMethodId: MethodId + open val setFieldMethodId: MethodId get() = utilClassId.utilMethodId( name = "setField", returnType = voidClassId, arguments = arrayOf(objectClassId, stringClassId, objectClassId) ) - override val setStaticFieldMethodId: MethodId + open val setStaticFieldMethodId: MethodId get() = utilClassId.utilMethodId( name = "setStaticField", returnType = voidClassId, arguments = arrayOf(Class::class.id, stringClassId, objectClassId) ) - override val getFieldValueMethodId: MethodId + open val getFieldValueMethodId: MethodId get() = utilClassId.utilMethodId( name = "getFieldValue", returnType = objectClassId, arguments = arrayOf(objectClassId, stringClassId) ) - override val getStaticFieldValueMethodId: MethodId + open val getStaticFieldValueMethodId: MethodId get() = utilClassId.utilMethodId( name = "getStaticFieldValue", returnType = objectClassId, arguments = arrayOf(Class::class.id, stringClassId) ) - override val getEnumConstantByNameMethodId: MethodId + open val getEnumConstantByNameMethodId: MethodId get() = utilClassId.utilMethodId( name = "getEnumConstantByName", returnType = objectClassId, arguments = arrayOf(Class::class.id, stringClassId) ) - override val deepEqualsMethodId: MethodId + open val deepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "deepEquals", returnType = booleanClassId, arguments = arrayOf(objectClassId, objectClassId) ) - override val arraysDeepEqualsMethodId: MethodId + open val arraysDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "arraysDeepEquals", returnType = booleanClassId, arguments = arrayOf(objectClassId, objectClassId) ) - override val iterablesDeepEqualsMethodId: MethodId + open val iterablesDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "iterablesDeepEquals", returnType = booleanClassId, arguments = arrayOf(java.lang.Iterable::class.id, java.lang.Iterable::class.id) ) - override val streamsDeepEqualsMethodId: MethodId + open val streamsDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "streamsDeepEquals", returnType = booleanClassId, arguments = arrayOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id) ) - override val mapsDeepEqualsMethodId: MethodId + open val mapsDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "mapsDeepEquals", returnType = booleanClassId, arguments = arrayOf(java.util.Map::class.id, java.util.Map::class.id) ) - override val hasCustomEqualsMethodId: MethodId + open val hasCustomEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "hasCustomEquals", returnType = booleanClassId, arguments = arrayOf(Class::class.id) ) - override val getArrayLengthMethodId: MethodId + open val getArrayLengthMethodId: MethodId get() = utilClassId.utilMethodId( name = "getArrayLength", returnType = intClassId, @@ -249,6 +176,31 @@ internal class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodPro } } +/** + * This provider represents an util class file that is located in the library. + * Source code of the library can be found in the module `utbot-codegen-utils`. + */ +@Suppress("unused") +internal object LibraryUtilMethodProvider : UtilMethodProvider(utUtilsClassId) + +/** + * This provider represents an util class file that is generated and put into the user's test module. + * The generated class is UtUtils (its id is defined at [utUtilsClassId]). + * + * Content of this util class may be different (due to mocks in deepEquals), but the methods (and their ids) are the same. + */ +internal object UtilClassFileMethodProvider : UtilMethodProvider(utUtilsClassId) + +internal class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId) + +internal val utUtilsClassId: ClassId + get() = BuiltinClassId( + name = "org.utbot.runtime.utils.UtUtils", + canonicalName = "org.utbot.runtime.utils.UtUtils", + simpleName = "UtUtils", + isFinal = true + ) + /** * [MethodId] for [AutoCloseable.close]. */ diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 3c01709131..8e14e23d8e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -28,6 +28,7 @@ import kotlinx.collections.immutable.persistentSetOf import org.utbot.framework.codegen.model.constructor.CgMethodTestSet import org.utbot.framework.codegen.model.constructor.builtin.LibraryUtilMethodProvider import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.UtilClassFileMethodProvider import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider import org.utbot.framework.codegen.model.constructor.TestClassContext import org.utbot.framework.codegen.model.constructor.TestClassModel @@ -73,14 +74,8 @@ internal interface CgContextOwner { // test class currently being generated (if series of nested classes is generated, it is the innermost one) var currentTestClass: ClassId - // Flag indicating if there is a dependency on UtBot codegen utils library in the user's project. - // The sources of this library can be found in the module utbot-codegen-utils. - // If the library is in the dependencies, code generation will use util methods from it. - // Otherwise, util methods will be generated in the test class directly. - val codegenUtilsLibraryUsed: Boolean - // provider of util methods used for the test class currently being generated - val currentUtilMethodProvider: UtilMethodProvider + val utilMethodProvider: UtilMethodProvider // current executable under test var currentExecutable: ExecutableId? @@ -107,6 +102,8 @@ internal interface CgContextOwner { // util methods required by the test class being built val requiredUtilMethods: MutableSet + val utilMethodsUsed: Boolean + // test methods being generated val testMethods: MutableList @@ -138,7 +135,13 @@ internal interface CgContextOwner { val parametrizedTestSource: ParametrizedTestSource - // flag indicating whether a mock framework is used in the generated code + /** + * Flag indicating whether a mock framework is used in the generated code + * NOTE! This flag is not about whether a mock framework is present + * in the user's project dependencies or not. + * This flag is true if the generated test class contains at least one mock object, + * and false otherwise. See method [withMockFramework]. + */ var mockFrameworkUsed: Boolean // object that represents a set of information about JUnit of selected version @@ -326,7 +329,7 @@ internal interface CgContextOwner { * The latter is used when our codegen utils library is included in the user's project dependencies. */ val utilsClassId: ClassId - get() = currentUtilMethodProvider.utilClassId + get() = utilMethodProvider.utilClassId /** * Check whether a method is an util method of the current class @@ -339,57 +342,57 @@ internal interface CgContextOwner { * When this method is used with type cast in Kotlin, this type cast have to be safety */ val MethodId.isGetFieldUtilMethod: Boolean - get() = this == currentUtilMethodProvider.getFieldValueMethodId - || this == currentUtilMethodProvider.getStaticFieldValueMethodId + get() = this == utilMethodProvider.getFieldValueMethodId + || this == utilMethodProvider.getStaticFieldValueMethodId val testClassThisInstance: CgThisInstance // util methods of current test class val getUnsafeInstance: MethodId - get() = currentUtilMethodProvider.getUnsafeInstanceMethodId + get() = utilMethodProvider.getUnsafeInstanceMethodId val createInstance: MethodId - get() = currentUtilMethodProvider.createInstanceMethodId + get() = utilMethodProvider.createInstanceMethodId val createArray: MethodId - get() = currentUtilMethodProvider.createArrayMethodId + get() = utilMethodProvider.createArrayMethodId val setField: MethodId - get() = currentUtilMethodProvider.setFieldMethodId + get() = utilMethodProvider.setFieldMethodId val setStaticField: MethodId - get() = currentUtilMethodProvider.setStaticFieldMethodId + get() = utilMethodProvider.setStaticFieldMethodId val getFieldValue: MethodId - get() = currentUtilMethodProvider.getFieldValueMethodId + get() = utilMethodProvider.getFieldValueMethodId val getStaticFieldValue: MethodId - get() = currentUtilMethodProvider.getStaticFieldValueMethodId + get() = utilMethodProvider.getStaticFieldValueMethodId val getEnumConstantByName: MethodId - get() = currentUtilMethodProvider.getEnumConstantByNameMethodId + get() = utilMethodProvider.getEnumConstantByNameMethodId val deepEquals: MethodId - get() = currentUtilMethodProvider.deepEqualsMethodId + get() = utilMethodProvider.deepEqualsMethodId val arraysDeepEquals: MethodId - get() = currentUtilMethodProvider.arraysDeepEqualsMethodId + get() = utilMethodProvider.arraysDeepEqualsMethodId val iterablesDeepEquals: MethodId - get() = currentUtilMethodProvider.iterablesDeepEqualsMethodId + get() = utilMethodProvider.iterablesDeepEqualsMethodId val streamsDeepEquals: MethodId - get() = currentUtilMethodProvider.streamsDeepEqualsMethodId + get() = utilMethodProvider.streamsDeepEqualsMethodId val mapsDeepEquals: MethodId - get() = currentUtilMethodProvider.mapsDeepEqualsMethodId + get() = utilMethodProvider.mapsDeepEqualsMethodId val hasCustomEquals: MethodId - get() = currentUtilMethodProvider.hasCustomEqualsMethodId + get() = utilMethodProvider.hasCustomEqualsMethodId val getArrayLength: MethodId - get() = currentUtilMethodProvider.getArrayLengthMethodId + get() = utilMethodProvider.getArrayLengthMethodId } /** @@ -397,7 +400,7 @@ internal interface CgContextOwner { */ internal data class CgContext( override val classUnderTest: ClassId, - override val codegenUtilsLibraryUsed: Boolean, + val generateUtilClassFile: Boolean, override var currentExecutable: ExecutableId? = null, override val collectedExceptions: MutableSet = mutableSetOf(), override val collectedMethodAnnotations: MutableSet = mutableSetOf(), @@ -467,12 +470,23 @@ internal data class CgContext( ) } - override val currentUtilMethodProvider: UtilMethodProvider by lazy { - when { - codegenUtilsLibraryUsed -> LibraryUtilMethodProvider - else -> TestClassUtilMethodProvider(currentTestClass) + // TODO: note that when nested test classes feature is implemented, + // we will have to use the outermost test class here instead of currentTestClass to construct the TestClassUtilMethodProvider + /** + * Determine where the util methods will come from. + * If we don't want to use a separately generated util class, + * util methods will be generated directly in the test class (see [TestClassUtilMethodProvider]). + * Otherwise, an util class will be generated separately and we will use util methods from it (see [UtilClassFileMethodProvider]). + * + * We may also use utils from a library in the future (see [LibraryUtilMethodProvider]), + * but it is not clear at this point. Perhaps, we will remove the library completely. + */ + override val utilMethodProvider: UtilMethodProvider + get() = if (generateUtilClassFile) { + UtilClassFileMethodProvider + } else { + TestClassUtilMethodProvider(currentTestClass) } - } override lateinit var currentTestClass: ClassId @@ -538,4 +552,7 @@ internal data class CgContext( override val currentMethodParameters: MutableMap = mutableMapOf() override val testClassThisInstance: CgThisInstance = CgThisInstance(outerMostTestClass) + + override val utilMethodsUsed: Boolean + get() = requiredUtilMethods.isNotEmpty() } \ No newline at end of file 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 e3e4c13235..d0a77f62a9 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 @@ -3,7 +3,6 @@ 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.TestClassUtilMethodProvider 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.forName @@ -20,6 +19,7 @@ import org.utbot.framework.codegen.model.constructor.util.classCgClassId import org.utbot.framework.codegen.model.constructor.util.getAmbiguousOverloadsOf import org.utbot.framework.codegen.model.constructor.util.importIfNeeded import org.utbot.framework.codegen.model.constructor.util.isTestClassUtil +import org.utbot.framework.codegen.model.constructor.util.isUtil import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAssignment @@ -112,8 +112,8 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA //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 (isTestClassUtil(methodId)) { - (currentUtilMethodProvider as TestClassUtilMethodProvider) + if (isUtil(methodId)) { + utilMethodProvider .findExceptionTypesOf(methodId) .forEach { addExceptionIfNeeded(it) } return diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt index 018e587fb3..a34cd4aa15 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -2,7 +2,7 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.codegen.model.UtilClassKind -import org.utbot.framework.codegen.model.constructor.builtin.UtUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.utUtilsClassId import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgUtilMethod import org.utbot.framework.codegen.model.tree.buildRegularClass @@ -20,7 +20,7 @@ internal object CgUtilClassConstructor { // imports are empty, because we use fully qualified classes and static methods, // so they will be imported once IDEA reformatting action has worked declaredClass = buildRegularClass { - id = UtUtilMethodProvider.utUtilsClassId + id = utUtilsClassId body = buildRegularClassBody { content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } } 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 ce62d18750..e7acf17798 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 @@ -122,14 +122,14 @@ internal abstract class TestFrameworkManager(val context: CgContext) // If an util method provider is not TestClassUtilMethodProvider, then we are using util methods from library. // In this case we don't need to add required util methods to the test class, // because they are all already in a library. - if (currentUtilMethodProvider is TestClassUtilMethodProvider) { + if (utilMethodProvider is TestClassUtilMethodProvider) { requiredUtilMethods += setOf( - currentUtilMethodProvider.deepEqualsMethodId, - currentUtilMethodProvider.arraysDeepEqualsMethodId, - currentUtilMethodProvider.iterablesDeepEqualsMethodId, - currentUtilMethodProvider.streamsDeepEqualsMethodId, - currentUtilMethodProvider.mapsDeepEqualsMethodId, - currentUtilMethodProvider.hasCustomEqualsMethodId + utilMethodProvider.deepEqualsMethodId, + utilMethodProvider.arraysDeepEqualsMethodId, + utilMethodProvider.iterablesDeepEqualsMethodId, + utilMethodProvider.streamsDeepEqualsMethodId, + utilMethodProvider.mapsDeepEqualsMethodId, + utilMethodProvider.hasCustomEqualsMethodId ) } // TODO we cannot use common assertEquals because of using custom deepEquals @@ -137,7 +137,7 @@ internal abstract class TestFrameworkManager(val context: CgContext) // Unfortunately, if test with assertTrue fails, it gives non informative message false != true // Thus, we need to provide custom message to assertTrue showing compared objects correctly // SAT-1345 - return assertions[assertTrue](utilsClassId[deepEquals](expected, actual, mockFrameworkUsed)) + return assertions[assertTrue](utilsClassId[deepEquals](expected, actual)) } @Suppress("unused") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index eb737920a5..9bc03afb09 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -134,14 +134,14 @@ data class ExpressionWithType(val type: ClassId, val expression: CgExpression) * Check if a method is an util method of the current class */ internal fun CgContextOwner.isUtil(method: MethodId): Boolean { - return method in currentUtilMethodProvider.utilMethodIds + return method in utilMethodProvider.utilMethodIds } /** * Check if a method is an util method that is declared in the test class (not taken from the codegen utils library) */ internal fun CgContextOwner.isTestClassUtil(method: MethodId): Boolean { - return currentUtilMethodProvider is TestClassUtilMethodProvider && isUtil(method) + return utilMethodProvider is TestClassUtilMethodProvider && isUtil(method) } val classCgClassId = CgClassId(Class::class.id, typeParameters = WildcardTypeParameter(), isNullable = false) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt index 8554e8b435..54f0428520 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt @@ -2,8 +2,8 @@ package org.utbot.framework.codegen.model.visitor import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_PACKAGE_NAME -import org.utbot.framework.codegen.model.constructor.builtin.UtUtilMethodProvider import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.utUtilsClassId import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage @@ -47,7 +47,7 @@ internal class CgRendererContext( importedClasses = emptySet(), importedStaticMethods = emptySet(), classPackageName = UT_UTILS_PACKAGE_NAME, - generatedClass = UtUtilMethodProvider.utUtilsClassId, + generatedClass = utUtilsClassId, utilMethodProvider = utilClassKind.utilMethodProvider, codegenLanguage = language, mockFrameworkUsed = utilClassKind.mockFrameworkUsed, 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 bc1bba9e73..62164d8dde 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 @@ -2,6 +2,7 @@ package org.utbot.framework.codegen.model.visitor import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.importIfNeeded import org.utbot.framework.plugin.api.ClassId @@ -14,7 +15,7 @@ import java.lang.reflect.Modifier import java.util.Arrays import java.util.Objects -internal fun TestClassUtilMethodProvider.utilMethodTextById( +internal fun UtilMethodProvider.utilMethodTextById( id: MethodId, mockFrameworkUsed: Boolean, mockFramework: MockFramework, @@ -811,7 +812,7 @@ fun getArrayLength(codegenLanguage: CodegenLanguage) = internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { // if util methods come from codegen utils library and not from the test class, // then we don't need to import any other methods, hence we return from method - val utilMethodProvider = currentUtilMethodProvider as? TestClassUtilMethodProvider ?: return + val utilMethodProvider = utilMethodProvider as? TestClassUtilMethodProvider ?: return for (classId in utilMethodProvider.regularImportsByUtilMethod(id, codegenLanguage)) { importIfNeeded(classId) } From d7d0df936d74da34d14c09b5b8aaf43dfb836a20 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Mon, 8 Aug 2022 17:00:54 +0300 Subject: [PATCH 05/30] Support util class generation in plugin If no util methods are required, then util class will not be generated. Otherwise, we will generate an util class with or without Mockito usage, depending on whether Mockito is used in the tests or not. Note that whenever an util class with Mockito is required, it will be generated and it will rewrite a previous utils file if it exists. That's because the previous file could have been without Mockito. --- .../framework/codegen/utils/MockUtils.java | 10 - .../org/utbot/runtime/utils/MockUtils.java | 31 +++ .../codegen => runtime}/utils/UtUtils.java | 62 +++-- .../framework/codegen/model/CodeGenerator.kt | 104 ++++++++- .../codegen/model/visitor/UtilMethods.kt | 162 +++++++------ .../generator/CodeGenerationController.kt | 212 +++++++++++++++--- .../plugin/models/GenerateTestsModel.kt | 3 +- .../plugin/ui/GenerateTestsDialogWindow.kt | 7 - .../plugin/ui/utils/LibraryMatcher.kt | 1 + 9 files changed, 433 insertions(+), 159 deletions(-) delete mode 100644 utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java create mode 100644 utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java rename utbot-codegen-utils/src/main/java/org/utbot/{framework/codegen => runtime}/utils/UtUtils.java (84%) diff --git a/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java deleted file mode 100644 index 706659392e..0000000000 --- a/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/MockUtils.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.utbot.framework.codegen.utils; - -final class MockUtils { - private MockUtils() {} - - // TODO: for now we have only Mockito but it can be changed in the future - public static boolean isMock(Object obj) { - return org.mockito.Mockito.mockingDetails(obj).isMock(); - } -} diff --git a/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java new file mode 100644 index 0000000000..7a07224593 --- /dev/null +++ b/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java @@ -0,0 +1,31 @@ +package org.utbot.runtime.utils; + +import java.lang.reflect.Method; + +final class MockUtils { + private MockUtils() {} + + // TODO: for now we have only Mockito but it can be changed in the future + + /** + * This method tries to return the following value: `org.mockito.Mockito.mockingDetails(obj).isMock()`, + * but via reflection, because we do not know if Mockito library is on the classpath. + * @param obj an object that we check for being a mock object. + * @return true if we were able to access `isMock()` method, and it returned true, false otherwise + */ + public static boolean isMock(Object obj) { + try { + Class mockitoClass = Class.forName("org.mockito.Mockito"); + Method mockingDetailsMethod = mockitoClass.getDeclaredMethod("mockingDetails", Object.class); + + Object mockingDetails = mockingDetailsMethod.invoke(null, obj); + + Class mockingDetailsClass = Class.forName("org.mockito.MockingDetails"); + Method isMockMethod = mockingDetailsClass.getDeclaredMethod("isMock"); + + return (boolean) isMockMethod.invoke(mockingDetails); + } catch (Exception e) { + return false; + } + } +} diff --git a/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java similarity index 84% rename from utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java rename to utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java index 230b31dfa3..011b1ec1aa 100644 --- a/utbot-codegen-utils/src/main/java/org/utbot/framework/codegen/utils/UtUtils.java +++ b/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java @@ -1,4 +1,4 @@ -package org.utbot.framework.codegen.utils; +package org.utbot.runtime.utils; import java.lang.reflect.InvocationTargetException; import java.util.HashSet; @@ -149,11 +149,11 @@ public int hashCode() { } } - public static boolean deepEquals(Object o1, Object o2, boolean mockFrameworkUsed) { - return deepEquals(o1, o2, new java.util.HashSet<>(), mockFrameworkUsed); + public static boolean deepEquals(Object o1, Object o2) { + return deepEquals(o1, o2, new java.util.HashSet<>()); } - private static boolean deepEquals(Object o1, Object o2, java.util.Set visited, boolean mockFrameworkUsed) { + private static boolean deepEquals(Object o1, Object o2, java.util.Set visited) { visited.add(new FieldsPair(o1, o2)); if (o1 == o2) { @@ -169,7 +169,7 @@ private static boolean deepEquals(Object o1, Object o2, java.util.Set) o1, (Iterable) o2, visited, mockFrameworkUsed); + return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited); } if (o2 instanceof Iterable) { @@ -181,7 +181,7 @@ private static boolean deepEquals(Object o1, Object o2, java.util.Set) o1, (java.util.stream.Stream) o2, visited, mockFrameworkUsed); + return streamsDeepEquals((java.util.stream.Stream) o1, (java.util.stream.Stream) o2, visited); } if (o2 instanceof java.util.stream.Stream) { @@ -193,7 +193,7 @@ private static boolean deepEquals(Object o1, Object o2, java.util.Set) o1, (java.util.Map) o2, visited, mockFrameworkUsed); + return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); } if (o2 instanceof java.util.Map) { @@ -207,7 +207,7 @@ private static boolean deepEquals(Object o1, Object o2, java.util.Set(), mockFrameworkUsed); + public static boolean arraysDeepEquals(Object arr1, Object arr2) { + return arraysDeepEquals(arr1, arr2, new HashSet<>()); } - private static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited, boolean mockFrameworkUsed) { + private static 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; @@ -263,7 +259,7 @@ private static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set< for (int i = 0; i < length; i++) { Object item1 = java.lang.reflect.Array.get(arr1, i); Object item2 = java.lang.reflect.Array.get(arr2, i); - if (!deepEquals(item1, item2, visited, mockFrameworkUsed)) { + if (!deepEquals(item1, item2, visited)) { return false; } } @@ -271,15 +267,15 @@ private static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set< return true; } - public static boolean iterablesDeepEquals(Iterable i1, Iterable i2, boolean mockFrameworkUsed) { - return iterablesDeepEquals(i1, i2, new HashSet<>(), mockFrameworkUsed); + public static boolean iterablesDeepEquals(Iterable i1, Iterable i2) { + return iterablesDeepEquals(i1, i2, new HashSet<>()); } - private static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited, boolean mockFrameworkUsed) { + private static 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, mockFrameworkUsed)) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { return false; } } @@ -293,22 +289,20 @@ private static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java. public static boolean streamsDeepEquals( java.util.stream.Stream s1, - java.util.stream.Stream s2, - boolean mockFrameworkUsed + java.util.stream.Stream s2 ) { - return streamsDeepEquals(s1, s2, new HashSet<>(), mockFrameworkUsed); + return streamsDeepEquals(s1, s2, new HashSet<>()); } private static boolean streamsDeepEquals( java.util.stream.Stream s1, java.util.stream.Stream s2, - java.util.Set visited, - boolean mockFrameworkUsed + java.util.Set visited ) { final java.util.Iterator firstIterator = s1.iterator(); final java.util.Iterator secondIterator = s2.iterator(); while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited, mockFrameworkUsed)) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { return false; } } @@ -322,17 +316,15 @@ private static boolean streamsDeepEquals( public static boolean mapsDeepEquals( java.util.Map m1, - java.util.Map m2, - boolean mockFrameworkUsed + java.util.Map m2 ) { - return mapsDeepEquals(m1, m2, new HashSet<>(), mockFrameworkUsed); + return mapsDeepEquals(m1, m2, new HashSet<>()); } private static boolean mapsDeepEquals( java.util.Map m1, java.util.Map m2, - java.util.Set visited, - boolean mockFrameworkUsed + java.util.Set visited ) { final java.util.Iterator> firstIterator = m1.entrySet().iterator(); final java.util.Iterator> secondIterator = m2.entrySet().iterator(); @@ -340,11 +332,11 @@ private static boolean mapsDeepEquals( final java.util.Map.Entry firstEntry = firstIterator.next(); final java.util.Map.Entry secondEntry = secondIterator.next(); - if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited, mockFrameworkUsed)) { + if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { return false; } - if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited, mockFrameworkUsed)) { + if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited)) { return false; } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 91a1fe5f27..4c888699db 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -6,9 +6,13 @@ import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.StaticsMocking import org.utbot.framework.codegen.TestFramework +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.model.constructor.builtin.UtilClassFileMethodProvider import org.utbot.framework.codegen.model.constructor.CgMethodTestSet 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.tree.CgTestClassConstructor +import org.utbot.framework.codegen.model.constructor.tree.CgUtilClassConstructor import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.codegen.model.tree.AbstractCgClassFile import org.utbot.framework.codegen.model.tree.CgRegularClassFile @@ -23,9 +27,9 @@ import org.utbot.framework.codegen.model.constructor.TestClassModel class CodeGenerator( private val classUnderTest: ClassId, paramNames: MutableMap> = mutableMapOf(), - codegenUtilsLibraryUsed: Boolean = false, + generateUtilClassFile: Boolean = false, testFramework: TestFramework = TestFramework.defaultItem, - mockFramework: MockFramework? = MockFramework.defaultItem, + mockFramework: MockFramework = MockFramework.defaultItem, staticsMocking: StaticsMocking = StaticsMocking.defaultItem, forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, generateWarningsForStaticMocking: Boolean = true, @@ -38,9 +42,10 @@ class CodeGenerator( ) { private var context: CgContext = CgContext( classUnderTest = classUnderTest, + generateUtilClassFile = generateUtilClassFile, paramNames = paramNames, testFramework = testFramework, - mockFramework = mockFramework ?: MockFramework.MOCKITO, + mockFramework = mockFramework, codegenLanguage = codegenLanguage, parametrizedTestSource = parameterizedTestSource, staticsMocking = staticsMocking, @@ -60,7 +65,7 @@ class CodeGenerator( fun generateAsStringWithTestReport( testSets: Collection, testClassCustomName: String? = null, - ): TestsCodeWithTestReport { + ): CodeGenerationResult { val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList() return generateAsStringWithTestReport(cgTestSets, testClassCustomName) } @@ -68,11 +73,15 @@ class CodeGenerator( private fun generateAsStringWithTestReport( cgTestSets: List, testClassCustomName: String? = null, - ): TestsCodeWithTestReport = withCustomContext(testClassCustomName) { + ): CodeGenerationResult = withCustomContext(testClassCustomName) { context.withTestClassFileScope { val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets) val testClassFile = CgTestClassConstructor(context).construct(testClassModel) - TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport) + CodeGenerationResult( + generatedCode = renderClassFile(testClassFile), + utilClassKind = UtilClassKind.fromCgContextOrNull(context), + testsGenerationReport = testClassFile.testsGenerationReport + ) } } @@ -101,5 +110,86 @@ class CodeGenerator( } } -data class TestsCodeWithTestReport(val generatedCode: String, val testsGenerationReport: TestsGenerationReport) +/** + * @property generatedCode the source code of the test class + * @property utilClassKind the kind of util class if it is required, otherwise - null + * @property testsGenerationReport some info about test generation process + */ +data class CodeGenerationResult( + val generatedCode: String, + // null if no util class needed, e.g. when we are using a library or generating utils directly into test class + val utilClassKind: UtilClassKind?, + val testsGenerationReport: TestsGenerationReport +) + +/** + * A kind of util class. See the description of each kind at their respective classes. + * @property utilMethodProvider a [UtilClassFileMethodProvider] containing information about + * utilities that come from a separately generated UtUtils class + * (as opposed to utils that are declared directly in the test class, for example). + * @property mockFrameworkUsed a flag indicating if a mock framework was used. + * For detailed description see [CgContextOwner.mockFrameworkUsed]. + * @property mockFramework a framework used to create mocks + * @property priority when we generate multiple test classes, they can require different [UtilClassKind]. + * We will generate an util class corresponding to the kind with the greatest priority. + * For example, one test class may not use mocks, but the other one does. + * Then we will generate an util class with mocks, because it has a greater priority (see [UtUtilsWithMockito]). + */ +sealed class UtilClassKind( + internal val utilMethodProvider: UtilClassFileMethodProvider, + internal val mockFrameworkUsed: Boolean, + internal val mockFramework: MockFramework = MockFramework.MOCKITO, + private val priority: Int +) : Comparable { + + /** + * A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework. + */ + class RegularUtUtils internal constructor(provider: UtilClassFileMethodProvider) + : UtilClassKind(provider, mockFrameworkUsed = false, priority = 0) + + /** + * A kind of UtUtils class that uses a mock framework. At the moment the framework is Mockito. + */ + class UtUtilsWithMockito internal constructor(provider: UtilClassFileMethodProvider) + : UtilClassKind(provider, mockFrameworkUsed = true, priority = 1) + + override fun compareTo(other: UtilClassKind): Int { + return priority.compareTo(other.priority) + } + /** + * Construct an util class file as a [CgRegularClassFile] and render it. + * @return the text of the generated util class file. + */ + fun getUtilClassText(codegenLanguage: CodegenLanguage): String { + val utilClassFile = CgUtilClassConstructor.constructUtilsClassFile(this) + val renderer = CgAbstractRenderer.makeRenderer(this, codegenLanguage) + utilClassFile.accept(renderer) + return renderer.toString() + } + + companion object { + /** + * Check if an util class is required, and if so, what kind. + * @return null if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider], + * because it means that util methods will be taken from some other provider (e.g. utbot-codegen-utils library) + * or they will be generated directly into the test class (in this case provider will be [TestClassUtilMethodProvider]) + */ + internal fun fromCgContextOrNull(context: CgContext): UtilClassKind? { + val provider = context.utilMethodProvider as? UtilClassFileMethodProvider ?: return null + if (!context.mockFrameworkUsed) { + return RegularUtUtils(provider) + } + return when (context.mockFramework) { + MockFramework.MOCKITO -> UtUtilsWithMockito(provider) + // in case we will add any other mock frameworks, newer Kotlin compiler versions + // will report a non-exhaustive 'when', so we will not forget to support them here as well + } + } + + const val UT_UTILS_PACKAGE_NAME = "org.utbot.runtime.utils" + const val UT_UTILS_CLASS_NAME = "UtUtils" + const val PACKAGE_DELIMITER = "." + } +} 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 62164d8dde..5294a17f2f 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 @@ -15,39 +15,56 @@ import java.lang.reflect.Modifier import java.util.Arrays import java.util.Objects +private enum class Visibility(val text: String) { + PRIVATE("private"), + @Suppress("unused") + PROTECTED("protected"), + PUBLIC("public"); + + infix fun by(language: CodegenLanguage): String { + if (this == PUBLIC && language == CodegenLanguage.KOTLIN) { + // public is default in Kotlin + return "" + } + return "$text " + } +} + internal fun UtilMethodProvider.utilMethodTextById( id: MethodId, mockFrameworkUsed: Boolean, mockFramework: MockFramework, codegenLanguage: CodegenLanguage ): Result = runCatching { + // If util methods are declared in the test class, then they are private. Otherwise, they are public. + val visibility = if (this is TestClassUtilMethodProvider) Visibility.PRIVATE else Visibility.PUBLIC with(this) { when (id) { - getUnsafeInstanceMethodId -> getUnsafeInstance(codegenLanguage) - createInstanceMethodId -> createInstance(codegenLanguage) - createArrayMethodId -> createArray(codegenLanguage) - setFieldMethodId -> setField(codegenLanguage) - setStaticFieldMethodId -> setStaticField(codegenLanguage) - getFieldValueMethodId -> getFieldValue(codegenLanguage) - getStaticFieldValueMethodId -> getStaticFieldValue(codegenLanguage) - getEnumConstantByNameMethodId -> getEnumConstantByName(codegenLanguage) - deepEqualsMethodId -> deepEquals(codegenLanguage, mockFrameworkUsed, mockFramework) - arraysDeepEqualsMethodId -> arraysDeepEquals(codegenLanguage) - iterablesDeepEqualsMethodId -> iterablesDeepEquals(codegenLanguage) - streamsDeepEqualsMethodId -> streamsDeepEquals(codegenLanguage) - mapsDeepEqualsMethodId -> mapsDeepEquals(codegenLanguage) - hasCustomEqualsMethodId -> hasCustomEquals(codegenLanguage) - getArrayLengthMethodId -> getArrayLength(codegenLanguage) + getUnsafeInstanceMethodId -> getUnsafeInstance(visibility, codegenLanguage) + createInstanceMethodId -> createInstance(visibility, codegenLanguage) + createArrayMethodId -> createArray(visibility, codegenLanguage) + setFieldMethodId -> setField(visibility, codegenLanguage) + setStaticFieldMethodId -> setStaticField(visibility, codegenLanguage) + getFieldValueMethodId -> getFieldValue(visibility, codegenLanguage) + getStaticFieldValueMethodId -> getStaticFieldValue(visibility, codegenLanguage) + getEnumConstantByNameMethodId -> getEnumConstantByName(visibility, codegenLanguage) + deepEqualsMethodId -> deepEquals(visibility, codegenLanguage, mockFrameworkUsed, mockFramework) + arraysDeepEqualsMethodId -> arraysDeepEquals(visibility, codegenLanguage) + iterablesDeepEqualsMethodId -> iterablesDeepEquals(visibility, codegenLanguage) + streamsDeepEqualsMethodId -> streamsDeepEquals(visibility, codegenLanguage) + mapsDeepEqualsMethodId -> mapsDeepEquals(visibility, codegenLanguage) + hasCustomEqualsMethodId -> hasCustomEquals(visibility, codegenLanguage) + getArrayLengthMethodId -> getArrayLength(visibility, codegenLanguage) else -> error("Unknown util method for class $this: $id") } } } -fun getEnumConstantByName(language: CodegenLanguage): String = +private fun getEnumConstantByName(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { + ${visibility by language}static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); for (java.lang.reflect.Field field : fields) { String fieldName = field.getName(); @@ -64,7 +81,7 @@ fun getEnumConstantByName(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getEnumConstantByName(enumClass: Class<*>, name: String): kotlin.Any? { + ${visibility by language}fun getEnumConstantByName(enumClass: Class<*>, name: String): kotlin.Any? { val fields: kotlin.Array = enumClass.declaredFields for (field in fields) { val fieldName = field.name @@ -81,11 +98,11 @@ fun getEnumConstantByName(language: CodegenLanguage): String = } }.trimIndent() -fun getStaticFieldValue(language: CodegenLanguage): String = +private fun getStaticFieldValue(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { + ${visibility by language}static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { java.lang.reflect.Field field; Class originClass = clazz; do { @@ -108,7 +125,7 @@ fun getStaticFieldValue(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getStaticFieldValue(clazz: Class<*>, fieldName: String): kotlin.Any? { + ${visibility by language}fun getStaticFieldValue(clazz: Class<*>, fieldName: String): kotlin.Any? { var currentClass: Class<*>? = clazz var field: java.lang.reflect.Field do { @@ -131,11 +148,11 @@ fun getStaticFieldValue(language: CodegenLanguage): String = } }.trimIndent() -fun getFieldValue(language: CodegenLanguage): String = +private fun getFieldValue(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getFieldValue(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException { + ${visibility by language}static Object getFieldValue(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException { Class clazz = obj.getClass(); java.lang.reflect.Field field; do { @@ -158,7 +175,7 @@ fun getFieldValue(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getFieldValue(any: kotlin.Any, fieldName: String): kotlin.Any? { + ${visibility by language}fun getFieldValue(any: kotlin.Any, fieldName: String): kotlin.Any? { var clazz: Class<*>? = any.javaClass var field: java.lang.reflect.Field do { @@ -181,11 +198,11 @@ fun getFieldValue(language: CodegenLanguage): String = } }.trimIndent() -fun setStaticField(language: CodegenLanguage): String = +private fun setStaticField(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { + ${visibility by language}static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { java.lang.reflect.Field field; do { @@ -208,7 +225,7 @@ fun setStaticField(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun setStaticField(defaultClass: Class<*>, fieldName: String, fieldValue: kotlin.Any?) { + ${visibility by language}fun setStaticField(defaultClass: Class<*>, fieldName: String, fieldValue: kotlin.Any?) { var field: java.lang.reflect.Field? var clazz = defaultClass @@ -232,11 +249,11 @@ fun setStaticField(language: CodegenLanguage): String = } }.trimIndent() -fun setField(language: CodegenLanguage): String = +private fun setField(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static void setField(Object object, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { + ${visibility by language}static void setField(Object object, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = object.getClass(); java.lang.reflect.Field field; @@ -260,7 +277,7 @@ fun setField(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun setField(any: kotlin.Any, fieldName: String, fieldValue: kotlin.Any?) { + ${visibility by language}fun setField(any: kotlin.Any, fieldName: String, fieldValue: kotlin.Any?) { var clazz: Class<*> = any.javaClass var field: java.lang.reflect.Field? do { @@ -283,11 +300,11 @@ fun setField(language: CodegenLanguage): String = } }.trimIndent() -fun createArray(language: CodegenLanguage): String = +private fun createArray(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { + ${visibility by language}static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); for (int i = 0; i < values.length; i++) { @@ -300,7 +317,7 @@ fun createArray(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun createArray( + ${visibility by language}fun createArray( className: String, length: Int, vararg values: kotlin.Any @@ -317,11 +334,11 @@ fun createArray(language: CodegenLanguage): String = } }.trimIndent() -fun createInstance(language: CodegenLanguage): String = +private fun createInstance(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object createInstance(String className) throws Exception { + ${visibility by language}static Object createInstance(String className) throws Exception { Class clazz = Class.forName(className); return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class.class) .invoke(getUnsafeInstance(), clazz); @@ -330,7 +347,7 @@ fun createInstance(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun createInstance(className: String): kotlin.Any? { + ${visibility by language}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) @@ -339,11 +356,11 @@ fun createInstance(language: CodegenLanguage): String = } }.trimIndent() -fun getUnsafeInstance(language: CodegenLanguage): String = +private fun getUnsafeInstance(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static Object getUnsafeInstance() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + ${visibility by language}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); @@ -352,7 +369,7 @@ fun getUnsafeInstance(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun getUnsafeInstance(): kotlin.Any? { + ${visibility by language}fun getUnsafeInstance(): kotlin.Any? { val f: java.lang.reflect.Field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe") f.isAccessible = true return f[null] @@ -367,13 +384,19 @@ fun getUnsafeInstance(language: CodegenLanguage): String = private fun isMockCondition(mockFrameworkUsed: Boolean, mockFramework: MockFramework): String { if (!mockFrameworkUsed) return "" - // TODO for now we have only Mockito but in can be changed in the future - if (mockFramework != MockFramework.MOCKITO) return "" - - return " && !org.mockito.Mockito.mockingDetails(o1).isMock()" + return when (mockFramework) { + MockFramework.MOCKITO -> " && !org.mockito.Mockito.mockingDetails(o1).isMock()" + // in case we will add any other mock frameworks, newer Kotlin compiler versions + // will report a non-exhaustive 'when', so we will not forget to support them here as well + } } -fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramework: MockFramework): String = +private fun deepEquals( + visibility: Visibility, + language: CodegenLanguage, + mockFrameworkUsed: Boolean, + mockFramework: MockFramework +): String = when (language) { CodegenLanguage.JAVA -> { """ @@ -391,16 +414,16 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FieldsPair that = (FieldsPair) o; - return Objects.equals(o1, that.o1) && Objects.equals(o2, that.o2); + return java.util.Objects.equals(o1, that.o1) && java.util.Objects.equals(o2, that.o2); } @Override public int hashCode() { - return Objects.hash(o1, o2); + return java.util.Objects.hash(o1, o2); } } - private static boolean deepEquals(Object o1, Object o2) { + ${visibility by language}static boolean deepEquals(Object o1, Object o2) { return deepEquals(o1, o2, new java.util.HashSet<>()); } @@ -500,7 +523,7 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } CodegenLanguage.KOTLIN -> { """ - private fun deepEquals(o1: kotlin.Any?, o2: kotlin.Any?): Boolean = deepEquals(o1, o2, hashSetOf()) + ${visibility by language}fun deepEquals(o1: kotlin.Any?, o2: kotlin.Any?): Boolean = deepEquals(o1, o2, hashSetOf()) private fun deepEquals( o1: kotlin.Any?, @@ -575,11 +598,11 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } } -fun arraysDeepEquals(language: CodegenLanguage): String = +private fun arraysDeepEquals(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { + ${visibility by language}static 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; @@ -597,7 +620,7 @@ fun arraysDeepEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun arraysDeepEquals( + ${visibility by language}fun arraysDeepEquals( arr1: kotlin.Any?, arr2: kotlin.Any?, visited: kotlin.collections.MutableSet> @@ -617,11 +640,11 @@ fun arraysDeepEquals(language: CodegenLanguage): String = } } -fun iterablesDeepEquals(language: CodegenLanguage): String = +private fun iterablesDeepEquals(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { + ${visibility by language}static 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()) { @@ -640,7 +663,7 @@ fun iterablesDeepEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun iterablesDeepEquals( + ${visibility by language}fun iterablesDeepEquals( i1: Iterable<*>, i2: Iterable<*>, visited: kotlin.collections.MutableSet> @@ -657,11 +680,11 @@ fun iterablesDeepEquals(language: CodegenLanguage): String = } } -fun streamsDeepEquals(language: CodegenLanguage): String = +private fun streamsDeepEquals(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static boolean streamsDeepEquals( + ${visibility by language}static boolean streamsDeepEquals( java.util.stream.Stream s1, java.util.stream.Stream s2, java.util.Set visited @@ -684,7 +707,7 @@ fun streamsDeepEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun streamsDeepEquals( + ${visibility by language}fun streamsDeepEquals( s1: java.util.stream.Stream<*>, s2: java.util.stream.Stream<*>, visited: kotlin.collections.MutableSet> @@ -701,11 +724,11 @@ fun streamsDeepEquals(language: CodegenLanguage): String = } } -fun mapsDeepEquals(language: CodegenLanguage): String = +private fun mapsDeepEquals(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static boolean mapsDeepEquals( + ${visibility by language}static boolean mapsDeepEquals( java.util.Map m1, java.util.Map m2, java.util.Set visited @@ -735,7 +758,7 @@ fun mapsDeepEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun mapsDeepEquals( + ${visibility by language}fun mapsDeepEquals( m1: kotlin.collections.Map<*, *>, m2: kotlin.collections.Map<*, *>, visited: kotlin.collections.MutableSet> @@ -757,11 +780,11 @@ fun mapsDeepEquals(language: CodegenLanguage): String = } } -fun hasCustomEquals(language: CodegenLanguage): String = +private fun hasCustomEquals(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { """ - private static boolean hasCustomEquals(Class clazz) { + ${visibility by language}static boolean hasCustomEquals(Class clazz) { while (!Object.class.equals(clazz)) { try { clazz.getDeclaredMethod("equals", Object.class); @@ -778,7 +801,7 @@ fun hasCustomEquals(language: CodegenLanguage): String = } CodegenLanguage.KOTLIN -> { """ - private fun hasCustomEquals(clazz: Class<*>): Boolean { + ${visibility by language}fun hasCustomEquals(clazz: Class<*>): Boolean { var c = clazz while (kotlin.Any::class.java != c) { try { @@ -795,17 +818,17 @@ fun hasCustomEquals(language: CodegenLanguage): String = } } -fun getArrayLength(codegenLanguage: CodegenLanguage) = - when (codegenLanguage) { +private fun getArrayLength(visibility: Visibility, language: CodegenLanguage) = + when (language) { CodegenLanguage.JAVA -> """ - private static int getArrayLength(Object arr) { + ${visibility by language}static int getArrayLength(Object arr) { return java.lang.reflect.Array.getLength(arr); } """.trimIndent() CodegenLanguage.KOTLIN -> """ - private fun getArrayLength(arr: kotlin.Any?): Int = java.lang.reflect.Array.getLength(arr) + ${visibility by language}fun getArrayLength(arr: kotlin.Any?): Int = java.lang.reflect.Array.getLength(arr) """.trimIndent() } @@ -821,7 +844,10 @@ internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { } } -private fun TestClassUtilMethodProvider.regularImportsByUtilMethod(id: MethodId, codegenLanguage: CodegenLanguage): List { +private fun TestClassUtilMethodProvider.regularImportsByUtilMethod( + id: MethodId, + codegenLanguage: CodegenLanguage +): List { val fieldClassId = Field::class.id return when (id) { getUnsafeInstanceMethodId -> listOf(fieldClassId) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 19da58144e..a999ed3c0a 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -5,12 +5,14 @@ import com.intellij.codeInsight.FileModificationService import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.ide.fileTemplates.JavaTemplateUtil +import com.intellij.ide.highlighter.JavaFileType import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction import com.intellij.openapi.command.executeCommand import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Computable @@ -22,6 +24,7 @@ import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory import com.intellij.psi.PsiMethod import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager @@ -31,6 +34,7 @@ import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.util.IncorrectOperationException import com.siyeh.ig.psiutils.ImportUtils import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass +import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.util.toPsiDirectory @@ -51,7 +55,12 @@ import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.CodeGenerator -import org.utbot.framework.codegen.model.TestsCodeWithTestReport +import org.utbot.framework.codegen.model.CodeGenerationResult +import org.utbot.framework.codegen.model.UtilClassKind +import org.utbot.framework.codegen.model.UtilClassKind.Companion.PACKAGE_DELIMITER +import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME +import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_PACKAGE_NAME +import org.utbot.framework.codegen.model.UtilClassKind.UtUtilsWithMockito import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.ExecutableId @@ -84,6 +93,22 @@ import org.utbot.intellij.plugin.util.IntelliJApiHelper.run object CodeGenerationController { + private class UtilMethodListener { + var requiredUtilClassKind: UtilClassKind? = null + + fun onTestClassGenerated(result: CodeGenerationResult) { + requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind) + } + + private fun > maxOfNullable(a: T?, b: T?): T? { + return when { + a == null -> b + b == null -> a + else -> maxOf(a, b) + } + } + } + fun generateTests( model: GenerateTestsModel, testSetsByClass: Map>, @@ -96,19 +121,29 @@ object CodeGenerationController { val reports = mutableListOf() val testFiles = mutableListOf() + val utilMethodListener = UtilMethodListener() for (srcClass in testSetsByClass.keys) { val testSets = testSetsByClass[srcClass] ?: continue try { - val classPackageName = if (model.testPackageName.isNullOrEmpty()) - srcClass.containingFile.containingDirectory.getPackage()?.qualifiedName else model.testPackageName + val classPackageName = model.getTestClassPackageNameFor(srcClass) val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory val testClass = createTestClass(srcClass, testDirectory, model) ?: continue - val file = testClass.containingFile + val testClassFile = testClass.containingFile val cut = psi2KClass[srcClass] ?: error("Didn't find KClass instance for class ${srcClass.name}") runWriteCommandAction(model.project, "Generate tests with UtBot", null, { try { - generateCodeAndReport(srcClass, cut, testClass, file, testSets, model, latch, reports) - testFiles.add(file) + generateCodeAndReport( + srcClass, + cut, + testClass, + testClassFile, + testSets, + model, + latch, + reports, + utilMethodListener + ) + testFiles.add(testClassFile) } catch (e: IncorrectOperationException) { showCreatingClassError(model.project, createTestClassName(srcClass)) } @@ -118,6 +153,31 @@ object CodeGenerationController { } } + + run(EDT_LATER) { + waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) { + val utilClassKind = utilMethodListener.requiredUtilClassKind + if (utilClassKind != null) { + // create a directory to put utils class into + val utilClassDirectory = createUtUtilSubdirectories(baseTestDirectory) + + val language = model.codegenLanguage + val utUtilsName = "$UT_UTILS_CLASS_NAME${language.extension}" + + val utUtilsAlreadyExists = utilClassDirectory.findFile(utUtilsName) != null + + // we generate and write an util class in one of the two cases: + // - util file does not yet exist --> then we generate it, because it is required by generated tests + // - utilClassKind is UtUtilsWithMockito --> then we generate utils class and add it to utils directory. + // If utils file already exists, we overwrite it, because existing utils may be without Mockito, + // and we want to make sure that the generated utils use Mockito. + if (!utUtilsAlreadyExists || utilClassKind is UtUtilsWithMockito) { + generateAndWriteUtilClass(utUtilsName, utilClassDirectory, model, utilClassKind) + } + } + } + } + run(READ_ACTION) { val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) run(THREAD_POOL) { @@ -145,10 +205,98 @@ object CodeGenerationController { } } - private fun waitForCountDown(latch: CountDownLatch, action: Runnable) { + /** + * Create package directories if needed for UtUtils class. + * Then generate and create a UtUtils class file in the utils package. + * Also run reformatting for the generated class. + */ + private fun generateAndWriteUtilClass( + utUtilsName: String, + utilClassDirectory: PsiDirectory, + model: GenerateTestsModel, + utilClassKind: UtilClassKind + ) { + val psiDocumentManager = PsiDocumentManager.getInstance(model.project) + + val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) + + val existingUtUtilsDocument = utilClassDirectory + .findFile(utUtilsName) + ?.let { psiDocumentManager.getDocument(it) } + + val utUtilsFile = if (existingUtUtilsDocument != null) { + executeCommand { + existingUtUtilsDocument.setText(utUtilsText) + } + unblockDocument(model.project, existingUtUtilsDocument) + psiDocumentManager.getPsiFile(existingUtUtilsDocument) + } else { + val utUtilsFile = PsiFileFactory.getInstance(model.project) + .createFileFromText( + utUtilsName, + model.codegenLanguage.fileType, + utUtilsText + ) + // add the UtUtils class file into the utils directory + runWriteCommandAction(model.project) { + utilClassDirectory.add(utUtilsFile) + } + utUtilsFile + } + + // there's only one class in the file + val utUtilsClass = (utUtilsFile as PsiClassOwner).classes.first() + + runWriteCommandAction(model.project, "UtBot util class reformatting", null, { + reformat(model, utUtilsFile, utUtilsClass) + }) + + val utUtilsDocument = psiDocumentManager.getDocument(utUtilsFile) + ?: error("Failed to get a Document for UtUtils file") + + unblockDocument(model.project, utUtilsDocument) + } + + /** + * @param srcClass class under test + * @return name of the package of a given [srcClass]. + * Null is returned if [PsiDirectory.getPackage] call returns null for the [srcClass] directory. + */ + private fun GenerateTestsModel.getTestClassPackageNameFor(srcClass: PsiClass): String? { + return when { + testPackageName.isNullOrEmpty() -> srcClass.containingFile.containingDirectory.getPackage()?.qualifiedName + else -> testPackageName + } + } + + /** + * Create all package directories for UtUtils class. + * @return the innermost directory - utils from `org.utbot.runtime.utils` + */ + private fun createUtUtilSubdirectories(baseTestDirectory: PsiDirectory): PsiDirectory { + val directoryNames = UT_UTILS_PACKAGE_NAME.split(PACKAGE_DELIMITER) + var currentDirectory = baseTestDirectory + runWriteCommandAction(baseTestDirectory.project) { + for (name in directoryNames) { + currentDirectory = currentDirectory.findSubdirectory(name) ?: currentDirectory.createSubdirectory(name) + } + } + return currentDirectory + } + + /** + * @return Java or Kotlin file type depending on the given [CodegenLanguage] + */ + private val CodegenLanguage.fileType: FileType + get() = when (this) { + CodegenLanguage.JAVA -> JavaFileType.INSTANCE + CodegenLanguage.KOTLIN -> KotlinFileType.INSTANCE + } + + private fun waitForCountDown(latch: CountDownLatch, timeout: Long = 5, timeUnit: TimeUnit = TimeUnit.SECONDS, action: Runnable) { try { - if (!latch.await(5, TimeUnit.SECONDS)) { - run(THREAD_POOL) { waitForCountDown(latch, action) } + if (!latch.await(timeout, timeUnit)) { + run(THREAD_POOL) { waitForCountDown(latch, timeout, timeUnit, action) } } else { action.run() } @@ -254,33 +402,36 @@ object CodeGenerationController { model: GenerateTestsModel, reportsCountDown: CountDownLatch, reports: MutableList, + utilMethodListener: UtilMethodListener ) { val classMethods = srcClass.extractClassMethodsIncludingNested(false) val paramNames = DumbService.getInstance(model.project) .runReadActionInSmartMode(Computable { findMethodParamNames(classUnderTest, classMethods) }) val codeGenerator = CodeGenerator( - classUnderTest = classUnderTest.id, - paramNames = paramNames.toMutableMap(), - testFramework = model.testFramework, - mockFramework = model.mockFramework, - codegenLanguage = model.codegenLanguage, - parameterizedTestSource = model.parametrizedTestSource, - staticsMocking = model.staticsMocking, - forceStaticMocking = model.forceStaticMocking, - generateWarningsForStaticMocking = model.generateWarningsForStaticMocking, - runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, - hangingTestsTimeout = model.hangingTestsTimeout, - enableTestsTimeout = true, - testClassPackageName = testClass.packageName - ) + classUnderTest = classUnderTest.id, + generateUtilClassFile = true, + paramNames = paramNames.toMutableMap(), + testFramework = model.testFramework, + mockFramework = model.mockFramework, + codegenLanguage = model.codegenLanguage, + parameterizedTestSource = model.parametrizedTestSource, + staticsMocking = model.staticsMocking, + forceStaticMocking = model.forceStaticMocking, + generateWarningsForStaticMocking = model.generateWarningsForStaticMocking, + runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, + hangingTestsTimeout = model.hangingTestsTimeout, + enableTestsTimeout = true, + testClassPackageName = testClass.packageName + ) val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass) //TODO: Use PsiDocumentManager.getInstance(model.project).getDocument(file) // if we don't want to open _all_ new files with tests in editor one-by-one run(THREAD_POOL) { - val testsCodeWithTestReport = codeGenerator.generateAsStringWithTestReport(testSets) - val generatedTestsCode = testsCodeWithTestReport.generatedCode + val codeGenerationResult = codeGenerator.generateAsStringWithTestReport(testSets) + utilMethodListener.onTestClassGenerated(codeGenerationResult) + val generatedTestsCode = codeGenerationResult.generatedCode run(EDT_LATER) { run(WRITE_ACTION) { unblockDocument(testClass.project, editor.document) @@ -305,17 +456,18 @@ object CodeGenerationController { unblockDocument(testClassUpdated.project, editor.document) // uploading formatted code - val testsCodeWithTestReportFormatted = - testsCodeWithTestReport.copy(generatedCode = file.text) + val codeGenerationResultFormatted = + codeGenerationResult.copy(generatedCode = file.text) // creating and saving reports - reports += testsCodeWithTestReportFormatted.testsGenerationReport + reports += codeGenerationResultFormatted.testsGenerationReport + run(WRITE_ACTION) { saveSarifReport( testClassUpdated, testSets, model, - testsCodeWithTestReportFormatted, + codeGenerationResultFormatted, ) } unblockDocument(testClassUpdated.project, editor.document) @@ -358,7 +510,7 @@ object CodeGenerationController { testClass: PsiClass, testSets: List, model: GenerateTestsModel, - testsCodeWithTestReport: TestsCodeWithTestReport, + testsCodeWithTestReport: CodeGenerationResult, ) { val project = model.project val generatedTestsCode = testsCodeWithTestReport.generatedCode diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt index cf858d4d37..5c4928a241 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -48,11 +48,10 @@ data class GenerateTestsModel( var testPackageName: String? = null lateinit var testFramework: TestFramework lateinit var mockStrategy: MockStrategyApi - var mockFramework: MockFramework? = null + lateinit var mockFramework: MockFramework lateinit var staticsMocking: StaticsMocking lateinit var parametrizedTestSource: ParametrizedTestSource lateinit var codegenLanguage: CodegenLanguage - var codegenUtilsLibraryInstalled: Boolean = false lateinit var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour lateinit var hangingTestsTimeout: HangingTestsTimeout lateinit var forceStaticMocking: ForceStaticMocking 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 80c8b01e61..9fa4228dcb 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 @@ -124,7 +124,6 @@ import org.utbot.intellij.plugin.ui.components.TestFolderComboWithBrowseButton import org.utbot.intellij.plugin.ui.utils.LibrarySearchScope import org.utbot.intellij.plugin.ui.utils.addSourceRootIfAbsent import org.utbot.intellij.plugin.ui.utils.allLibraries -import org.utbot.intellij.plugin.ui.utils.findCodegenUtilsLibrary import org.utbot.intellij.plugin.ui.utils.findFrameworkLibrary import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle @@ -205,8 +204,6 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m it.isConfigured = staticsMockingConfigured() } - model.codegenUtilsLibraryInstalled = findCodegenUtilsLibrary(model.project, model.testModule) != null - // Configure notification urls callbacks TestsReportNotifier.urlOpeningListener.callbacks[TestReportUrlOpeningListener.mockitoSuffix]?.plusAssign { configureMockFramework() @@ -514,10 +511,6 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m // then process force static mocking case model.generateWarningsForStaticMocking = model.staticsMocking is NoStaticMocking if (model.forceStaticMocking == ForceStaticMocking.FORCE) { - // we have to use mock framework to mock statics, no user provided => choose default - if (model.mockFramework == null) { - model.mockFramework = MockFramework.defaultItem - } // we need mock framework extension to mock statics, no user provided => choose default if (model.staticsMocking is NoStaticMocking) { model.staticsMocking = StaticsMocking.defaultItem diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt index e4f13b3d36..490cd67d37 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt @@ -25,6 +25,7 @@ fun findFrameworkLibrary( scope: LibrarySearchScope = LibrarySearchScope.Module, ): LibraryOrderEntry? = findMatchingLibrary(project, testModule, mockFramework.patterns(), scope) +@Suppress("unused") fun findCodegenUtilsLibrary( project: Project, testModule: Module, From 9c501c17a04386725ccfa348f920774a88e3ee74 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 10 Aug 2022 19:21:17 +0300 Subject: [PATCH 06/30] Simplify some util methods checks --- .../model/constructor/tree/CgCallableAccessManager.kt | 5 ++--- .../codegen/model/constructor/util/ConstructorUtils.kt | 8 -------- 2 files changed, 2 insertions(+), 11 deletions(-) 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 d0a77f62a9..877073080c 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 @@ -18,7 +18,6 @@ import org.utbot.framework.codegen.model.constructor.util.CgComponents import org.utbot.framework.codegen.model.constructor.util.classCgClassId import org.utbot.framework.codegen.model.constructor.util.getAmbiguousOverloadsOf import org.utbot.framework.codegen.model.constructor.util.importIfNeeded -import org.utbot.framework.codegen.model.constructor.util.isTestClassUtil import org.utbot.framework.codegen.model.constructor.util.isUtil import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray @@ -107,7 +106,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA } private fun newMethodCall(methodId: MethodId) { - if (isTestClassUtil(methodId)) requiredUtilMethods += methodId + if (isUtil(methodId)) requiredUtilMethods += methodId importIfNeeded(methodId) //Builtin methods does not have jClass, so [methodId.method] will crash on it, @@ -226,7 +225,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA * @return true if a method can be called with the given arguments without reflection */ private fun MethodId.canBeCalledWith(caller: CgExpression?, args: List): Boolean = - (isTestClassUtil(this) || isAccessibleFrom(testClassPackageName)) + (isUtil(this) || isAccessibleFrom(testClassPackageName)) && caller canBeReceiverOf this && args canBeArgsOf this diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index 9bc03afb09..d4059833b1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -48,7 +48,6 @@ import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.framework.plugin.api.util.arrayLikeName -import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.plugin.api.util.builtinStaticMethodId import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectArrayClassId @@ -137,13 +136,6 @@ internal fun CgContextOwner.isUtil(method: MethodId): Boolean { return method in utilMethodProvider.utilMethodIds } -/** - * Check if a method is an util method that is declared in the test class (not taken from the codegen utils library) - */ -internal fun CgContextOwner.isTestClassUtil(method: MethodId): Boolean { - return utilMethodProvider is TestClassUtilMethodProvider && isUtil(method) -} - val classCgClassId = CgClassId(Class::class.id, typeParameters = WildcardTypeParameter(), isNullable = false) /** From c0e11619c071e9f22b63f2048356087d40d9762b Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 10 Aug 2022 19:23:16 +0300 Subject: [PATCH 07/30] Collect required util methods regardless of util method provider --- .../constructor/tree/TestFrameworkManager.kt | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) 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 e7acf17798..9a36320cfc 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 @@ -3,7 +3,6 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.framework.codegen.Junit4 import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.TestNg -import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.codegen.model.constructor.TestClassContext import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId @@ -119,19 +118,14 @@ internal abstract class TestFrameworkManager(val context: CgContext) } open fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall { - // If an util method provider is not TestClassUtilMethodProvider, then we are using util methods from library. - // In this case we don't need to add required util methods to the test class, - // because they are all already in a library. - if (utilMethodProvider is TestClassUtilMethodProvider) { - requiredUtilMethods += setOf( - utilMethodProvider.deepEqualsMethodId, - utilMethodProvider.arraysDeepEqualsMethodId, - utilMethodProvider.iterablesDeepEqualsMethodId, - utilMethodProvider.streamsDeepEqualsMethodId, - utilMethodProvider.mapsDeepEqualsMethodId, - utilMethodProvider.hasCustomEqualsMethodId - ) - } + requiredUtilMethods += setOf( + utilMethodProvider.deepEqualsMethodId, + utilMethodProvider.arraysDeepEqualsMethodId, + utilMethodProvider.iterablesDeepEqualsMethodId, + utilMethodProvider.streamsDeepEqualsMethodId, + utilMethodProvider.mapsDeepEqualsMethodId, + utilMethodProvider.hasCustomEqualsMethodId + ) // TODO we cannot use common assertEquals because of using custom deepEquals // For this reason we have to use assertTrue here // Unfortunately, if test with assertTrue fails, it gives non informative message false != true From c2ba7a007e8d523b92133023760c6220a37314a4 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 10 Aug 2022 19:25:53 +0300 Subject: [PATCH 08/30] Put information about whether mocks were used into CodeGenerationResult --- .../org/utbot/framework/codegen/model/CodeGenerator.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 4c888699db..67d4c69eeb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -80,7 +80,8 @@ class CodeGenerator( CodeGenerationResult( generatedCode = renderClassFile(testClassFile), utilClassKind = UtilClassKind.fromCgContextOrNull(context), - testsGenerationReport = testClassFile.testsGenerationReport + testsGenerationReport = testClassFile.testsGenerationReport, + mockFrameworkUsed = context.mockFrameworkUsed ) } } @@ -114,12 +115,14 @@ class CodeGenerator( * @property generatedCode the source code of the test class * @property utilClassKind the kind of util class if it is required, otherwise - null * @property testsGenerationReport some info about test generation process + * @property mockFrameworkUsed flag indicating whether any mock objects have been created during code generation ot not */ data class CodeGenerationResult( val generatedCode: String, // null if no util class needed, e.g. when we are using a library or generating utils directly into test class val utilClassKind: UtilClassKind?, - val testsGenerationReport: TestsGenerationReport + val testsGenerationReport: TestsGenerationReport, + val mockFrameworkUsed: Boolean = false ) /** @@ -177,6 +180,7 @@ sealed class UtilClassKind( * or they will be generated directly into the test class (in this case provider will be [TestClassUtilMethodProvider]) */ internal fun fromCgContextOrNull(context: CgContext): UtilClassKind? { + if (context.requiredUtilMethods.isEmpty()) return null val provider = context.utilMethodProvider as? UtilClassFileMethodProvider ?: return null if (!context.mockFrameworkUsed) { return RegularUtUtils(provider) From 3956238d3f068250931d51a2b7bec0b487dcf13c Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 10 Aug 2022 19:45:34 +0300 Subject: [PATCH 09/30] Carefully consider different cases when generating an util class --- .../framework/codegen/model/CodeGenerator.kt | 7 + .../generator/CodeGenerationController.kt | 208 ++++++++++++------ 2 files changed, 153 insertions(+), 62 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 67d4c69eeb..657a572500 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -195,5 +195,12 @@ sealed class UtilClassKind( const val UT_UTILS_PACKAGE_NAME = "org.utbot.runtime.utils" const val UT_UTILS_CLASS_NAME = "UtUtils" const val PACKAGE_DELIMITER = "." + + /** + * List of package names of UtUtils class. + * See whole package name at [UT_UTILS_PACKAGE_NAME]. + */ + val utilsPackages: List + get() = UT_UTILS_PACKAGE_NAME.split(PACKAGE_DELIMITER) } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index a999ed3c0a..8df55f3b89 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -7,6 +7,7 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.ide.fileTemplates.JavaTemplateUtil import com.intellij.ide.highlighter.JavaFileType import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction import com.intellij.openapi.command.executeCommand @@ -57,10 +58,7 @@ import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.codegen.model.CodeGenerationResult import org.utbot.framework.codegen.model.UtilClassKind -import org.utbot.framework.codegen.model.UtilClassKind.Companion.PACKAGE_DELIMITER import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME -import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_PACKAGE_NAME -import org.utbot.framework.codegen.model.UtilClassKind.UtUtilsWithMockito import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.ExecutableId @@ -93,11 +91,13 @@ import org.utbot.intellij.plugin.util.IntelliJApiHelper.run object CodeGenerationController { - private class UtilMethodListener { + private class UtilClassListener { var requiredUtilClassKind: UtilClassKind? = null + var mockFrameworkUsed: Boolean = false fun onTestClassGenerated(result: CodeGenerationResult) { requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind) + mockFrameworkUsed = maxOf(mockFrameworkUsed, result.mockFrameworkUsed) } private fun > maxOfNullable(a: T?, b: T?): T? { @@ -121,7 +121,7 @@ object CodeGenerationController { val reports = mutableListOf() val testFiles = mutableListOf() - val utilMethodListener = UtilMethodListener() + val utilClassListener = UtilClassListener() for (srcClass in testSetsByClass.keys) { val testSets = testSetsByClass[srcClass] ?: continue try { @@ -141,7 +141,7 @@ object CodeGenerationController { model, latch, reports, - utilMethodListener + utilClassListener ) testFiles.add(testClassFile) } catch (e: IncorrectOperationException) { @@ -156,25 +156,29 @@ object CodeGenerationController { run(EDT_LATER) { waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) { - val utilClassKind = utilMethodListener.requiredUtilClassKind - if (utilClassKind != null) { - // create a directory to put utils class into - val utilClassDirectory = createUtUtilSubdirectories(baseTestDirectory) - - val language = model.codegenLanguage - val utUtilsName = "$UT_UTILS_CLASS_NAME${language.extension}" - - val utUtilsAlreadyExists = utilClassDirectory.findFile(utUtilsName) != null - - // we generate and write an util class in one of the two cases: - // - util file does not yet exist --> then we generate it, because it is required by generated tests - // - utilClassKind is UtUtilsWithMockito --> then we generate utils class and add it to utils directory. - // If utils file already exists, we overwrite it, because existing utils may be without Mockito, - // and we want to make sure that the generated utils use Mockito. - if (!utUtilsAlreadyExists || utilClassKind is UtUtilsWithMockito) { - generateAndWriteUtilClass(utUtilsName, utilClassDirectory, model, utilClassKind) - } + val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(baseTestDirectory) + + val utilClassKind = utilClassListener.requiredUtilClassKind + ?: return@waitForCountDown // no util class needed + + val utilClassExists = existingUtilClass != null + val mockFrameworkNotUsed = !utilClassListener.mockFrameworkUsed + + if (utilClassExists && mockFrameworkNotUsed) { + // If util class already exists and mock framework is not used, + // then existing util class is enough, and we don't need to generate a new one. + // That's because both regular and mock versions of util class can work + // with tests that do not use mocks, so we do not have to worry about + // version of util class that we have at the moment. + return@waitForCountDown } + + createOrUpdateUtilClass( + testDirectory = baseTestDirectory, + utilClassKind = utilClassKind, + existingUtilClass = existingUtilClass, + model = model + ) } } @@ -206,55 +210,107 @@ object CodeGenerationController { } /** - * Create package directories if needed for UtUtils class. - * Then generate and create a UtUtils class file in the utils package. - * Also run reformatting for the generated class. + * If [existingUtilClass] is null (no util class exists), then we create package directories for util class, + * create util class itself, and put it into the corresponding directory. + * Otherwise, we overwrite the existing util class with a new one. + * This is necessary in case if existing util class has no mocks support, but the newly generated tests do use mocks. + * So, we overwrite an util class with a new one that does support mocks. + * + * @param testDirectory root test directory where we will put our generated tests. + * @param utilClassKind kind of util class required by the test class(es) that we generated. + * @param existingUtilClass util class that already exists or null if it does not yet exist. + * @param model [GenerateTestsModel] that contains some useful information for util class generation. */ - private fun generateAndWriteUtilClass( - utUtilsName: String, - utilClassDirectory: PsiDirectory, - model: GenerateTestsModel, - utilClassKind: UtilClassKind + private fun createOrUpdateUtilClass( + testDirectory: PsiDirectory, + utilClassKind: UtilClassKind, + existingUtilClass: PsiFile?, + model: GenerateTestsModel ) { - val psiDocumentManager = PsiDocumentManager.getInstance(model.project) + val language = model.codegenLanguage - val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) + val utUtilsFile = if (existingUtilClass == null) { + // create a directory to put utils class into + val utilClassDirectory = createUtUtilSubdirectories(testDirectory) + // create util class file and put it into utils directory + createNewUtilClass(utilClassDirectory, language, utilClassKind, model) + } else { + overwriteUtilClass(existingUtilClass, utilClassKind, model) + } + + val utUtilsClass = runReadAction { + // there's only one class in the file + (utUtilsFile as PsiClassOwner).classes.first() + } + + runWriteCommandAction(model.project, "UtBot util class reformatting", null, { + reformat(model, utUtilsFile, utUtilsClass) + }) + + val utUtilsDocument = PsiDocumentManager + .getInstance(model.project) + .getDocument(utUtilsFile) ?: error("Failed to get a Document for UtUtils file") + + unblockDocument(model.project, utUtilsDocument) + } - val existingUtUtilsDocument = utilClassDirectory - .findFile(utUtilsName) - ?.let { psiDocumentManager.getDocument(it) } + private fun overwriteUtilClass( + existingUtilClass: PsiFile, + utilClassKind: UtilClassKind, + model: GenerateTestsModel + ): PsiFile { + val utilsClassDocument = PsiDocumentManager + .getInstance(model.project) + .getDocument(existingUtilClass) + ?: error("Failed to get Document for UtUtils class PsiFile: ${existingUtilClass.name}") - val utUtilsFile = if (existingUtUtilsDocument != null) { - executeCommand { - existingUtUtilsDocument.setText(utUtilsText) + val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) + + run(EDT_LATER) { + run(WRITE_ACTION) { + unblockDocument(model.project, utilsClassDocument) + executeCommand { + utilsClassDocument.setText(utUtilsText) + } + unblockDocument(model.project, utilsClassDocument) } - unblockDocument(model.project, existingUtUtilsDocument) - psiDocumentManager.getPsiFile(existingUtUtilsDocument) - } else { - val utUtilsFile = PsiFileFactory.getInstance(model.project) + } + return existingUtilClass + } + + /** + * This method creates an util class file and adds it into [utilClassDirectory]. + * + * @param utilClassDirectory directory to put util class into. + * @param language language of util class. + * @param utilClassKind kind of util class required by the test class(es) that we generated. + * @param model [GenerateTestsModel] that contains some useful information for util class generation. + */ + private fun createNewUtilClass( + utilClassDirectory: PsiDirectory, + language: CodegenLanguage, + utilClassKind: UtilClassKind, + model: GenerateTestsModel, + ): PsiFile { + val utUtilsName = language.utilClassFileName + + val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) + + val utUtilsFile = runReadAction { + PsiFileFactory.getInstance(model.project) .createFileFromText( utUtilsName, model.codegenLanguage.fileType, utUtilsText ) - // add the UtUtils class file into the utils directory - runWriteCommandAction(model.project) { - utilClassDirectory.add(utUtilsFile) - } - utUtilsFile } - // there's only one class in the file - val utUtilsClass = (utUtilsFile as PsiClassOwner).classes.first() - - runWriteCommandAction(model.project, "UtBot util class reformatting", null, { - reformat(model, utUtilsFile, utUtilsClass) - }) - - val utUtilsDocument = psiDocumentManager.getDocument(utUtilsFile) - ?: error("Failed to get a Document for UtUtils file") + // add UtUtils class file into the utils directory + runWriteCommandAction(model.project) { + utilClassDirectory.add(utUtilsFile) + } - unblockDocument(model.project, utUtilsDocument) + return utUtilsFile } /** @@ -269,12 +325,40 @@ object CodeGenerationController { } } + private val CodegenLanguage.utilClassFileName: String + get() = "$UT_UTILS_CLASS_NAME${this.extension}" + + /** + * @param testDirectory root test directory where we will put our generated tests. + * @return directory for util class if it exists or null otherwise. + */ + private fun getUtilDirectoryOrNull(testDirectory: PsiDirectory): PsiDirectory? { + val directoryNames = UtilClassKind.utilsPackages + var currentDirectory = testDirectory + for (name in directoryNames) { + val subdirectory = runReadAction { currentDirectory.findSubdirectory(name) } ?: return null + currentDirectory = subdirectory + } + return currentDirectory + } + + /** + * @param testDirectory root test directory where we will put our generated tests. + * @return file of util class if it exists or null otherwise. + */ + private fun CodegenLanguage.getUtilClassOrNull(testDirectory: PsiDirectory): PsiFile? { + return runReadAction { + val utilDirectory = getUtilDirectoryOrNull(testDirectory) + utilDirectory?.findFile(this.utilClassFileName) + } + } + /** * Create all package directories for UtUtils class. * @return the innermost directory - utils from `org.utbot.runtime.utils` */ private fun createUtUtilSubdirectories(baseTestDirectory: PsiDirectory): PsiDirectory { - val directoryNames = UT_UTILS_PACKAGE_NAME.split(PACKAGE_DELIMITER) + val directoryNames = UtilClassKind.utilsPackages var currentDirectory = baseTestDirectory runWriteCommandAction(baseTestDirectory.project) { for (name in directoryNames) { @@ -402,7 +486,7 @@ object CodeGenerationController { model: GenerateTestsModel, reportsCountDown: CountDownLatch, reports: MutableList, - utilMethodListener: UtilMethodListener + utilClassListener: UtilClassListener ) { val classMethods = srcClass.extractClassMethodsIncludingNested(false) val paramNames = DumbService.getInstance(model.project) @@ -430,7 +514,7 @@ object CodeGenerationController { // if we don't want to open _all_ new files with tests in editor one-by-one run(THREAD_POOL) { val codeGenerationResult = codeGenerator.generateAsStringWithTestReport(testSets) - utilMethodListener.onTestClassGenerated(codeGenerationResult) + utilClassListener.onTestClassGenerated(codeGenerationResult) val generatedTestsCode = codeGenerationResult.generatedCode run(EDT_LATER) { run(WRITE_ACTION) { From 54f8a863f1a7ed6d78a4159f8c793d4f8fd7c58a Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 10 Aug 2022 19:46:28 +0300 Subject: [PATCH 10/30] Update util methods collection in code generation taking into account multiple possible util method providers --- .../tree/CgTestClassConstructor.kt | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index 42c9018073..2294177538 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -4,6 +4,7 @@ import org.utbot.common.appendHtmlLine import org.utbot.engine.displayName import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.model.constructor.CgMethodTestSet +import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider 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 @@ -97,8 +98,10 @@ internal class CgTestClassConstructor(val context: CgContext) : } if (currentTestClass == outerMostTestClass) { - val utilMethods = createUtilMethods() - if (utilMethods.isNotEmpty()) { + val utilMethods = collectUtilMethods() + // If utilMethodProvider is TestClassUtilMethodProvider, then util methods should be declared + // in the test class. Otherwise, util methods will be located elsewhere (e.g. another class or library). + if (utilMethodProvider is TestClassUtilMethodProvider && utilMethods.isNotEmpty()) { staticDeclarationRegions += CgStaticsRegion("Util methods", utilMethods) } } @@ -197,8 +200,17 @@ internal class CgTestClassConstructor(val context: CgContext) : }.onFailure { error -> processFailure(testSet, error) } } - // TODO: collect imports of util methods - private fun createUtilMethods(): List { + /** + * This method collects a list of util methods needed by the class. + * By the end of the test method generation [requiredUtilMethods] may not contain all the methods needed. + * That's because some util methods may not be directly used in tests, but they may be used from other util methods. + * We define such method dependencies in [MethodId.dependencies]. + * + * Once all dependencies are collected, they are also added to [requiredUtilMethods]. + * + * @return a list of [CgUtilMethod] representing required util methods (including dependencies). + */ + private fun collectUtilMethods(): List { val utilMethods = mutableListOf() // some util methods depend on the others // using this loop we make sure that all the @@ -208,11 +220,18 @@ internal class CgTestClassConstructor(val context: CgContext) : requiredUtilMethods.remove(method) if (method.name !in existingMethodNames) { utilMethods += CgUtilMethod(method) - importUtilMethodDependencies(method) + // we only need imports from util methods if these util methods are declared in the test class + if (utilMethodProvider is TestClassUtilMethodProvider) { + importUtilMethodDependencies(method) + } existingMethodNames += method.name requiredUtilMethods += method.dependencies() } } + // Collect all util methods back into requiredUtilMethods. + // Now there will also be util methods that weren't present in requiredUtilMethods at first, + // but were needed for the present util methods to work. + requiredUtilMethods += utilMethods.map { method -> method.id } return utilMethods } From 1b8212b3f52fc841902f6a831a0c9743bf6d06b1 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 11 Aug 2022 17:50:19 +0300 Subject: [PATCH 11/30] Simplify util method kinds --- .../utbot/framework/codegen/model/CodeGenerator.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 657a572500..e060cfa17c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -148,14 +148,12 @@ sealed class UtilClassKind( /** * A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework. */ - class RegularUtUtils internal constructor(provider: UtilClassFileMethodProvider) - : UtilClassKind(provider, mockFrameworkUsed = false, priority = 0) + object RegularUtUtils : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = false, priority = 0) /** * A kind of UtUtils class that uses a mock framework. At the moment the framework is Mockito. */ - class UtUtilsWithMockito internal constructor(provider: UtilClassFileMethodProvider) - : UtilClassKind(provider, mockFrameworkUsed = true, priority = 1) + object UtUtilsWithMockito : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = true, priority = 1) override fun compareTo(other: UtilClassKind): Int { return priority.compareTo(other.priority) @@ -181,12 +179,11 @@ sealed class UtilClassKind( */ internal fun fromCgContextOrNull(context: CgContext): UtilClassKind? { if (context.requiredUtilMethods.isEmpty()) return null - val provider = context.utilMethodProvider as? UtilClassFileMethodProvider ?: return null if (!context.mockFrameworkUsed) { - return RegularUtUtils(provider) + return RegularUtUtils } return when (context.mockFramework) { - MockFramework.MOCKITO -> UtUtilsWithMockito(provider) + MockFramework.MOCKITO -> UtUtilsWithMockito // in case we will add any other mock frameworks, newer Kotlin compiler versions // will report a non-exhaustive 'when', so we will not forget to support them here as well } From 51a09f78afbf757062aee97b51ab980449c45bf3 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 11 Aug 2022 17:55:44 +0300 Subject: [PATCH 12/30] Generate tests with and without separate util class when running all combinations tests --- .../utbot/tests/infrastructure/CheckersUtil.kt | 15 ++++++++++++++- .../infrastructure/TestCodeGeneratorPipeline.kt | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CheckersUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CheckersUtil.kt index 874fd112a1..093d9a73bd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CheckersUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CheckersUtil.kt @@ -26,6 +26,7 @@ data class TestFrameworkConfiguration( val codegenLanguage: CodegenLanguage, val forceStaticMocking: ForceStaticMocking, val resetNonFinalFieldsAfterClinit: Boolean = true, + val generateUtilClassFile: Boolean, val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.PASS, val enableTestsTimeout: Boolean = false // our tests should not fail due to timeout ) { @@ -83,7 +84,19 @@ val allTestFrameworkConfigurations: List = run { parametrizedTestSource, codegenLanguage, forceStaticMocking, - resetNonFinalFieldsAfterClinit + resetNonFinalFieldsAfterClinit, + generateUtilClassFile = false + ) + possibleConfiguration += TestFrameworkConfiguration( + testFramework, + mockFramework, + mockStrategy, + staticsMocking, + parametrizedTestSource, + codegenLanguage, + forceStaticMocking, + resetNonFinalFieldsAfterClinit, + generateUtilClassFile = true ) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt index 3d0e569696..358c2c50a2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt @@ -230,6 +230,7 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram val codeGenerator = with(testFrameworkConfiguration) { CodeGenerator( classUnderTest.id, + generateUtilClassFile = generateUtilClassFile, paramNames = params, testFramework = testFramework, staticsMocking = staticsMocking, @@ -290,6 +291,7 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram staticsMocking = StaticsMocking.defaultItem, parametrizedTestSource = ParametrizedTestSource.defaultItem, forceStaticMocking = ForceStaticMocking.defaultItem, + generateUtilClassFile = false ) private const val ERROR_REGION_BEGINNING = "///region Errors" From 227c971f8fc8db220e06cfb884b4cd3725b97aca Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 03:25:09 +0300 Subject: [PATCH 13/30] Check multiple test source roots for util class to avoid util class duplication --- .../generator/CodeGenerationController.kt | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 8df55f3b89..8805d838a5 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -14,6 +14,7 @@ import com.intellij.openapi.command.executeCommand import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.module.Module import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Computable @@ -26,6 +27,7 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiFileFactory +import com.intellij.psi.PsiManager import com.intellij.psi.PsiMethod import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager @@ -77,6 +79,7 @@ import org.utbot.intellij.plugin.ui.TestsReportNotifier import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots import org.utbot.intellij.plugin.util.RunConfigurationHelper import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested import org.utbot.intellij.plugin.util.signature @@ -156,7 +159,11 @@ object CodeGenerationController { run(EDT_LATER) { waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) { - val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(baseTestDirectory) + val project = model.project + val language = model.codegenLanguage + val testModule = model.testModule + + val existingUtilClass = language.getUtilClassOrNull(project, testModule) val utilClassKind = utilClassListener.requiredUtilClassKind ?: return@waitForCountDown // no util class needed @@ -353,6 +360,28 @@ object CodeGenerationController { } } + /** + * @param project project whose classes we generate tests for. + * @param testModule module where the generated tests will be placed. + * @return an existing util class from one of the test source roots + * in the given [testModule] or `null` if no util class was found. + */ + private fun CodegenLanguage.getUtilClassOrNull(project: Project, testModule: Module): PsiFile? { + val psiManager = PsiManager.getInstance(project) + + // all test roots for the given test module + val testRoots = runReadAction { + testModule + .suitableTestSourceRoots(this) + .mapNotNull { psiManager.findDirectory(it) } + } + + // return an util class from one of the test source roots or null if no util class was found + return testRoots + .mapNotNull { testRoot -> getUtilClassOrNull(testRoot) } + .firstOrNull() + } + /** * Create all package directories for UtUtils class. * @return the innermost directory - utils from `org.utbot.runtime.utils` From 233f24df5642b25dc59594d43c86d3c0bd015ea0 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 04:46:33 +0300 Subject: [PATCH 14/30] Support isStatic and isNested flags in CgRegularClass and its builder --- .../kotlin/org/utbot/framework/codegen/model/tree/Builders.kt | 4 +++- .../org/utbot/framework/codegen/model/tree/CgElement.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt index f7399e6770..55be22a41d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt @@ -37,8 +37,10 @@ class CgRegularClassBuilder : CgBuilder { var superclass: ClassId? = null val interfaces: MutableList = mutableListOf() lateinit var body: CgRegularClassBody + var isStatic: Boolean = false + var isNested: Boolean = false - override fun build() = CgRegularClass(id, annotations, superclass, interfaces, body) + override fun build() = CgRegularClass(id, annotations, superclass, interfaces, body, isStatic, isNested) } fun buildRegularClass(init: CgRegularClassBuilder.() -> Unit) = CgRegularClassBuilder().apply(init).build() 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 c139d64d27..f5bee17916 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 @@ -154,7 +154,9 @@ class CgRegularClass( override val annotations: List, override val superclass: ClassId?, override val interfaces: List, - override val body: CgRegularClassBody + override val body: CgRegularClassBody, + override val isStatic: Boolean, + override val isNested: Boolean ) : AbstractCgClass() data class CgTestClass( From 3e6811dc9df93132086f84c86ab7e9d84f0da2a6 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 04:47:29 +0300 Subject: [PATCH 15/30] Drop unused imports and property --- .../codegen/model/constructor/context/CgContext.kt | 7 ------- .../codegen/model/constructor/tree/TestFrameworkManager.kt | 2 -- 2 files changed, 9 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 8e14e23d8e..f71d8fdb09 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -32,7 +32,6 @@ import org.utbot.framework.codegen.model.constructor.builtin.UtilClassFileMethod import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider import org.utbot.framework.codegen.model.constructor.TestClassContext import org.utbot.framework.codegen.model.constructor.TestClassModel -import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId import org.utbot.framework.codegen.model.tree.CgParameterKind import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId @@ -331,12 +330,6 @@ internal interface CgContextOwner { val utilsClassId: ClassId get() = utilMethodProvider.utilClassId - /** - * Check whether a method is an util method of the current class - */ - val MethodId.isUtil: Boolean - get() = this in outerMostTestClass.possibleUtilMethodIds - /** * Checks is it our util reflection field getter method. * When this method is used with type cast in Kotlin, this type cast have to be safety 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 9a36320cfc..459734f0a9 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 @@ -4,8 +4,6 @@ import org.utbot.framework.codegen.Junit4 import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.TestNg import org.utbot.framework.codegen.model.constructor.TestClassContext -import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId -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.context.CgContext import org.utbot.framework.codegen.model.constructor.context.CgContextOwner From ac193b98e0dc79ab52211e02dc2f282cfcc866c7 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 04:50:02 +0300 Subject: [PATCH 16/30] Fix wrong property name --- .../codegen/model/constructor/tree/CgTestClassConstructor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index 2294177538..c83942a177 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -49,7 +49,7 @@ internal class CgTestClassConstructor(val context: CgContext) : */ fun construct(testClassModel: TestClassModel): CgTestClassFile { return buildTestClassFile { - this.testClass = withTestClassScope { constructTestClass(testClassModel) } + this.declaredClass = withTestClassScope { constructTestClass(testClassModel) } imports += context.collectedImports testsGenerationReport = this@CgTestClassConstructor.testsGenerationReport } From 29b2d2952021d2b39bb2e92d02972157b3fd1f19 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 04:52:15 +0300 Subject: [PATCH 17/30] Use outermost test class for test class util method provider and renderer context --- .../framework/codegen/model/constructor/context/CgContext.kt | 2 +- .../utbot/framework/codegen/model/visitor/CgRendererContext.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index f71d8fdb09..3bc74f7163 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -478,7 +478,7 @@ internal data class CgContext( get() = if (generateUtilClassFile) { UtilClassFileMethodProvider } else { - TestClassUtilMethodProvider(currentTestClass) + TestClassUtilMethodProvider(outerMostTestClass) } override lateinit var currentTestClass: ClassId diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt index 54f0428520..92963984cb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt @@ -33,7 +33,7 @@ internal class CgRendererContext( importedClasses = context.importedClasses, importedStaticMethods = context.importedStaticMethods, classPackageName = context.testClassPackageName, - generatedClass = context.currentTestClass, + generatedClass = context.outerMostTestClass, utilMethodProvider = context.utilMethodProvider, codegenLanguage = context.codegenLanguage, mockFrameworkUsed = context.mockFrameworkUsed, From 721e983d26f04e69cf16425935d3dd1a8323ee63 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 05:03:52 +0300 Subject: [PATCH 18/30] Do render nested classes in Java and Kotlin --- .../org/utbot/framework/codegen/model/tree/CgElement.kt | 6 +----- .../utbot/framework/codegen/model/visitor/CgJavaRenderer.kt | 2 +- .../framework/codegen/model/visitor/CgKotlinRenderer.kt | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) 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 f5bee17916..696e93de8e 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 @@ -187,11 +187,7 @@ data class CgTestClassBody( val testMethodRegions: List, val staticDeclarationRegions: List, val nestedClassRegions: List> -) : AbstractCgClassBody() { - - val regions: List> - get() = testMethodRegions -} +) : AbstractCgClassBody() /** * A class representing the IntelliJ IDEA's regions. 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 3f8efddc71..75728e0705 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 @@ -96,7 +96,7 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C override fun visit(element: CgTestClassBody) { // render regions for test methods and utils - val allRegions = element.testMethodRegions + element.staticDeclarationRegions + val allRegions = element.testMethodRegions + element.nestedClassRegions + element.staticDeclarationRegions for ((i, region) in allRegions.withIndex()) { if (i != 0) println() 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 b378816b29..9b5d8a04e5 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 @@ -117,7 +117,7 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = override fun visit(element: CgTestClassBody) { // render regions for test methods - for ((i, region) in (element.testMethodRegions).withIndex()) { + for ((i, region) in (element.testMethodRegions + element.nestedClassRegions).withIndex()) { if (i != 0) println() region.accept(this) From c0d48b349ee26cc4cd57e605860b24d38c51eb9b Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 16:13:41 +0300 Subject: [PATCH 19/30] Fix obtaining exception types of util methods --- .../constructor/builtin/UtilMethodBuiltins.kt | 27 -------------- .../tree/CgCallableAccessManager.kt | 37 +++++++++++++++++-- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index a3545c664e..6147639aa7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -147,33 +147,6 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { returnType = intClassId, arguments = arrayOf(objectClassId) ) - - //WARN: if you make changes in the following sets of exceptions, - //don't forget to change them in hardcoded [UtilMethods] as well - internal fun findExceptionTypesOf(methodId: MethodId): Set { - if (methodId !in utilMethodIds) return emptySet() - - with(this) { - return when (methodId) { - getEnumConstantByNameMethodId -> setOf(java.lang.IllegalAccessException::class.id) - getStaticFieldValueMethodId, - getFieldValueMethodId, - setStaticFieldMethodId, - setFieldMethodId -> setOf(java.lang.IllegalAccessException::class.id, java.lang.NoSuchFieldException::class.id) - createInstanceMethodId -> setOf(Exception::class.id) - getUnsafeInstanceMethodId -> setOf(java.lang.ClassNotFoundException::class.id, java.lang.NoSuchFieldException::class.id, java.lang.IllegalAccessException::class.id) - createArrayMethodId -> setOf(java.lang.ClassNotFoundException::class.id) - deepEqualsMethodId, - arraysDeepEqualsMethodId, - iterablesDeepEqualsMethodId, - streamsDeepEqualsMethodId, - mapsDeepEqualsMethodId, - hasCustomEqualsMethodId, - getArrayLengthMethodId -> emptySet() - else -> error("Unknown util method $this") - } - } - } } /** 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 877073080c..b70fd7af1d 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 @@ -36,6 +36,7 @@ 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 @@ -111,9 +112,8 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA //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 (isUtil(methodId)) { - utilMethodProvider - .findExceptionTypesOf(methodId) + if (methodId is BuiltinMethodId) { + findExceptionTypesOf(methodId) .forEach { addExceptionIfNeeded(it) } return } @@ -364,4 +364,35 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA return argumentsArrayVariable } + + //WARN: if you make changes in the following sets of exceptions, + //don't forget to change them in hardcoded [UtilMethods] as well + private fun findExceptionTypesOf(methodId: MethodId): Set { + // TODO: at the moment we treat BuiltinMethodIds that are not util method ids + // as if they have no exceptions. This should be fixed by storing exception types in BuiltinMethodId + // or allowing us to access actual java.lang.Class for classes from mockito and other libraries + // (this could be possibly solved by using user project's class loaders in UtContext) + if (methodId !in utilMethodProvider.utilMethodIds) return emptySet() + + with(utilMethodProvider) { + return when (methodId) { + getEnumConstantByNameMethodId -> setOf(java.lang.IllegalAccessException::class.id) + getStaticFieldValueMethodId, + getFieldValueMethodId, + setStaticFieldMethodId, + setFieldMethodId -> setOf(java.lang.IllegalAccessException::class.id, java.lang.NoSuchFieldException::class.id) + createInstanceMethodId -> setOf(Exception::class.id) + getUnsafeInstanceMethodId -> setOf(java.lang.ClassNotFoundException::class.id, java.lang.NoSuchFieldException::class.id, java.lang.IllegalAccessException::class.id) + createArrayMethodId -> setOf(java.lang.ClassNotFoundException::class.id) + deepEqualsMethodId, + arraysDeepEqualsMethodId, + iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, + mapsDeepEqualsMethodId, + hasCustomEqualsMethodId, + getArrayLengthMethodId -> emptySet() + else -> error("Unknown util method $this") + } + } + } } \ No newline at end of file From 39e77beb96fa522c116ab04c1b9674b72d2942de Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 16:52:30 +0300 Subject: [PATCH 20/30] Remove utbot-codegen-utils library, because now we will work with a separate utils class instead --- settings.gradle | 1 - utbot-codegen-utils/build.gradle | 16 - .../org/utbot/runtime/utils/MockUtils.java | 31 -- .../java/org/utbot/runtime/utils/UtUtils.java | 368 ------------------ .../framework/plugin/api/util/CodegenUtil.kt | 8 - .../framework/codegen/model/CodeGenerator.kt | 7 +- .../constructor/builtin/UtilMethodBuiltins.kt | 7 - .../model/constructor/context/CgContext.kt | 9 +- .../codegen/model/visitor/UtilMethods.kt | 2 +- .../plugin/ui/utils/LibraryMatcher.kt | 10 - 10 files changed, 5 insertions(+), 454 deletions(-) delete mode 100644 utbot-codegen-utils/build.gradle delete mode 100644 utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java delete mode 100644 utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java diff --git a/settings.gradle b/settings.gradle index b9b97ed841..c6ea552e41 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,7 +10,6 @@ pluginManagement { rootProject.name = 'utbot' -include 'utbot-codegen-utils' include 'utbot-core' include 'utbot-framework' include 'utbot-framework-api' diff --git a/utbot-codegen-utils/build.gradle b/utbot-codegen-utils/build.gradle deleted file mode 100644 index 78bb2fa992..0000000000 --- a/utbot-codegen-utils/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "6.1.0" -} - -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -shadowJar { - configurations = [project.configurations.compileClasspath] - archiveVersion.set(codegen_utils_version) - archiveClassifier.set('') - minimize() -} - -dependencies { - compileOnly group: 'org.mockito', name: 'mockito-core', version: mockito_version -} diff --git a/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java deleted file mode 100644 index 7a07224593..0000000000 --- a/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/MockUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.utbot.runtime.utils; - -import java.lang.reflect.Method; - -final class MockUtils { - private MockUtils() {} - - // TODO: for now we have only Mockito but it can be changed in the future - - /** - * This method tries to return the following value: `org.mockito.Mockito.mockingDetails(obj).isMock()`, - * but via reflection, because we do not know if Mockito library is on the classpath. - * @param obj an object that we check for being a mock object. - * @return true if we were able to access `isMock()` method, and it returned true, false otherwise - */ - public static boolean isMock(Object obj) { - try { - Class mockitoClass = Class.forName("org.mockito.Mockito"); - Method mockingDetailsMethod = mockitoClass.getDeclaredMethod("mockingDetails", Object.class); - - Object mockingDetails = mockingDetailsMethod.invoke(null, obj); - - Class mockingDetailsClass = Class.forName("org.mockito.MockingDetails"); - Method isMockMethod = mockingDetailsClass.getDeclaredMethod("isMock"); - - return (boolean) isMockMethod.invoke(mockingDetails); - } catch (Exception e) { - return false; - } - } -} diff --git a/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java b/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java deleted file mode 100644 index 011b1ec1aa..0000000000 --- a/utbot-codegen-utils/src/main/java/org/utbot/runtime/utils/UtUtils.java +++ /dev/null @@ -1,368 +0,0 @@ -package org.utbot.runtime.utils; - -import java.lang.reflect.InvocationTargetException; -import java.util.HashSet; -import java.util.Objects; - -public final class UtUtils { - private UtUtils() {} - - public static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { - java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); - for (java.lang.reflect.Field field : fields) { - String fieldName = field.getName(); - if (field.isEnumConstant() && fieldName.equals(name)) { - field.setAccessible(true); - - return field.get(null); - } - } - - return null; - } - - public static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { - java.lang.reflect.Field field; - Class originClass = clazz; - do { - try { - field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - return field.get(null); - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } while (clazz != null); - - throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + originClass); - } - - public static Object getFieldValue(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException { - Class clazz = obj.getClass(); - java.lang.reflect.Field field; - do { - try { - field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - return field.get(obj); - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } while (clazz != null); - - throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + obj.getClass()); - } - - public static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { - java.lang.reflect.Field field; - - do { - try { - field = clazz.getDeclaredField(fieldName); - } catch (Exception e) { - clazz = clazz.getSuperclass(); - field = null; - } - } while (field == null); - - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - field.setAccessible(true); - field.set(null, fieldValue); - } - - public static void setField(Object object, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { - Class clazz = object.getClass(); - java.lang.reflect.Field field; - - do { - try { - field = clazz.getDeclaredField(fieldName); - } catch (Exception e) { - clazz = clazz.getSuperclass(); - field = null; - } - } while (field == null); - - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - field.setAccessible(true); - field.set(object, fieldValue); - } - - public static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { - Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); - - for (int i = 0; i < values.length; i++) { - java.lang.reflect.Array.set(array, i, values[i]); - } - - return (Object[]) array; - } - - public 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); - } - - - public 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); - } - - static class FieldsPair { - final Object o1; - final Object o2; - - public FieldsPair(Object o1, Object o2) { - this.o1 = o1; - this.o2 = o2; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FieldsPair that = (FieldsPair) o; - return Objects.equals(o1, that.o1) && Objects.equals(o2, that.o2); - } - - @Override - public int hashCode() { - return Objects.hash(o1, o2); - } - } - - public static boolean deepEquals(Object o1, Object o2) { - return deepEquals(o1, o2, new java.util.HashSet<>()); - } - - private static boolean deepEquals(Object o1, Object o2, java.util.Set visited) { - visited.add(new FieldsPair(o1, o2)); - - if (o1 == o2) { - return true; - } - - if (o1 == null || o2 == null) { - return false; - } - - if (o1 instanceof Iterable) { - if (!(o2 instanceof Iterable)) { - return false; - } - - return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited); - } - - if (o2 instanceof Iterable) { - return false; - } - - if (o1 instanceof java.util.stream.Stream) { - if (!(o2 instanceof java.util.stream.Stream)) { - return false; - } - - return streamsDeepEquals((java.util.stream.Stream) o1, (java.util.stream.Stream) o2, visited); - } - - if (o2 instanceof java.util.stream.Stream) { - return false; - } - - if (o1 instanceof java.util.Map) { - if (!(o2 instanceof java.util.Map)) { - return false; - } - - return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); - } - - if (o2 instanceof java.util.Map) { - return false; - } - - Class firstClass = o1.getClass(); - if (firstClass.isArray()) { - if (!o2.getClass().isArray()) { - return false; - } - - // Primitive arrays should not appear here - return arraysDeepEquals(o1, o2, visited); - } - - // common classes - - // Check if class has custom equals method (including wrappers and strings) - // It is very important to check it here but not earlier because iterables and maps also have custom equals - // based on elements equals. - if (hasCustomEquals(firstClass) && !MockUtils.isMock(o1)) { - return o1.equals(o2); - } - - // common classes without custom equals, use comparison by fields - final java.util.List fields = new java.util.ArrayList<>(); - while (firstClass != Object.class) { - fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); - // Interface should not appear here - firstClass = firstClass.getSuperclass(); - } - - for (java.lang.reflect.Field field : fields) { - field.setAccessible(true); - try { - final Object field1 = field.get(o1); - final Object field2 = field.get(o2); - if (!visited.contains(new FieldsPair(field1, field2)) && !deepEquals(field1, field2, visited)) { - return false; - } - } catch (IllegalArgumentException e) { - return false; - } catch (IllegalAccessException e) { - // should never occur because field was set accessible - return false; - } - } - - return true; - } - - public static boolean arraysDeepEquals(Object arr1, Object arr2) { - return arraysDeepEquals(arr1, arr2, new HashSet<>()); - } - - private static 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++) { - Object item1 = java.lang.reflect.Array.get(arr1, i); - Object item2 = java.lang.reflect.Array.get(arr2, i); - if (!deepEquals(item1, item2, visited)) { - return false; - } - } - - return true; - } - - public static boolean iterablesDeepEquals(Iterable i1, Iterable i2) { - return iterablesDeepEquals(i1, i2, new HashSet<>()); - } - - private static 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; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - - public static boolean streamsDeepEquals( - java.util.stream.Stream s1, - java.util.stream.Stream s2 - ) { - return streamsDeepEquals(s1, s2, new HashSet<>()); - } - - private static boolean streamsDeepEquals( - java.util.stream.Stream s1, - java.util.stream.Stream s2, - java.util.Set visited - ) { - final java.util.Iterator firstIterator = s1.iterator(); - final java.util.Iterator secondIterator = s2.iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - - public static boolean mapsDeepEquals( - java.util.Map m1, - java.util.Map m2 - ) { - return mapsDeepEquals(m1, m2, new HashSet<>()); - } - - private static 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 java.util.Map.Entry firstEntry = firstIterator.next(); - final java.util.Map.Entry secondEntry = secondIterator.next(); - - if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { - return false; - } - - if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - - public static boolean hasCustomEquals(Class clazz) { - while (!Object.class.equals(clazz)) { - try { - clazz.getDeclaredMethod("equals", Object.class); - return true; - } catch (Exception e) { - // Interface should not appear here - clazz = clazz.getSuperclass(); - } - } - - return false; - } - - public static int getArrayLength(Object arr) { - return java.lang.reflect.Array.getLength(arr); - } -} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt index fedb9855fd..8e436b1fd2 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt @@ -4,11 +4,3 @@ data class Patterns( val moduleLibraryPatterns: List, val libraryPatterns: List, ) - -val codegenUtilsLibraryPatterns: Patterns - get() = Patterns( - moduleLibraryPatterns = listOf(CODEGEN_UTILS_JAR_PATTERN), - libraryPatterns = emptyList() - ) - -private val CODEGEN_UTILS_JAR_PATTERN = Regex("utbot-codegen-utils-[0-9](\\.[0-9]+){2}") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index e060cfa17c..da6a942bbb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -119,7 +119,7 @@ class CodeGenerator( */ data class CodeGenerationResult( val generatedCode: String, - // null if no util class needed, e.g. when we are using a library or generating utils directly into test class + // null if no util class needed, e.g. when we are generating utils directly into test class val utilClassKind: UtilClassKind?, val testsGenerationReport: TestsGenerationReport, val mockFrameworkUsed: Boolean = false @@ -173,9 +173,8 @@ sealed class UtilClassKind( companion object { /** * Check if an util class is required, and if so, what kind. - * @return null if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider], - * because it means that util methods will be taken from some other provider (e.g. utbot-codegen-utils library) - * or they will be generated directly into the test class (in this case provider will be [TestClassUtilMethodProvider]) + * @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider], + * because it means that util methods will be taken from some other provider (e.g. [TestClassUtilMethodProvider]). */ internal fun fromCgContextOrNull(context: CgContext): UtilClassKind? { if (context.requiredUtilMethods.isEmpty()) return null diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index 6147639aa7..9b1ab8b20a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -149,13 +149,6 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { ) } -/** - * This provider represents an util class file that is located in the library. - * Source code of the library can be found in the module `utbot-codegen-utils`. - */ -@Suppress("unused") -internal object LibraryUtilMethodProvider : UtilMethodProvider(utUtilsClassId) - /** * This provider represents an util class file that is generated and put into the user's test module. * The generated class is UtUtils (its id is defined at [utUtilsClassId]). diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 3bc74f7163..c9b0dbd76e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -26,7 +26,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf import org.utbot.framework.codegen.model.constructor.CgMethodTestSet -import org.utbot.framework.codegen.model.constructor.builtin.LibraryUtilMethodProvider import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider import org.utbot.framework.codegen.model.constructor.builtin.UtilClassFileMethodProvider import org.utbot.framework.codegen.model.constructor.builtin.UtilMethodProvider @@ -324,8 +323,7 @@ internal interface CgContextOwner { /** * [ClassId] of a class that contains util methods. - * For example, it can be the current test class, or it can be a `UtUtils` class from module `utbot-codegen-utils`. - * The latter is used when our codegen utils library is included in the user's project dependencies. + * For example, it can be the current test class, or it can be a generated separate `UtUtils` class. */ val utilsClassId: ClassId get() = utilMethodProvider.utilClassId @@ -463,16 +461,11 @@ internal data class CgContext( ) } - // TODO: note that when nested test classes feature is implemented, - // we will have to use the outermost test class here instead of currentTestClass to construct the TestClassUtilMethodProvider /** * Determine where the util methods will come from. * If we don't want to use a separately generated util class, * util methods will be generated directly in the test class (see [TestClassUtilMethodProvider]). * Otherwise, an util class will be generated separately and we will use util methods from it (see [UtilClassFileMethodProvider]). - * - * We may also use utils from a library in the future (see [LibraryUtilMethodProvider]), - * but it is not clear at this point. Perhaps, we will remove the library completely. */ override val utilMethodProvider: UtilMethodProvider get() = if (generateUtilClassFile) { 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 5294a17f2f..4634590e6e 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 @@ -833,7 +833,7 @@ private fun getArrayLength(visibility: Visibility, language: CodegenLanguage) = } internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { - // if util methods come from codegen utils library and not from the test class, + // if util methods come from a separate UtUtils class and not from the test class, // then we don't need to import any other methods, hence we return from method val utilMethodProvider = utilMethodProvider as? TestClassUtilMethodProvider ?: return for (classId in utilMethodProvider.regularImportsByUtilMethod(id, codegenLanguage)) { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt index 490cd67d37..49308b49bd 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.roots.LibraryOrderEntry import org.utbot.framework.plugin.api.util.Patterns -import org.utbot.framework.plugin.api.util.codegenUtilsLibraryPatterns fun findFrameworkLibrary( project: Project, @@ -25,15 +24,6 @@ fun findFrameworkLibrary( scope: LibrarySearchScope = LibrarySearchScope.Module, ): LibraryOrderEntry? = findMatchingLibrary(project, testModule, mockFramework.patterns(), scope) -@Suppress("unused") -fun findCodegenUtilsLibrary( - project: Project, - testModule: Module, - scope: LibrarySearchScope = LibrarySearchScope.Module, -): LibraryOrderEntry? { - return findMatchingLibrary(project, testModule, codegenUtilsLibraryPatterns, scope) -} - private fun findMatchingLibrary( project: Project, testModule: Module, From 61c2b42cf5c37f76fffc0acd1f3289a379316b81 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 12 Aug 2022 20:35:37 +0300 Subject: [PATCH 21/30] Fix code generator pipeline for cases when util class is generated separately --- .../framework/codegen/model/CodeGenerator.kt | 8 +-- .../infrastructure/CompilationAndRunUtils.kt | 13 +++-- .../TestCodeGeneratorPipeline.kt | 50 +++++++++++++++---- .../generator/CodeGenerationController.kt | 6 +-- 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index da6a942bbb..f21b9902e8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -65,7 +65,7 @@ class CodeGenerator( fun generateAsStringWithTestReport( testSets: Collection, testClassCustomName: String? = null, - ): CodeGenerationResult { + ): CodeGeneratorResult { val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList() return generateAsStringWithTestReport(cgTestSets, testClassCustomName) } @@ -73,11 +73,11 @@ class CodeGenerator( private fun generateAsStringWithTestReport( cgTestSets: List, testClassCustomName: String? = null, - ): CodeGenerationResult = withCustomContext(testClassCustomName) { + ): CodeGeneratorResult = withCustomContext(testClassCustomName) { context.withTestClassFileScope { val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets) val testClassFile = CgTestClassConstructor(context).construct(testClassModel) - CodeGenerationResult( + CodeGeneratorResult( generatedCode = renderClassFile(testClassFile), utilClassKind = UtilClassKind.fromCgContextOrNull(context), testsGenerationReport = testClassFile.testsGenerationReport, @@ -117,7 +117,7 @@ class CodeGenerator( * @property testsGenerationReport some info about test generation process * @property mockFrameworkUsed flag indicating whether any mock objects have been created during code generation ot not */ -data class CodeGenerationResult( +data class CodeGeneratorResult( val generatedCode: String, // null if no util class needed, e.g. when we are generating utils directly into test class val utilClassKind: UtilClassKind?, diff --git a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CompilationAndRunUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CompilationAndRunUtils.kt index b476aa4dcb..ddf523586a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CompilationAndRunUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CompilationAndRunUtils.kt @@ -3,7 +3,6 @@ package org.utbot.tests.infrastructure import org.utbot.framework.plugin.api.CodegenLanguage import java.io.File import java.nio.file.Path -import mu.KotlinLogging import org.utbot.common.FileUtil import org.utbot.engine.logger import org.utbot.framework.codegen.Junit5 @@ -15,6 +14,13 @@ data class ClassUnderTest( val generatedTestFile: File ) +fun writeFile(fileContents: String, targetFile: File): File { + val targetDir = targetFile.parentFile + targetDir.mkdirs() + targetFile.writeText(fileContents) + return targetFile +} + fun writeTest( testContents: String, testClassName: String, @@ -27,13 +33,10 @@ fun writeTest( File(buildDirectory.toFile(), "${testClassName.substringAfterLast(".")}${generatedLanguage.extension}") ) - val targetDir = classUnderTest.generatedTestFile.parentFile - targetDir.mkdirs() logger.info { "File size for ${classUnderTest.testClassSimpleName}: ${FileUtil.byteCountToDisplaySize(testContents.length.toLong())}" } - classUnderTest.generatedTestFile.writeText(testContents) - return classUnderTest.generatedTestFile + return writeFile(testContents, classUnderTest.generatedTestFile) } fun compileTests( diff --git a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt index 358c2c50a2..983dee4675 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestCodeGeneratorPipeline.kt @@ -10,6 +10,9 @@ import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.StaticsMocking import org.utbot.framework.codegen.TestFramework import org.utbot.framework.codegen.model.CodeGenerator +import org.utbot.framework.codegen.model.CodeGeneratorResult +import org.utbot.framework.codegen.model.UtilClassKind +import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MockFramework @@ -19,6 +22,8 @@ import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.description import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.withUtContext +import java.io.File +import java.nio.file.Path import kotlin.reflect.KClass private val logger = KotlinLogging.logger {} @@ -75,7 +80,8 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram val codegenLanguage = testFrameworkConfiguration.codegenLanguage val parametrizedTestSource = testFrameworkConfiguration.parametrizedTestSource - val testClass = callToCodeGenerator(testSets, classUnderTest) + val codeGenerationResult = callToCodeGenerator(testSets, classUnderTest) + val testClass = codeGenerationResult.generatedCode // actual number of the tests in the generated testClass val generatedMethodsCount = testClass @@ -149,17 +155,33 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram val testClassName = classPipeline.retrieveTestClassName("BrokenGeneratedTest") val generatedTestFile = writeTest(testClass, testClassName, buildDirectory, codegenLanguage) + val generatedUtilClassFile = codeGenerationResult.utilClassKind?.writeUtilClassToFile(buildDirectory, codegenLanguage) logger.error("Broken test has been written to the file: [$generatedTestFile]") + if (generatedUtilClassFile != null) { + logger.error("Util class for the broken test has been written to the file: [$generatedUtilClassFile]") + } logger.error("Failed configuration: $testFrameworkConfiguration") throw it } - classPipeline.stageContext = copy(data = testClass, stages = stages + information.completeStage()) + classPipeline.stageContext = copy(data = codeGenerationResult, stages = stages + information.completeStage()) } } + private fun UtilClassKind.writeUtilClassToFile(buildDirectory: Path, language: CodegenLanguage): File { + val utilClassFile = File(buildDirectory.toFile(), "$UT_UTILS_CLASS_NAME${language.extension}") + val utilClassText = getUtilClassText(language) + return writeFile(utilClassText, utilClassFile) + } + + private data class GeneratedTestClassInfo( + val testClassName: String, + val generatedTestFile: File, + val generatedUtilClassFile: File? + ) + @Suppress("UNCHECKED_CAST") private fun processCompilationStages(classesPipelines: List) { val information = StageExecutionInformation(Compilation) @@ -169,24 +191,34 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram val codegenLanguage = testFrameworkConfiguration.codegenLanguage val testClassesNamesToTestGeneratedTests = classesPipelines.map { classPipeline -> - val testClass = classPipeline.stageContext.data as String + val codeGeneratorResult = classPipeline.stageContext.data as CodeGeneratorResult//String + val testClass = codeGeneratorResult.generatedCode + val testClassName = classPipeline.retrieveTestClassName("GeneratedTest") val generatedTestFile = writeTest(testClass, testClassName, buildDirectory, codegenLanguage) + val generatedUtilClassFile = codeGeneratorResult.utilClassKind?.writeUtilClassToFile(buildDirectory, codegenLanguage) logger.info("Test has been written to the file: [$generatedTestFile]") + if (generatedUtilClassFile != null) { + logger.info("Util class for the test has been written to the file: [$generatedUtilClassFile]") + } - testClassName to generatedTestFile + GeneratedTestClassInfo(testClassName, generatedTestFile, generatedUtilClassFile) } + val sourceFiles = mutableListOf().apply { + this += testClassesNamesToTestGeneratedTests.map { it.generatedTestFile.absolutePath } + this += testClassesNamesToTestGeneratedTests.mapNotNull { it.generatedUtilClassFile?.absolutePath } + } compileTests( "$buildDirectory", - testClassesNamesToTestGeneratedTests.map { it.second.absolutePath }, + sourceFiles, codegenLanguage ) - testClassesNamesToTestGeneratedTests.zip(classesPipelines) { testClassNameToTest, classPipeline -> + testClassesNamesToTestGeneratedTests.zip(classesPipelines) { generatedTestClassInfo, classPipeline -> classPipeline.stageContext = classPipeline.stageContext.copy( - data = CompilationResult("$buildDirectory", testClassNameToTest.first), + data = CompilationResult("$buildDirectory", generatedTestClassInfo.testClassName), stages = classPipeline.stageContext.stages + information.completeStage() ) } @@ -224,7 +256,7 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram private fun callToCodeGenerator( testSets: List, classUnderTest: KClass<*> - ): String { + ): CodeGeneratorResult { val params = mutableMapOf>() val codeGenerator = with(testFrameworkConfiguration) { @@ -244,7 +276,7 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram } val testClassCustomName = "${classUnderTest.java.simpleName}GeneratedTest" - return codeGenerator.generateAsString(testSets, testClassCustomName) + return codeGenerator.generateAsStringWithTestReport(testSets, testClassCustomName) } private fun checkPipelinesResults(classesPipelines: List) { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 8805d838a5..6113d825ba 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -58,7 +58,7 @@ import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.CodeGenerator -import org.utbot.framework.codegen.model.CodeGenerationResult +import org.utbot.framework.codegen.model.CodeGeneratorResult import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport @@ -98,7 +98,7 @@ object CodeGenerationController { var requiredUtilClassKind: UtilClassKind? = null var mockFrameworkUsed: Boolean = false - fun onTestClassGenerated(result: CodeGenerationResult) { + fun onTestClassGenerated(result: CodeGeneratorResult) { requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind) mockFrameworkUsed = maxOf(mockFrameworkUsed, result.mockFrameworkUsed) } @@ -623,7 +623,7 @@ object CodeGenerationController { testClass: PsiClass, testSets: List, model: GenerateTestsModel, - testsCodeWithTestReport: CodeGenerationResult, + testsCodeWithTestReport: CodeGeneratorResult, ) { val project = model.project val generatedTestsCode = testsCodeWithTestReport.generatedCode From abda14dd1529a8ea87eecfa00f6de9c47933583f Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 17 Aug 2022 17:11:50 +0300 Subject: [PATCH 22/30] TODO: uncommit, there are some unfinished todo's --- .../tree/CgUtilClassConstructor.kt | 3 + .../generator/CodeGenerationController.kt | 85 ++++++++++++++----- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt index a34cd4aa15..de5f48b4d5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -4,6 +4,7 @@ import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.constructor.builtin.utUtilsClassId import org.utbot.framework.codegen.model.tree.CgRegularClassFile +import org.utbot.framework.codegen.model.tree.CgSingleLineComment import org.utbot.framework.codegen.model.tree.CgUtilMethod import org.utbot.framework.codegen.model.tree.buildRegularClass import org.utbot.framework.codegen.model.tree.buildRegularClassBody @@ -22,6 +23,8 @@ internal object CgUtilClassConstructor { declaredClass = buildRegularClass { id = utUtilsClassId body = buildRegularClassBody { + // TODO: get actual UTBot version and use it instead of the hardcoded one + content += CgSingleLineComment("UTBot version: 1.0-SNAPSHOT") content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 6113d825ba..d977b3f261 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -22,6 +22,7 @@ import com.intellij.openapi.wm.ToolWindowManager import com.intellij.psi.JavaDirectoryService import com.intellij.psi.PsiClass import com.intellij.psi.PsiClassOwner +import com.intellij.psi.PsiComment import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement @@ -49,6 +50,7 @@ import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.scripting.resolve.classId +import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType import org.utbot.common.HTML_LINE_SEPARATOR import org.utbot.common.PathUtil.toHtmlLinkTag import org.utbot.common.allNestedClasses @@ -159,33 +161,18 @@ object CodeGenerationController { run(EDT_LATER) { waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) { - val project = model.project - val language = model.codegenLanguage - val testModule = model.testModule - - val existingUtilClass = language.getUtilClassOrNull(project, testModule) - val utilClassKind = utilClassListener.requiredUtilClassKind ?: return@waitForCountDown // no util class needed - val utilClassExists = existingUtilClass != null - val mockFrameworkNotUsed = !utilClassListener.mockFrameworkUsed - - if (utilClassExists && mockFrameworkNotUsed) { - // If util class already exists and mock framework is not used, - // then existing util class is enough, and we don't need to generate a new one. - // That's because both regular and mock versions of util class can work - // with tests that do not use mocks, so we do not have to worry about - // version of util class that we have at the moment. - return@waitForCountDown + val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(model.project, model.testModule) + if (shouldCreateOrUpdateUtilClass(existingUtilClass, utilClassListener)) { + createOrUpdateUtilClass( + testDirectory = baseTestDirectory, + utilClassKind = utilClassKind, + existingUtilClass = existingUtilClass, + model = model + ) } - - createOrUpdateUtilClass( - testDirectory = baseTestDirectory, - utilClassKind = utilClassKind, - existingUtilClass = existingUtilClass, - model = model - ) } } @@ -216,6 +203,39 @@ object CodeGenerationController { } } + private fun shouldCreateOrUpdateUtilClass(existingUtilClass: PsiFile?, utilClassListener: UtilClassListener): Boolean { + val existingUtilClassVersion = existingUtilClass?.utilClassVersionOrNull + // TODO: here should be the current version of UTBot + val newUtilClassVersion = "1.0-SNAPSHOT" + val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion + + val mockFrameworkNotUsed = !utilClassListener.mockFrameworkUsed + + val utilClassExists = existingUtilClass != null + + if (!utilClassExists) { + // If no util class exists, then we should create a new one. + return true + } + + if (versionIsUpdated) { + // If an existing util class is out of date, + // then we must overwrite it with a newer version. + return true + } + + if (mockFrameworkNotUsed) { + // If util class already exists and mock framework is not used, + // then existing util class is enough, and we don't need to generate a new one. + // That's because both regular and mock versions of util class can work + // with tests that do not use mocks, so we do not have to worry about + // version of util class that we have at the moment. + return false + } + + return true + } + /** * If [existingUtilClass] is null (no util class exists), then we create package directories for util class, * create util class itself, and put it into the corresponding directory. @@ -320,6 +340,25 @@ object CodeGenerationController { return utUtilsFile } + /** + * Util class must have a comment that specifies the version of UTBot it was generated with. + * This property represents the version specified by this comment if it exists. Otherwise, the property is `null`. + */ + private val PsiFile.utilClassVersionOrNull: String? + get() = runReadAction { + childrenOfType() + .map { comment -> comment.text } + .firstOrNull { text -> UTBOT_VERSION_PREFIX in text } + ?.substringAfterLast(UTBOT_VERSION_PREFIX) + ?.trim() + } + + /** + * Util class must have a comment that specifies the version of UTBot it was generated with. + * This prefix is the start of this comment. The version of UTBot goes after it in the comment. + */ + private const val UTBOT_VERSION_PREFIX = "UTBot version:" + /** * @param srcClass class under test * @return name of the package of a given [srcClass]. From 65b34da11748c68dcfdc30e85fd66240d007bce4 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 18 Aug 2022 20:44:32 +0300 Subject: [PATCH 23/30] Obtain version comment from utils class, not file --- .../intellij/plugin/generator/CodeGenerationController.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index d977b3f261..d1045b8249 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -346,7 +346,12 @@ object CodeGenerationController { */ private val PsiFile.utilClassVersionOrNull: String? get() = runReadAction { - childrenOfType() + val utilClass = (this as? PsiClassOwner) + ?.classes + ?.firstOrNull() + ?: return@runReadAction null + + utilClass.childrenOfType() .map { comment -> comment.text } .firstOrNull { text -> UTBOT_VERSION_PREFIX in text } ?.substringAfterLast(UTBOT_VERSION_PREFIX) From 286e54c693d1da506fdfb5b55fce8d9b058cabeb Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Mon, 22 Aug 2022 15:49:54 +0300 Subject: [PATCH 24/30] =?UTF-8?q?Remove=20unnec=D0=B5ssary=20'open'=20modi?= =?UTF-8?q?fier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constructor/builtin/UtilMethodBuiltins.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index 9b1ab8b20a..a54785c81c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -41,7 +41,7 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { getArrayLengthMethodId ) - open val getUnsafeInstanceMethodId: MethodId + val getUnsafeInstanceMethodId: MethodId get() = utilClassId.utilMethodId( name = "getUnsafeInstance", returnType = Unsafe::class.id, @@ -50,98 +50,98 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { /** * Method that creates instance using Unsafe */ - open val createInstanceMethodId: MethodId + val createInstanceMethodId: MethodId get() = utilClassId.utilMethodId( name = "createInstance", returnType = CgClassId(objectClassId, isNullable = true), arguments = arrayOf(stringClassId) ) - open val createArrayMethodId: MethodId + val createArrayMethodId: MethodId get() = utilClassId.utilMethodId( name = "createArray", returnType = Array::class.id, arguments = arrayOf(stringClassId, intClassId, Array::class.id) ) - open val setFieldMethodId: MethodId + val setFieldMethodId: MethodId get() = utilClassId.utilMethodId( name = "setField", returnType = voidClassId, arguments = arrayOf(objectClassId, stringClassId, objectClassId) ) - open val setStaticFieldMethodId: MethodId + val setStaticFieldMethodId: MethodId get() = utilClassId.utilMethodId( name = "setStaticField", returnType = voidClassId, arguments = arrayOf(Class::class.id, stringClassId, objectClassId) ) - open val getFieldValueMethodId: MethodId + val getFieldValueMethodId: MethodId get() = utilClassId.utilMethodId( name = "getFieldValue", returnType = objectClassId, arguments = arrayOf(objectClassId, stringClassId) ) - open val getStaticFieldValueMethodId: MethodId + val getStaticFieldValueMethodId: MethodId get() = utilClassId.utilMethodId( name = "getStaticFieldValue", returnType = objectClassId, arguments = arrayOf(Class::class.id, stringClassId) ) - open val getEnumConstantByNameMethodId: MethodId + val getEnumConstantByNameMethodId: MethodId get() = utilClassId.utilMethodId( name = "getEnumConstantByName", returnType = objectClassId, arguments = arrayOf(Class::class.id, stringClassId) ) - open val deepEqualsMethodId: MethodId + val deepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "deepEquals", returnType = booleanClassId, arguments = arrayOf(objectClassId, objectClassId) ) - open val arraysDeepEqualsMethodId: MethodId + val arraysDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "arraysDeepEquals", returnType = booleanClassId, arguments = arrayOf(objectClassId, objectClassId) ) - open val iterablesDeepEqualsMethodId: MethodId + val iterablesDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "iterablesDeepEquals", returnType = booleanClassId, arguments = arrayOf(java.lang.Iterable::class.id, java.lang.Iterable::class.id) ) - open val streamsDeepEqualsMethodId: MethodId + val streamsDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "streamsDeepEquals", returnType = booleanClassId, arguments = arrayOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id) ) - open val mapsDeepEqualsMethodId: MethodId + val mapsDeepEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "mapsDeepEquals", returnType = booleanClassId, arguments = arrayOf(java.util.Map::class.id, java.util.Map::class.id) ) - open val hasCustomEqualsMethodId: MethodId + val hasCustomEqualsMethodId: MethodId get() = utilClassId.utilMethodId( name = "hasCustomEquals", returnType = booleanClassId, arguments = arrayOf(Class::class.id) ) - open val getArrayLengthMethodId: MethodId + val getArrayLengthMethodId: MethodId get() = utilClassId.utilMethodId( name = "getArrayLength", returnType = intClassId, From 62c76c63893c1a5b4b59503ade7579dab9776e24 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Mon, 22 Aug 2022 15:56:32 +0300 Subject: [PATCH 25/30] Remove unnecessary file for class Patterns --- .../org/utbot/framework/plugin/api/util/CodegenUtil.kt | 6 ------ .../framework/codegen/model/util/DependencyPatterns.kt | 6 +++++- .../utbot/framework/codegen/model/util/DependencyUtils.kt | 2 -- .../org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt deleted file mode 100644 index 8e436b1fd2..0000000000 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/CodegenUtil.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.utbot.framework.plugin.api.util - -data class Patterns( - val moduleLibraryPatterns: List, - val libraryPatterns: List, -) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt index 9958815e1e..14245d10c0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt @@ -5,7 +5,11 @@ import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.TestFramework import org.utbot.framework.codegen.TestNg import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.util.Patterns + +data class Patterns( + val moduleLibraryPatterns: List, + val libraryPatterns: List, +) fun TestFramework.patterns(): Patterns { val moduleLibraryPatterns = when (this) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt index fc65e064c5..cf09cbc321 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt @@ -1,12 +1,10 @@ package org.utbot.framework.codegen.model.util -import org.utbot.framework.codegen.TestFramework import org.utbot.framework.concrete.UtExecutionInstrumentation import org.utbot.framework.plugin.api.MockFramework import java.io.File import java.util.jar.JarFile import mu.KotlinLogging -import org.utbot.framework.plugin.api.util.Patterns private val logger = KotlinLogging.logger {} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt index 49308b49bd..cc82cde6b6 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt @@ -6,7 +6,7 @@ import org.utbot.framework.plugin.api.MockFramework import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.roots.LibraryOrderEntry -import org.utbot.framework.plugin.api.util.Patterns +import org.utbot.framework.codegen.model.util.Patterns fun findFrameworkLibrary( project: Project, From 2b51d74a8358909a5ce41127357bf5f9d573dda7 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Mon, 22 Aug 2022 16:05:12 +0300 Subject: [PATCH 26/30] Add documentation for CgRegularClass --- .../utbot/framework/codegen/model/tree/CgElement.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 696e93de8e..ec8abe977a 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 @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.model.tree import org.utbot.common.WorkaroundReason import org.utbot.common.workaround import org.utbot.framework.codegen.Import +import org.utbot.framework.codegen.model.constructor.tree.CgUtilClassConstructor import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.codegen.model.util.CgExceptionHandler import org.utbot.framework.codegen.model.visitor.CgRendererContext @@ -149,6 +150,18 @@ sealed class AbstractCgClass : CgElement { get() = id.simpleName } +/** + * This class represents any class that we may want to generate other than the test class. + * At the moment the only such case is the generation of util class UtUtils. + * + * The difference with [CgTestClass] is in the body. + * The structure of a test class body is fixed (we know what it should contain), + * whereas an arbitrary class could contain anything. + * For example, the body of UtUtils class contains a comment with information + * about the version of UTBot it was generated with, and all the util methods. + * + * @see CgUtilClassConstructor + */ class CgRegularClass( override val id: ClassId, override val annotations: List, From 694e018174d7d49642852fc6a213c1eca8b99888 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Mon, 22 Aug 2022 16:06:55 +0300 Subject: [PATCH 27/30] Add documentation for CgRegularClass --- .../codegen/model/visitor/CgAbstractRenderer.kt | 10 ++++++++++ .../framework/codegen/model/visitor/CgJavaRenderer.kt | 8 -------- .../codegen/model/visitor/CgKotlinRenderer.kt | 8 -------- 3 files changed, 10 insertions(+), 16 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 b6c86f8250..38d5633020 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 @@ -59,6 +59,7 @@ import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgRegion +import org.utbot.framework.codegen.model.tree.CgRegularClass import org.utbot.framework.codegen.model.tree.CgRegularClassBody import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgReturnStatement @@ -71,6 +72,7 @@ import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgStaticRunnable import org.utbot.framework.codegen.model.tree.CgStaticsRegion +import org.utbot.framework.codegen.model.tree.CgTestClass import org.utbot.framework.codegen.model.tree.CgTestClassFile import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster @@ -151,6 +153,14 @@ internal abstract class CgAbstractRenderer( visit(element as AbstractCgClassFile<*>) } + override fun visit(element: CgRegularClass) { + visit(element as AbstractCgClass<*>) + } + + override fun visit(element: CgTestClass) { + visit(element as AbstractCgClass<*>) + } + override fun visit(element: AbstractCgClassBody) { visit(element as CgElement) } 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 75728e0705..88bbba9ffa 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 @@ -86,14 +86,6 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C println("}") } - override fun visit(element: CgRegularClass) { - visit(element as AbstractCgClass<*>) - } - - override fun visit(element: CgTestClass) { - visit(element as AbstractCgClass<*>) - } - override fun visit(element: CgTestClassBody) { // render regions for test methods and utils val allRegions = element.testMethodRegions + element.nestedClassRegions + element.staticDeclarationRegions 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 9b5d8a04e5..1e5a107218 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 @@ -107,14 +107,6 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = println("}") } - override fun visit(element: CgRegularClass) { - visit(element as AbstractCgClass<*>) - } - - override fun visit(element: CgTestClass) { - visit(element as AbstractCgClass<*>) - } - override fun visit(element: CgTestClassBody) { // render regions for test methods for ((i, region) in (element.testMethodRegions + element.nestedClassRegions).withIndex()) { From b9c740dac4e7ce6b2c792029190b20db5793aa91 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Tue, 23 Aug 2022 15:22:35 +0300 Subject: [PATCH 28/30] Store version of util class in a comment and use it to decide whether to overwrite an existing util class or not --- .../framework/codegen/model/CodeGenerator.kt | 25 +++++++++++++++ .../constructor/builtin/UtilMethodBuiltins.kt | 17 +++++++++- .../tree/CgUtilClassConstructor.kt | 4 +-- .../generator/CodeGenerationController.kt | 32 +++++++++---------- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index f21b9902e8..9577bdcb3f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -23,6 +23,8 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.codegen.model.constructor.TestClassModel +import org.utbot.framework.codegen.model.tree.CgComment +import org.utbot.framework.codegen.model.tree.CgSingleLineComment class CodeGenerator( private val classUnderTest: ClassId, @@ -145,6 +147,21 @@ sealed class UtilClassKind( private val priority: Int ) : Comparable { + /** + * The version of util class being generated. + * For more details see [UtilClassFileMethodProvider.UTIL_CLASS_VERSION]. + */ + val utilClassVersion: String + get() = UtilClassFileMethodProvider.UTIL_CLASS_VERSION + + /** + * The comment specifying the version of util class being generated. + * + * @see UtilClassFileMethodProvider.UTIL_CLASS_VERSION + */ + val utilClassVersionComment: CgComment + get() = CgSingleLineComment("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion}") + /** * A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework. */ @@ -171,6 +188,14 @@ sealed class UtilClassKind( } companion object { + + /** + * Class UtUtils will contain a comment specifying the version of this util class + * (if we ever change util methods, then util class will be different, hence the update of its version). + * This is a prefix that will go before the version in the comment. + */ + const val UTIL_CLASS_VERSION_COMMENT_PREFIX = "UtUtils class version: " + /** * Check if an util class is required, and if so, what kind. * @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider], diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index a54785c81c..4afe1d8cc8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.model.constructor.builtin import org.utbot.framework.codegen.MockitoStaticMocking import org.utbot.framework.codegen.model.constructor.util.utilMethodId import org.utbot.framework.codegen.model.tree.CgClassId +import org.utbot.framework.codegen.model.visitor.utilMethodTextById import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.MethodId @@ -155,7 +156,21 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { * * Content of this util class may be different (due to mocks in deepEquals), but the methods (and their ids) are the same. */ -internal object UtilClassFileMethodProvider : UtilMethodProvider(utUtilsClassId) +internal object UtilClassFileMethodProvider : UtilMethodProvider(utUtilsClassId) { + /** + * This property contains the current version of util class. + * This version will be written to the util class file inside a comment. + * + * Whenever we want to create an util class, we first check if there is an already existing one. + * If there is, then we decide whether we need to overwrite it or not. One of the factors here + * is the version of this existing class. If the version of existing class is older than the one + * that is currently stored in [UtilClassFileMethodProvider.UTIL_CLASS_VERSION], then we need to + * overwrite an util class, because it might have been changed in the new version. + * + * **IMPORTANT** if you make any changes to util methods (see [utilMethodTextById]), do not forget to update this version. + */ + const val UTIL_CLASS_VERSION = "1.0" +} internal class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt index de5f48b4d5..f4d274edb6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -4,7 +4,6 @@ import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.constructor.builtin.utUtilsClassId import org.utbot.framework.codegen.model.tree.CgRegularClassFile -import org.utbot.framework.codegen.model.tree.CgSingleLineComment import org.utbot.framework.codegen.model.tree.CgUtilMethod import org.utbot.framework.codegen.model.tree.buildRegularClass import org.utbot.framework.codegen.model.tree.buildRegularClassBody @@ -23,8 +22,7 @@ internal object CgUtilClassConstructor { declaredClass = buildRegularClass { id = utUtilsClassId body = buildRegularClassBody { - // TODO: get actual UTBot version and use it instead of the hardcoded one - content += CgSingleLineComment("UTBot version: 1.0-SNAPSHOT") + content += utilClassKind.utilClassVersionComment content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index d1045b8249..aee3577b15 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -33,6 +33,7 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.search.GlobalSearchScopesCore +import com.intellij.psi.util.childrenOfType import com.intellij.refactoring.util.classMembers.MemberInfo import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.util.IncorrectOperationException @@ -50,7 +51,6 @@ import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.scripting.resolve.classId -import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType import org.utbot.common.HTML_LINE_SEPARATOR import org.utbot.common.PathUtil.toHtmlLinkTag import org.utbot.common.allNestedClasses @@ -161,11 +161,12 @@ object CodeGenerationController { run(EDT_LATER) { waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) { + val mockFrameworkUsed = utilClassListener.mockFrameworkUsed val utilClassKind = utilClassListener.requiredUtilClassKind ?: return@waitForCountDown // no util class needed val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(model.project, model.testModule) - if (shouldCreateOrUpdateUtilClass(existingUtilClass, utilClassListener)) { + if (shouldCreateOrUpdateUtilClass(existingUtilClass, mockFrameworkUsed, utilClassKind)) { createOrUpdateUtilClass( testDirectory = baseTestDirectory, utilClassKind = utilClassKind, @@ -203,13 +204,12 @@ object CodeGenerationController { } } - private fun shouldCreateOrUpdateUtilClass(existingUtilClass: PsiFile?, utilClassListener: UtilClassListener): Boolean { - val existingUtilClassVersion = existingUtilClass?.utilClassVersionOrNull - // TODO: here should be the current version of UTBot - val newUtilClassVersion = "1.0-SNAPSHOT" - val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion - - val mockFrameworkNotUsed = !utilClassListener.mockFrameworkUsed + private fun shouldCreateOrUpdateUtilClass( + existingUtilClass: PsiFile?, + mockFrameworkUsed: Boolean, + requiredUtilClassKind: UtilClassKind + ): Boolean { + val mockFrameworkNotUsed = !mockFrameworkUsed val utilClassExists = existingUtilClass != null @@ -218,6 +218,10 @@ object CodeGenerationController { return true } + val existingUtilClassVersion = existingUtilClass?.utilClassVersionOrNull + val newUtilClassVersion = requiredUtilClassKind.utilClassVersion + val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion + if (versionIsUpdated) { // If an existing util class is out of date, // then we must overwrite it with a newer version. @@ -353,17 +357,11 @@ object CodeGenerationController { utilClass.childrenOfType() .map { comment -> comment.text } - .firstOrNull { text -> UTBOT_VERSION_PREFIX in text } - ?.substringAfterLast(UTBOT_VERSION_PREFIX) + .firstOrNull { text -> UtilClassKind.UTIL_CLASS_VERSION_COMMENT_PREFIX in text } + ?.substringAfterLast(UtilClassKind.UTIL_CLASS_VERSION_COMMENT_PREFIX) ?.trim() } - /** - * Util class must have a comment that specifies the version of UTBot it was generated with. - * This prefix is the start of this comment. The version of UTBot goes after it in the comment. - */ - private const val UTBOT_VERSION_PREFIX = "UTBot version:" - /** * @param srcClass class under test * @return name of the package of a given [srcClass]. From d463ae2baaf95a62776205f550b53545078751fd Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Tue, 23 Aug 2022 16:56:35 +0300 Subject: [PATCH 29/30] Store kind of util class in a comment to obtain the kind of an existing util class --- .../framework/codegen/model/CodeGenerator.kt | 42 ++++++-- .../tree/CgUtilClassConstructor.kt | 1 + .../generator/CodeGenerationController.kt | 101 +++++++++++------- 3 files changed, 100 insertions(+), 44 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 9577bdcb3f..5947ec8674 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -82,8 +82,7 @@ class CodeGenerator( CodeGeneratorResult( generatedCode = renderClassFile(testClassFile), utilClassKind = UtilClassKind.fromCgContextOrNull(context), - testsGenerationReport = testClassFile.testsGenerationReport, - mockFrameworkUsed = context.mockFrameworkUsed + testsGenerationReport = testClassFile.testsGenerationReport ) } } @@ -117,14 +116,12 @@ class CodeGenerator( * @property generatedCode the source code of the test class * @property utilClassKind the kind of util class if it is required, otherwise - null * @property testsGenerationReport some info about test generation process - * @property mockFrameworkUsed flag indicating whether any mock objects have been created during code generation ot not */ data class CodeGeneratorResult( val generatedCode: String, // null if no util class needed, e.g. when we are generating utils directly into test class val utilClassKind: UtilClassKind?, val testsGenerationReport: TestsGenerationReport, - val mockFrameworkUsed: Boolean = false ) /** @@ -162,15 +159,40 @@ sealed class UtilClassKind( val utilClassVersionComment: CgComment get() = CgSingleLineComment("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion}") + + /** + * The comment specifying the kind of util class being generated. + * + * @see utilClassKindCommentText + */ + val utilClassKindComment: CgComment + get() = CgSingleLineComment(utilClassKindCommentText) + + /** + * The text of comment specifying the kind of util class. + * At the moment, there are two kinds: [RegularUtUtils] (without Mockito) and [UtUtilsWithMockito]. + * + * This comment is needed when the plugin decides whether to overwrite an existing util class or not. + * When making that decision, it is important to determine if the existing class uses mocks or not, + * and this comment will help do that. + */ + abstract val utilClassKindCommentText: String + /** * A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework. */ - object RegularUtUtils : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = false, priority = 0) + object RegularUtUtils : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = false, priority = 0) { + override val utilClassKindCommentText: String + get() = "This is a regular UtUtils class (without mock framework usage)" + } /** * A kind of UtUtils class that uses a mock framework. At the moment the framework is Mockito. */ - object UtUtilsWithMockito : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = true, priority = 1) + object UtUtilsWithMockito : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = true, priority = 1) { + override val utilClassKindCommentText: String + get() = "This is UtUtils class with Mockito support" + } override fun compareTo(other: UtilClassKind): Int { return priority.compareTo(other.priority) @@ -196,6 +218,14 @@ sealed class UtilClassKind( */ const val UTIL_CLASS_VERSION_COMMENT_PREFIX = "UtUtils class version: " + fun utilClassKindByCommentOrNull(comment: String): UtilClassKind? { + return when (comment) { + RegularUtUtils.utilClassKindCommentText -> RegularUtUtils + UtUtilsWithMockito.utilClassKindCommentText -> UtUtilsWithMockito + else -> null + } + } + /** * Check if an util class is required, and if so, what kind. * @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider], diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt index f4d274edb6..e81af665a4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -23,6 +23,7 @@ internal object CgUtilClassConstructor { id = utUtilsClassId body = buildRegularClassBody { content += utilClassKind.utilClassVersionComment + content += utilClassKind.utilClassKindComment content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index aee3577b15..51aeb18c1c 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -98,19 +98,9 @@ object CodeGenerationController { private class UtilClassListener { var requiredUtilClassKind: UtilClassKind? = null - var mockFrameworkUsed: Boolean = false fun onTestClassGenerated(result: CodeGeneratorResult) { requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind) - mockFrameworkUsed = maxOf(mockFrameworkUsed, result.mockFrameworkUsed) - } - - private fun > maxOfNullable(a: T?, b: T?): T? { - return when { - a == null -> b - b == null -> a - else -> maxOf(a, b) - } } } @@ -161,12 +151,12 @@ object CodeGenerationController { run(EDT_LATER) { waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) { - val mockFrameworkUsed = utilClassListener.mockFrameworkUsed - val utilClassKind = utilClassListener.requiredUtilClassKind + val requiredUtilClassKind = utilClassListener.requiredUtilClassKind ?: return@waitForCountDown // no util class needed val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(model.project, model.testModule) - if (shouldCreateOrUpdateUtilClass(existingUtilClass, mockFrameworkUsed, utilClassKind)) { + val utilClassKind = newUtilClassKindOrNull(existingUtilClass, requiredUtilClassKind) + if (utilClassKind != null) { createOrUpdateUtilClass( testDirectory = baseTestDirectory, utilClassKind = utilClassKind, @@ -204,40 +194,50 @@ object CodeGenerationController { } } - private fun shouldCreateOrUpdateUtilClass( - existingUtilClass: PsiFile?, - mockFrameworkUsed: Boolean, - requiredUtilClassKind: UtilClassKind - ): Boolean { - val mockFrameworkNotUsed = !mockFrameworkUsed - - val utilClassExists = existingUtilClass != null - - if (!utilClassExists) { - // If no util class exists, then we should create a new one. - return true + /** + * This method decides whether to overwrite an existing util class with a new one. And if so, then with what kind of util class. + * - If no util class exists, then we generate a new one. + * - If existing util class' version is out of date, then we overwrite it with a new one. + * But we use the maximum of two kinds (existing and the new one) to avoid problems with mocks. + * - If existing util class is up-to-date **and** has a greater or equal priority than the new one, + * then we do not need to overwrite it (return null). + * - Lastly, if the new util class kind has a greater priority than the existing one, + * then we do overwrite it with a newer version. + * + * @param existingUtilClass a [PsiFile] representing a file of an existing util class. If it does not exist, then [existingUtilClass] is `null`. + * @param requiredUtilClassKind the kind of the new util class that we attempt to generate. + * @return an [UtilClassKind] of a new util class that will be created or `null`, if no new util class is needed. + */ + private fun newUtilClassKindOrNull(existingUtilClass: PsiFile?, requiredUtilClassKind: UtilClassKind): UtilClassKind? { + if (existingUtilClass == null) { + // If no util class exists, then we should create a new one with the given kind. + return requiredUtilClassKind } - val existingUtilClassVersion = existingUtilClass?.utilClassVersionOrNull + val existingUtilClassVersion = existingUtilClass.utilClassVersionOrNull ?: return requiredUtilClassKind val newUtilClassVersion = requiredUtilClassKind.utilClassVersion val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion + val existingUtilClassKind = existingUtilClass.utilClassKindOrNull ?: return requiredUtilClassKind + if (versionIsUpdated) { - // If an existing util class is out of date, - // then we must overwrite it with a newer version. - return true + // If an existing util class is out of date, then we must overwrite it with a newer version. + // But we choose the kind with more priority, because it is possible that + // the existing util class needed mocks, but the new one doesn't. + // In this case we still want to support mocks, because the previously generated tests + // expect that the util class does support them. + return maxOfNullable(existingUtilClassKind, requiredUtilClassKind) } - if (mockFrameworkNotUsed) { - // If util class already exists and mock framework is not used, - // then existing util class is enough, and we don't need to generate a new one. - // That's because both regular and mock versions of util class can work - // with tests that do not use mocks, so we do not have to worry about - // version of util class that we have at the moment. - return false + if (requiredUtilClassKind <= existingUtilClassKind) { + // If the existing util class kind has a greater or equal priority than the new one we attempt to generate, + // then we should not do anything. The existing util class is already enough. + return null } - return true + // The last case. The existing util class has a strictly less priority than the new one. + // So we generate the new one to overwrite the previous one with it. + return requiredUtilClassKind } /** @@ -345,7 +345,7 @@ object CodeGenerationController { } /** - * Util class must have a comment that specifies the version of UTBot it was generated with. + * Util class must have a comment that specifies its version. * This property represents the version specified by this comment if it exists. Otherwise, the property is `null`. */ private val PsiFile.utilClassVersionOrNull: String? @@ -362,6 +362,23 @@ object CodeGenerationController { ?.trim() } + /** + * Util class must have a comment that specifies its kind. + * This property obtains the kind specified by this comment if it exists. Otherwise, the property is `null`. + */ + private val PsiFile.utilClassKindOrNull: UtilClassKind? + get() = runReadAction { + val utilClass = (this as? PsiClassOwner) + ?.classes + ?.firstOrNull() + ?: return@runReadAction null + + utilClass.childrenOfType() + .map { comment -> comment.text } + .mapNotNull { text -> UtilClassKind.utilClassKindByCommentOrNull(text) } + .firstOrNull() + } + /** * @param srcClass class under test * @return name of the package of a given [srcClass]. @@ -886,4 +903,12 @@ object CodeGenerationController { title = "Failed to Create Class" ) } + + private fun > maxOfNullable(a: T?, b: T?): T? { + return when { + a == null -> b + b == null -> a + else -> maxOf(a, b) + } + } } From 1c01865462e5c45dc3994145ac62064587f36054 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Wed, 31 Aug 2022 19:38:37 +0300 Subject: [PATCH 30/30] Fix incorrect import --- .../intellij/plugin/generator/CodeGenerationController.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 51aeb18c1c..8d3eac68bd 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -33,7 +33,6 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.search.GlobalSearchScopesCore -import com.intellij.psi.util.childrenOfType import com.intellij.refactoring.util.classMembers.MemberInfo import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.util.IncorrectOperationException @@ -49,6 +48,7 @@ import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.scripting.resolve.classId import org.utbot.common.HTML_LINE_SEPARATOR @@ -355,7 +355,7 @@ object CodeGenerationController { ?.firstOrNull() ?: return@runReadAction null - utilClass.childrenOfType() + utilClass.getChildrenOfType() .map { comment -> comment.text } .firstOrNull { text -> UtilClassKind.UTIL_CLASS_VERSION_COMMENT_PREFIX in text } ?.substringAfterLast(UtilClassKind.UTIL_CLASS_VERSION_COMMENT_PREFIX) @@ -373,7 +373,7 @@ object CodeGenerationController { ?.firstOrNull() ?: return@runReadAction null - utilClass.childrenOfType() + utilClass.getChildrenOfType() .map { comment -> comment.text } .mapNotNull { text -> UtilClassKind.utilClassKindByCommentOrNull(text) } .firstOrNull()