From b53432717821af98f385e5234c7521244a4610aa Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Fri, 3 Jun 2022 17:55:43 +0300 Subject: [PATCH 1/7] Added missing num dimension constraints for generics --- .../main/kotlin/org/utbot/engine/Memory.kt | 3 +- .../utbot/engine/pc/Z3TranslatorVisitor.kt | 33 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 0ca5359db9..e75556b86b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -431,7 +431,6 @@ class TypeRegistry { private val genericAddrToNumDimensionsArrays = mutableMapOf() - @Suppress("unused") private fun genericAddrToNumDimensions(i: Int) = genericAddrToNumDimensionsArrays.getOrPut(i) { mkArrayConst( "genericAddrToNumDimensions_$i", @@ -562,6 +561,8 @@ class TypeRegistry { */ fun symNumDimensions(addr: UtAddrExpression) = addrToNumDimensions.select(addr) + fun genericNumDimensions(addr: UtAddrExpression, i: Int) = genericAddrToNumDimensions(i).select(addr) + /** * Returns a constraint stating that number of dimensions for the given address is zero */ diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt index 0a8aea1f0b..4718f673c6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt @@ -226,10 +226,13 @@ open class Z3TranslatorVisitor( override fun visit(expr: UtGenericExpression): Expr = expr.run { val constraints = mutableListOf() for (i in types.indices) { + val symNumDimensions = translate(typeRegistry.genericNumDimensions(addr, i)) as BitVecExpr val symType = translate(typeRegistry.genericTypeId(addr, i)) + if (types[i].leastCommonType.isJavaLangObject()) { continue } + val possibleBaseTypes = types[i].possibleConcreteTypes.map { it.baseType } val typeConstraint = z3Context.mkOr( @@ -241,6 +244,22 @@ open class Z3TranslatorVisitor( }.toTypedArray() ) constraints += typeConstraint + + val numDimensionsConstraint = z3Context.mkAnd( + *possibleBaseTypes.map { + val numDimensions = z3Context.mkBV(it.numDimensions, Int.SIZE_BITS) + + if (it.isJavaLangObject()) { + z3Context.mkBVSGE(symNumDimensions, numDimensions) + } else { + z3Context.mkEq(symNumDimensions, numDimensions) + } + }.toTypedArray() + ) + + constraints += numDimensionsConstraint + + z3Context.mkAnd(*constraints.toTypedArray()) } z3Context.mkOr( z3Context.mkAnd(*constraints.toTypedArray()), @@ -250,11 +269,19 @@ open class Z3TranslatorVisitor( override fun visit(expr: UtIsGenericTypeExpression): Expr = expr.run { val symType = translate(typeRegistry.symTypeId(addr)) + val symNumDimensions = translate(typeRegistry.symNumDimensions(addr)) + val genericSymType = translate(typeRegistry.genericTypeId(baseAddr, parameterTypeIndex)) - z3Context.mkOr( + val genericNumDimensions = translate(typeRegistry.genericNumDimensions(baseAddr, parameterTypeIndex)) + + val typeConstraint = z3Context.mkOr( z3Context.mkEq(symType, genericSymType), z3Context.mkEq(translate(expr.addr), translate(nullObjectAddr)) ) + + val dimensionsConstraint = z3Context.mkEq(symNumDimensions, genericNumDimensions) + + z3Context.mkAnd(typeConstraint, dimensionsConstraint) } override fun visit(expr: UtEqGenericTypeParametersExpression): Expr = expr.run { @@ -263,6 +290,10 @@ open class Z3TranslatorVisitor( val firstSymType = translate(typeRegistry.genericTypeId(firstAddr, i)) val secondSymType = translate(typeRegistry.genericTypeId(secondAddr, j)) constraints += z3Context.mkEq(firstSymType, secondSymType) + + val firstSymNumDimensions = translate(typeRegistry.genericNumDimensions(firstAddr, i)) + val secondSymNumDimensions = translate(typeRegistry.genericNumDimensions(secondAddr, j)) + constraints += z3Context.mkEq(firstSymNumDimensions, secondSymNumDimensions) } z3Context.mkAnd(*constraints.toTypedArray()) } From c4c0c17d1732257b64feb8f00208914b29443400 Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Tue, 14 Jun 2022 09:50:10 +0300 Subject: [PATCH 2/7] Small fix for constructing type storage --- .../src/main/kotlin/org/utbot/engine/TypeResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt index 486499a5bc..e9f35643cc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt @@ -110,7 +110,7 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy fun constructTypeStorage(type: Type, possibleTypes: Collection): TypeStorage { val concretePossibleTypes = possibleTypes .map { (if (it is ArrayType) it.baseType else it) to it.numDimensions } - .filterNot { (baseType, _) -> baseType is RefType && baseType.sootClass.isInappropriate } + .filterNot { (baseType, numDimensions) -> numDimensions == 0 && baseType is RefType && baseType.sootClass.isInappropriate } .mapTo(mutableSetOf()) { (baseType, numDimensions) -> if (numDimensions == 0) baseType else baseType.makeArrayType(numDimensions) } From 831ab04fdbd977d6678f6b51621a982f737ad86e Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Tue, 24 May 2022 15:18:56 +0300 Subject: [PATCH 3/7] Implemented UtStream --- .../plugin/api/impl/FieldIdStrategies.kt | 2 +- .../overrides/collections/Collection.java | 27 + .../overrides/collections/UtArrayList.java | 25 + .../overrides/collections/UtHashSet.java | 25 + .../overrides/collections/UtLinkedList.java | 26 + .../utbot/engine/overrides/stream/Arrays.java | 20 + .../utbot/engine/overrides/stream/Stream.java | 54 ++ .../engine/overrides/stream/UtStream.java | 682 ++++++++++++++++++ .../org/utbot/engine/ArrayObjectWrappers.kt | 54 +- .../org/utbot/engine/CollectionWrappers.kt | 177 ++--- .../main/kotlin/org/utbot/engine/Memory.kt | 30 +- .../src/main/kotlin/org/utbot/engine/Mocks.kt | 8 +- .../kotlin/org/utbot/engine/ObjectWrappers.kt | 22 +- .../org/utbot/engine/OptionalWrapper.kt | 7 +- .../kotlin/org/utbot/engine/StreamWrappers.kt | 127 ++++ .../main/kotlin/org/utbot/engine/Strings.kt | 122 ++-- .../org/utbot/engine/UtBotSymbolicEngine.kt | 20 +- .../utbot/engine/pc/Z3TranslatorVisitor.kt | 15 - .../constructor/builtin/UtilMethodBuiltins.kt | 8 + .../model/constructor/context/CgContext.kt | 4 + .../tree/CgCallableAccessManager.kt | 2 + .../tree/CgTestClassConstructor.kt | 4 +- .../constructor/tree/TestFrameworkManager.kt | 2 + .../codegen/model/visitor/UtilMethods.kt | 68 ++ .../utbot/framework/plugin/api/SootUtils.kt | 9 +- .../examples/AbstractTestCaseGeneratorTest.kt | 15 +- .../lambda/SimpleLambdaExamplesTest.kt | 32 + .../examples/stream/BaseStreamExampleTest.kt | 372 ++++++++++ .../examples/lambda/SimpleLambdaExamples.java | 21 + .../examples/stream/BaseStreamExample.java | 458 ++++++++++++ 30 files changed, 2238 insertions(+), 200 deletions(-) create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/SimpleLambdaExamples.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt index a29a58c026..8ce32fdbc2 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt @@ -47,7 +47,7 @@ class FieldIdReflectionStrategy(val fieldId: FieldId) : FieldIdStrategy { get() = Modifier.isStatic(fieldId.field.modifiers) override val isSynthetic: Boolean - get() = false + get() = fieldId.field.isSynthetic override val type: ClassId get() = fieldId.field.type.id diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java new file mode 100644 index 0000000000..8f7edebe37 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java @@ -0,0 +1,27 @@ +package org.utbot.engine.overrides.collections; + +import org.utbot.api.annotation.UtClassMock; +import org.utbot.engine.overrides.stream.UtStream; + +import java.util.stream.Stream; + +@UtClassMock(target = java.util.Collection.class, internalUsage = true) +public interface Collection extends java.util.Collection { + @SuppressWarnings("unchecked") + @Override + default Stream parallelStream() { + Object[] data = toArray(); + int size = data.length; + + return new UtStream<>((E[]) data, size); + } + + @SuppressWarnings("unchecked") + @Override + default Stream stream() { + Object[] data = toArray(); + int size = data.length; + + return new UtStream<>((E[]) data, size); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java index 9020f73192..8d3c4d45e5 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java @@ -13,7 +13,10 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import java.util.stream.Stream; + import org.jetbrains.annotations.NotNull; +import org.utbot.engine.overrides.stream.UtStream; import static org.utbot.api.mock.UtMock.assume; import static org.utbot.engine.ResolverKt.MAX_LIST_SIZE; @@ -361,6 +364,28 @@ public void replaceAll(UnaryOperator operator) { } } + @SuppressWarnings("unchecked") + @Override + public Stream stream() { + preconditionCheck(); + + int size = elementData.end; + Object[] data = elementData.toArray(0, size); + + return new UtStream<>((E[]) data, size); + } + + @SuppressWarnings("unchecked") + @Override + public Stream parallelStream() { + preconditionCheck(); + + int size = elementData.end; + Object[] data = elementData.toArray(0, size); + + return new UtStream<>((E[]) data, size); + } + /** * Auxiliary method, that should be only executed concretely * @return new ArrayList with all the elements from this. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java index b081ecab7c..69dd700e22 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java @@ -8,7 +8,10 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; + import org.jetbrains.annotations.NotNull; +import org.utbot.engine.overrides.stream.UtStream; import static org.utbot.api.mock.UtMock.assume; import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; @@ -263,6 +266,28 @@ public Iterator iterator() { return new UtHashSetIterator(); } + @SuppressWarnings("unchecked") + @Override + public Stream stream() { + preconditionCheck(); + + int size = elementData.end; + Object[] data = elementData.toArray(0, size); + + return new UtStream<>((E[]) data, size); + } + + @SuppressWarnings("unchecked") + @Override + public Stream parallelStream() { + preconditionCheck(); + + int size = elementData.end; + Object[] data = elementData.toArray(0, size); + + return new UtStream<>((E[]) data, size); + } + public class UtHashSetIterator implements Iterator { int index = 0; diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java index 163e88f761..55a577cb52 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java @@ -13,7 +13,10 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import java.util.stream.Stream; + import org.jetbrains.annotations.NotNull; +import org.utbot.engine.overrides.stream.UtStream; import static org.utbot.api.mock.UtMock.assume; import static org.utbot.engine.ResolverKt.MAX_LIST_SIZE; @@ -448,6 +451,29 @@ public Iterator descendingIterator() { preconditionCheck(); return new ReverseIteratorWrapper(elementData.end); } + + @SuppressWarnings("unchecked") + @Override + public Stream stream() { + preconditionCheck(); + + int size = elementData.end; + Object[] data = elementData.toArray(0, size); + + return new UtStream<>((E[]) data, size); + } + + @SuppressWarnings("unchecked") + @Override + public Stream parallelStream() { + preconditionCheck(); + + int size = elementData.end; + Object[] data = elementData.toArray(0, size); + + return new UtStream<>((E[]) data, size); + } + public class ReverseIteratorWrapper implements ListIterator { int index; diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java new file mode 100644 index 0000000000..01b5f7dcf7 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java @@ -0,0 +1,20 @@ +package org.utbot.engine.overrides.stream; + +import org.utbot.api.annotation.UtClassMock; + +import java.util.stream.Stream; + +@UtClassMock(target = java.util.Arrays.class, internalUsage = true) +public class Arrays { + public static Stream stream(T[] array, int startInclusive, int endExclusive) { + int size = array.length; + + if (startInclusive < 0 || endExclusive < startInclusive || endExclusive > size) { + throw new ArrayIndexOutOfBoundsException(); + } + + return new UtStream<>(array, startInclusive, endExclusive); + } + + // TODO primitive arrays https://github.com/UnitTestBot/UTBotJava/issues/146 +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java new file mode 100644 index 0000000000..e56ecf308b --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java @@ -0,0 +1,54 @@ +package org.utbot.engine.overrides.stream; + +import org.utbot.api.annotation.UtClassMock; + +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.stream.BaseStream; + +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; + +@SuppressWarnings({"UnnecessaryInterfaceModifier", "unused"}) +@UtClassMock(target = java.util.stream.Stream.class, internalUsage = true) +public interface Stream extends BaseStream> { + @SuppressWarnings("unchecked") + public static java.util.stream.Stream of(E element) { + Object[] data = new Object[1]; + data[0] = element; + + return new UtStream<>((E[]) data, 1); + } + + @SuppressWarnings("unchecked") + public static java.util.stream.Stream of(E... elements) { + int size = elements.length; + + return new UtStream<>(elements, size); + } + + @SuppressWarnings("unchecked") + public static java.util.stream.Stream empty() { + return new UtStream<>((E[]) new Object[]{}, 0); + } + + public static java.util.stream.Stream generate(Supplier s) { + // as "generate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + public static java.util.stream.Stream iterate(final E seed, final UnaryOperator f) { + // as "iterate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + public static java.util.stream.Stream concat( + java.util.stream.Stream a, + java.util.stream.Stream b + ) { + // as provided streams might be infinite, we cannot analyze this method symbolically + executeConcretely(); + return null; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java new file mode 100644 index 0000000000..a379f4fe43 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java @@ -0,0 +1,682 @@ +package org.utbot.engine.overrides.stream; + +import org.utbot.engine.overrides.UtArrayMock; +import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray; +import org.utbot.engine.overrides.collections.UtGenericStorage; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import java.util.stream.Collector; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.ResolverKt.HARD_MAX_ARRAY_SIZE; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.parameter; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtStream implements Stream, UtGenericStorage { + private final RangeModifiableUnlimitedArray elementData; + + private final RangeModifiableUnlimitedArray closeHandlers = new RangeModifiableUnlimitedArray<>(); + + private boolean isParallel = false; + + /** + * {@code false} by default, assigned to {@code true} after performing any operation on this stream. Any operation, + * performed on a closed UtStream, throws the {@link IllegalStateException}. + */ + private boolean isClosed = false; + + public UtStream() { + visit(this); + elementData = new RangeModifiableUnlimitedArray<>(); + } + + public UtStream(E[] data, int length) { + visit(this); + elementData = new RangeModifiableUnlimitedArray<>(); + elementData.setRange(0, data, 0, length); + elementData.end = length; + } + + public UtStream(E[] data, int startInclusive, int endExclusive) { + visit(this); + + int size = endExclusive - startInclusive; + + elementData = new RangeModifiableUnlimitedArray<>(); + elementData.setRange(0, data, startInclusive, size); + elementData.end = size; + } + + /** + * Precondition check is called only once by object, + * if it was passed as parameter to method under test. + *

+ * Preconditions that are must be satisfied: + *

  • elementData.size in 0..HARD_MAX_ARRAY_SIZE.
  • + *
  • elementData is marked as parameter
  • + *
  • elementData.storage and it's elements are marked as parameters
  • + */ + @SuppressWarnings("DuplicatedCode") + void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + setEqualGenericType(elementData); + + assume(elementData != null); + assume(elementData.storage != null); + + parameter(elementData); + parameter(elementData.storage); + + assume(elementData.begin == 0); + + assume(elementData.end >= 0); + // we can create a stream for an array using Stream.of + assume(elementData.end <= HARD_MAX_ARRAY_SIZE); + + visit(this); + } + + private void preconditionCheckWithClosingStream() { + preconditionCheck(); + + if (isClosed) { + throw new IllegalStateException(); + } + + // Even if exception occurs in the body of a stream operation, this stream could not be used later. + isClosed = true; + } + + @SuppressWarnings("unchecked") + @Override + public Stream filter(Predicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Object[] filtered = new Object[size]; + int j = 0; + for (int i = 0; i < size; i++) { + E element = elementData.get(i); + if (predicate.test(element)) { + filtered[j++] = element; + } + } + + return new UtStream<>((E[]) filtered, j); + } + + @SuppressWarnings("unchecked") + @Override + public Stream map(Function mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Object[] mapped = new Object[size]; + for (int i = 0; i < size; i++) { + mapped[i] = mapper.apply(elementData.get(i)); + } + + return new UtStream<>((R[]) mapped, size); + } + + @Override + public IntStream mapToInt(ToIntFunction mapper) { + preconditionCheckWithClosingStream(); + // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + executeConcretely(); + return null; + } + + @Override + public LongStream mapToLong(ToLongFunction mapper) { + preconditionCheckWithClosingStream(); + // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + executeConcretely(); + return null; + } + + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + preconditionCheckWithClosingStream(); + // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + executeConcretely(); + return null; + } + + @Override + public Stream flatMap(Function> mapper) { + preconditionCheckWithClosingStream(); + // as mapper can produce infinite streams, we cannot process it symbolically + executeConcretely(); + return null; + } + + @Override + public IntStream flatMapToInt(Function mapper) { + preconditionCheckWithClosingStream(); + // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + executeConcretely(); + return null; + } + + @Override + public LongStream flatMapToLong(Function mapper) { + preconditionCheckWithClosingStream(); + // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + executeConcretely(); + return null; + } + + @Override + public DoubleStream flatMapToDouble(Function mapper) { + preconditionCheckWithClosingStream(); + // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + executeConcretely(); + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Stream distinct() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Object[] distinctElements = new Object[size]; + int distinctSize = 0; + for (int i = 0; i < size; i++) { + E element = elementData.get(i); + boolean isDuplicate = false; + + if (element == null) { + for (int j = 0; j < distinctSize; j++) { + Object alreadyProcessedElement = distinctElements[j]; + if (alreadyProcessedElement == null) { + isDuplicate = true; + break; + } + } + } else { + for (int j = 0; j < distinctSize; j++) { + Object alreadyProcessedElement = distinctElements[j]; + if (element.equals(alreadyProcessedElement)) { + isDuplicate = true; + break; + } + } + } + + if (!isDuplicate) { + distinctElements[distinctSize++] = element; + } + } + + return new UtStream<>((E[]) distinctElements, distinctSize); + } + + // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + @SuppressWarnings("unchecked") + @Override + public Stream sorted() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + + if (size == 0) { + return new UtStream<>(); + } + + Object[] sortedElements = UtArrayMock.copyOf(elementData.toArray(0, size), size); + + // bubble sort + for (int i = 0; i < size - 1; i++) { + for (int j = 0; j < size - i - 1; j++) { + if (((Comparable) sortedElements[j]).compareTo((E) sortedElements[j + 1]) > 0) { + Object tmp = sortedElements[j]; + sortedElements[j] = sortedElements[j + 1]; + sortedElements[j + 1] = tmp; + } + } + } + + return new UtStream<>((E[]) sortedElements, size); + } + + // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + @SuppressWarnings("unchecked") + @Override + public Stream sorted(Comparator comparator) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + + if (size == 0) { + return new UtStream<>(); + } + + Object[] sortedElements = UtArrayMock.copyOf(elementData.toArray(0, size), size); + + // bubble sort + for (int i = 0; i < size - 1; i++) { + for (int j = 0; j < size - i - 1; j++) { + if (comparator.compare((E) sortedElements[j], (E) sortedElements[j + 1]) > 0) { + Object tmp = sortedElements[j]; + sortedElements[j] = sortedElements[j + 1]; + sortedElements[j + 1] = tmp; + } + } + } + + return new UtStream<>((E[]) sortedElements, size); + } + + @Override + public Stream peek(Consumer action) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + action.accept(elementData.get(i)); + } + + // returned stream should be opened, so we "reopen" this stream to return it + isClosed = false; + + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Stream limit(long maxSize) { + preconditionCheckWithClosingStream(); + + if (maxSize < 0) { + throw new IllegalArgumentException(); + } + + // TODO how to process long value correctly - assumeOrExecuteConcretely? + int newSize = (int) maxSize; + int curSize = elementData.end; + + if (newSize == curSize) { + return this; + } + + if (newSize > curSize) { + newSize = curSize; + } + + return new UtStream<>((E[]) elementData.toArray(0, newSize), newSize); + } + + @SuppressWarnings("unchecked") + @Override + public Stream skip(long n) { + preconditionCheckWithClosingStream(); + + if (n < 0) { + throw new IllegalArgumentException(); + } + + if (n == 0) { + return this; + } + + int curSize = elementData.end; + if (n > curSize) { + return new UtStream<>(); + } + + // TODO how to process long value correctly - assumeOrExecuteConcretely? + int newSize = (int) (curSize - n); + + return new UtStream<>((E[]) elementData.toArray((int) n, newSize), newSize); + } + + @Override + public void forEach(Consumer action) { + peek(action); + } + + @Override + public void forEachOrdered(Consumer action) { + peek(action); + } + + @NotNull + @Override + public Object[] toArray() { + preconditionCheckWithClosingStream(); + + return elementData.toArray(0, elementData.end); + } + + @NotNull + @Override + public A[] toArray(IntFunction generator) { + preconditionCheckWithClosingStream(); + + // TODO untracked ArrayStoreException - JIRA:1089 + int size = elementData.end; + A[] array = generator.apply(size); + + UtArrayMock.arraycopy(elementData.toArray(0, size), 0, array, 0, size); + + return array; + } + + @Override + public E reduce(E identity, BinaryOperator accumulator) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + E result = identity; + for (int i = 0; i < size; i++) { + result = accumulator.apply(result, elementData.get(i)); + } + + return result; + } + + @SuppressWarnings("ConstantConditions") + @NotNull + @Override + public Optional reduce(BinaryOperator accumulator) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return Optional.empty(); + } + + E result = null; + for (int i = 0; i < size; i++) { + E element = elementData.get(i); + if (result == null) { + result = element; + } else { + result = accumulator.apply(result, element); + } + } + + return Optional.of(result); + } + + @Override + public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we do not need to use the combiner + int size = elementData.end; + U result = identity; + for (int i = 0; i < size; i++) { + result = accumulator.apply(result, elementData.get(i)); + } + + return result; + } + + @Override + public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we do not need to use the combiner + int size = elementData.end; + R result = supplier.get(); + for (int i = 0; i < size; i++) { + accumulator.accept(result, elementData.get(i)); + } + + return result; + } + + @Override + public R collect(Collector collector) { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we do not need to use the combiner + int size = elementData.end; + A result = collector.supplier().get(); + for (int i = 0; i < size; i++) { + collector.accumulator().accept(result, elementData.get(i)); + } + + return collector.finisher().apply(result); + } + + @NotNull + @Override + public Optional min(Comparator comparator) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return Optional.empty(); + } + + E min = elementData.get(0); + for (int i = 1; i < size; i++) { + E element = elementData.get(i); + if (comparator.compare(min, element) > 0) { + min = element; + } + } + + return Optional.of(min); + } + + @NotNull + @Override + public Optional max(Comparator comparator) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return Optional.empty(); + } + + E max = elementData.get(0); + for (int i = 1; i < size; i++) { + E element = elementData.get(i); + if (comparator.compare(max, element) < 0) { + max = element; + } + } + + return Optional.of(max); + } + + @Override + public long count() { + preconditionCheckWithClosingStream(); + + return elementData.end; + } + + @Override + public boolean anyMatch(Predicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (predicate.test(elementData.get(i))) { + return true; + } + } + + return false; + } + + @Override + public boolean allMatch(Predicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (!predicate.test(elementData.get(i))) { + return false; + } + } + + return true; + } + + @Override + public boolean noneMatch(Predicate predicate) { + preconditionCheckWithClosingStream(); + + return !anyMatch(predicate); + } + + @NotNull + @Override + public Optional findFirst() { + preconditionCheckWithClosingStream(); + + if (elementData.end == 0) { + return Optional.empty(); + } + + E first = elementData.get(0); + + return Optional.of(first); + } + + @NotNull + @Override + public Optional findAny() { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we can just return the first element + return findFirst(); + } + + @NotNull + @Override + public Iterator iterator() { + preconditionCheckWithClosingStream(); + + return new UtStreamIterator(0); + } + + @NotNull + @Override + public Spliterator spliterator() { + preconditionCheckWithClosingStream(); + // implementation from AbstractList + return Spliterators.spliterator(elementData.toArray(0, elementData.end), Spliterator.ORDERED); + } + + @Override + public boolean isParallel() { + // this method does not "close" this stream + preconditionCheck(); + + return isParallel; + } + + @NotNull + @Override + public Stream sequential() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = false; + + return this; + } + + @NotNull + @Override + public Stream parallel() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = true; + + return this; + } + + @NotNull + @Override + public Stream unordered() { + // this method does not "close" this stream + preconditionCheck(); + + return this; + } + + @NotNull + @Override + public Stream onClose(Runnable closeHandler) { + // this method does not "close" this stream + preconditionCheck(); + + // adds closeHandler to existing + closeHandlers.set(closeHandlers.end++, closeHandler); + + return this; + } + + @Override + public void close() { + // Stream can be closed via this method many times + preconditionCheck(); + + // TODO resources closing https://github.com/UnitTestBot/UTBotJava/issues/189 + + // NOTE: this implementation does not care about suppressing and throwing exceptions produced by handlers + for (int i = 0; i < closeHandlers.end; i++) { + closeHandlers.get(i).run(); + } + } + + public class UtStreamIterator implements Iterator { + int index; + + UtStreamIterator(int index) { + if (index < 0 || index > elementData.end) { + throw new IndexOutOfBoundsException(); + } + + this.index = index; + } + + @Override + public boolean hasNext() { + preconditionCheck(); + + return index != elementData.end; + } + + @Override + public E next() { + preconditionCheck(); + + if (index == elementData.end) { + throw new NoSuchElementException(); + } + + return elementData.get(index++); + } + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 41db4cbefa..1137de8d58 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -26,18 +26,21 @@ import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtReferenceModel import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId +import soot.ArrayType import soot.Scene +import soot.SootClass +import soot.SootField import soot.SootMethod +import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +import sun.reflect.generics.reflectiveObjects.TypeVariableImpl +import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl val rangeModifiableArrayId: ClassId = RangeModifiableUnlimitedArray::class.id class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { - private val rangeModifiableArrayClass = Scene.v().getSootClass(rangeModifiableArrayId.name) - private val beginField = rangeModifiableArrayClass.getField("int begin") - private val endField = rangeModifiableArrayClass.getField("int end") - private val storageField = rangeModifiableArrayClass.getField("java.lang.Object[] storage") - override fun UtBotSymbolicEngine.invoke( wrapper: ObjectValue, method: SootMethod, @@ -168,13 +171,15 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { val typeStorage = typeResolver.constructTypeStorage(OBJECT_TYPE.arrayType, useConcreteType = false) val array = ArrayValue(typeStorage, arrayAddr) + val hardConstraints = setOf( + Eq(memory.findArrayLength(arrayAddr), length), + typeRegistry.typeConstraint(arrayAddr, array.typeStorage).all(), + ).asHardConstraint() + listOf( MethodResult( SymbolicSuccess(array), - hardConstraints = setOf( - Eq(memory.findArrayLength(arrayAddr), length), - typeRegistry.typeConstraint(array.addr, array.typeStorage).all() - ).asHardConstraint(), + hardConstraints = hardConstraints, memoryUpdates = arrayUpdateWithValue(arrayAddr, OBJECT_TYPE.arrayType, value) ) ) @@ -233,7 +238,7 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { val resultModel = UtArrayModel( concreteAddr, - objectClassId, + objectArrayClassId, sizeValue, UtNullModel(objectClassId), mutableMapOf() @@ -243,17 +248,34 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { // the constructed model to avoid infinite recursion below resolver.addConstructedModel(concreteAddr, resultModel) + val typeStorage = resolver.typeRegistry.genericTypeStorageByAddr[wrapper.addr]?.singleOrNull() ?: TypeStorage(OBJECT_TYPE) + + (0 until sizeValue).associateWithTo(resultModel.stores) { i -> - resolver.resolveModel( - ObjectValue( - TypeStorage(OBJECT_TYPE), - UtAddrExpression(arrayExpression.select(mkInt(i + firstValue))) - ) - ) + val addr = UtAddrExpression(arrayExpression.select(mkInt(i + firstValue))) + + val value = if (typeStorage.leastCommonType is ArrayType) { + ArrayValue(typeStorage, addr) + } else { + ObjectValue(typeStorage, addr) + } + + resolver.resolveModel(value) } return resultModel } + + companion object { + internal val rangeModifiableArrayClass: SootClass + get() = Scene.v().getSootClass(rangeModifiableArrayId.name) + internal val beginField: SootField + get() = rangeModifiableArrayClass.getFieldByName("begin") + internal val endField: SootField + get() = rangeModifiableArrayClass.getFieldByName("end") + internal val storageField: SootField + get() = rangeModifiableArrayClass.getFieldByName("storage") + } } val associativeArrayId: ClassId = AssociativeArray::class.id diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index 3b74f25a60..dc2dadeba6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -1,15 +1,24 @@ package org.utbot.engine +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf import org.utbot.common.unreachableBranch +import org.utbot.engine.CommonStreamWrapper.Companion.isClosedStreamFieldId +import org.utbot.engine.CommonStreamWrapper.Companion.isParallelStreamFieldId +import org.utbot.engine.CommonStreamWrapper.Companion.utStreamType import org.utbot.engine.overrides.collections.AssociativeArray +import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray import org.utbot.engine.overrides.collections.UtArrayList import org.utbot.engine.overrides.collections.UtGenericAssociative import org.utbot.engine.overrides.collections.UtGenericStorage import org.utbot.engine.overrides.collections.UtHashMap import org.utbot.engine.overrides.collections.UtHashSet import org.utbot.engine.overrides.collections.UtLinkedList +import org.utbot.engine.overrides.stream.UtStream import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtExpression +import org.utbot.engine.pc.mkEq +import org.utbot.engine.pc.mkFalse import org.utbot.engine.pc.select import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.z3.intValue @@ -33,7 +42,6 @@ import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.util.nextModelName -import java.util.LinkedList import soot.IntType import soot.RefType import soot.Scene @@ -50,7 +58,8 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * @see invoke */ - protected abstract fun UtBotSymbolicEngine.overrideInvoke( + protected abstract fun overrideInvoke( + engine: UtBotSymbolicEngine, wrapper: ObjectValue, method: SootMethod, parameters: List @@ -74,8 +83,7 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) method: SootMethod, parameters: List ): List { - - val methodResults = overrideInvoke(wrapper, method, parameters) + val methodResults = overrideInvoke(this, wrapper, method, parameters) if (methodResults != null) { return methodResults } @@ -94,25 +102,35 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) } /** - * WrapperInterface that proxies an implementation with methods of [overriddenClass]. + * Wrapper for a particular [java.util.Collection] or [java.util.Map] or [java.util.stream.Stream]. */ -abstract class BaseCollectionWrapper(collectionClassName: String) : BaseOverriddenWrapper(collectionClassName) { +abstract class BaseContainerWrapper(containerClassName: String) : BaseOverriddenWrapper(containerClassName) { /** * Resolve [wrapper] to [UtAssembleModel] using [resolver]. */ override fun value(resolver: Resolver, wrapper: ObjectValue): UtAssembleModel = resolver.run { - val classId = chooseClassIdWithConstructor(wrapper.type.sootClass.id) val addr = holder.concreteAddr(wrapper.addr) val modelName = nextModelName(baseModelName) val parameterModels = resolveValueModels(wrapper) + val classId = chooseClassIdWithConstructor(wrapper.type.sootClass.id) + val instantiationChain = mutableListOf() val modificationsChain = mutableListOf() - return UtAssembleModel(addr, classId, modelName, instantiationChain, modificationsChain) + + UtAssembleModel(addr, classId, modelName, instantiationChain, modificationsChain) .apply { - instantiationChain += UtExecutableCallModel(null, constructorId(classId), emptyList(), this) - modificationsChain += parameterModels.map { UtExecutableCallModel(this, modificationMethodId, it) } + instantiationChain += UtExecutableCallModel( + instance = null, + executable = constructorId(classId), + params = emptyList(), + returnValue = this + ) + + modificationsChain += parameterModels.map { + UtExecutableCallModel(this, modificationMethodId, it) + } } } @@ -140,6 +158,39 @@ abstract class BaseCollectionWrapper(collectionClassName: String) : BaseOverridd override fun toString() = "$overriddenClassName()" } +abstract class BaseGenericStorageBasedContainerWrapper(containerClassName: String) : BaseContainerWrapper(containerClassName) { + override fun overrideInvoke( + engine: UtBotSymbolicEngine, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = + with(engine) { + when (method.signature) { + UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> { + val equalGenericTypeConstraint = typeRegistry + .eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) + .asHardConstraint() + + val methodResult = MethodResult( + SymbolicSuccess(voidValue), + equalGenericTypeConstraint + ) + + listOf(methodResult) + } + else -> null + } + } + + override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { + val elementDataFieldId = FieldId(overriddenClass.type.classId, "elementData") + val arrayModel = collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as? UtArrayModel + + return arrayModel?.let { constructValues(arrayModel, arrayModel.length) } ?: emptyList() + } +} + /** * Auxiliary enum class for specifying an implementation for [ListWrapper], that it will use. */ @@ -165,30 +216,7 @@ enum class UtListClass { * Modification chain consists of consequent [java.util.List.add] methods * that are arranged to iterating order of list. */ -class ListWrapper(private val utListClass: UtListClass) : BaseCollectionWrapper(utListClass.className) { - override fun UtBotSymbolicEngine.overrideInvoke( - wrapper: ObjectValue, - method: SootMethod, - parameters: List - ): List? = - if (method.signature == UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE) { - listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr).asHardConstraint() - ) - ) - } else { - null - } - - override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { - val elementDataFieldId = FieldId(overriddenClass.type.classId, "elementData") - val arrayModel = collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as? UtArrayModel - - return arrayModel?.let { constructValues(arrayModel, arrayModel.length) } ?: emptyList() - } - +class ListWrapper(private val utListClass: UtListClass) : BaseGenericStorageBasedContainerWrapper(utListClass.className) { /** * Chooses proper class for instantiation. Uses [java.util.ArrayList] instead of [java.util.List] * or [java.util.AbstractList]. @@ -203,7 +231,7 @@ class ListWrapper(private val utListClass: UtListClass) : BaseCollectionWrapper( classId = java.util.List::class.id, name = "add", returnType = booleanClassId, - arguments = arrayOf(java.lang.Object::class.id), + arguments = arrayOf(objectClassId), ) override val baseModelName = "list" @@ -221,31 +249,7 @@ class ListWrapper(private val utListClass: UtListClass) : BaseCollectionWrapper( * through [java.util.HashSet] in program and generated test depends on the order of * entries, then real behavior of generated test can differ from expected and undefined. */ -class SetWrapper : BaseCollectionWrapper(UtHashSet::class.qualifiedName!!) { - override fun UtBotSymbolicEngine.overrideInvoke( - wrapper: ObjectValue, - method: SootMethod, - parameters: List - ): List? = - if (method.signature == UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE) { - listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) - .asHardConstraint() - ) - ) - } else { - null - } - - override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { - val elementDataFieldId = FieldId(overriddenClass.type.classId, "elementData") - val arrayModel = collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as? UtArrayModel - - return arrayModel?.let { constructValues(arrayModel, arrayModel.length) } ?: emptyList() - } - +class SetWrapper : BaseGenericStorageBasedContainerWrapper(UtHashSet::class.qualifiedName!!) { /** * Chooses proper class for instantiation. Uses [java.util.ArrayList] instead of [java.util.List] * or [java.util.AbstractList]. @@ -260,7 +264,7 @@ class SetWrapper : BaseCollectionWrapper(UtHashSet::class.qualifiedName!!) { classId = java.util.Set::class.id, name = "add", returnType = booleanClassId, - arguments = arrayOf(java.lang.Object::class.id), + arguments = arrayOf(objectClassId), ) override val baseModelName: String = "set" @@ -278,33 +282,35 @@ class SetWrapper : BaseCollectionWrapper(UtHashSet::class.qualifiedName!!) { * through [java.util.HashMap] in program and generated test depends on the order of * entries, then real behavior of generated test can differ from expected and undefined. */ -class MapWrapper : BaseCollectionWrapper(UtHashMap::class.qualifiedName!!) { - override fun UtBotSymbolicEngine.overrideInvoke( +class MapWrapper : BaseContainerWrapper(UtHashMap::class.qualifiedName!!) { + override fun overrideInvoke( + engine: UtBotSymbolicEngine, wrapper: ObjectValue, method: SootMethod, parameters: List - ): List? { - return when (method.signature) { - UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr).asHardConstraint() + ): List? = + with(engine) { + when (method.signature) { + UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( + MethodResult( + SymbolicSuccess(voidValue), + typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) + .asHardConstraint() + ) ) - ) - UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( - MethodResult( - SymbolicSuccess(voidValue), + UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( + MethodResult( + SymbolicSuccess(voidValue), typeRegistry.eqGenericTypeParametersConstraint( parameters[0].addr, wrapper.addr, parameterSize = 2 ).asHardConstraint() + ) ) - ) - else -> null + else -> null + } } - } - override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { val fieldModels = collectFieldModels(wrapper.addr, overriddenClass.type) val keyModels = fieldModels[overriddenClass.getFieldByName("keys").fieldId] as? UtArrayModel @@ -344,7 +350,7 @@ class MapWrapper : BaseCollectionWrapper(UtHashMap::class.qualifiedName!!) { /** * Constructs collection values using underlying array model. If model is null model, returns list of nulls. */ -private fun constructValues(model: UtModel, size: Int): List> = when (model) { +internal fun constructValues(model: UtModel, size: Int): List> = when (model) { is UtArrayModel -> List(size) { listOf(model.stores[it] ?: model.constModel) } is UtNullModel -> { val elementClassId = model.classId.elementClassId @@ -387,7 +393,7 @@ private fun constructKeysAndValues(keysModel: UtModel, valuesModel: UtModel, siz private val UT_GENERIC_STORAGE_CLASS get() = Scene.v().getSootClass(UtGenericStorage::class.java.canonicalName) -private val UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE = +internal val UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE = UT_GENERIC_STORAGE_CLASS.getMethodByName(UtGenericStorage<*>::setEqualGenericType.name).signature private val UT_GENERIC_ASSOCIATIVE_CLASS @@ -396,10 +402,16 @@ private val UT_GENERIC_ASSOCIATIVE_CLASS private val UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE = UT_GENERIC_ASSOCIATIVE_CLASS.getMethodByName(UtGenericAssociative<*, *>::setEqualGenericType.name).signature +private val COLLECTION_CLASS: SootClass + get() = Scene.v().getSootClass(java.util.Collection::class.java.canonicalName) + +private val UT_COLLECTION_STREAM_SIGNATURE: String = + COLLECTION_CLASS.getMethodByName("stream").signature + val ARRAY_LIST_TYPE: RefType - get() = Scene.v().getSootClass(ArrayList::class.java.canonicalName).type + get() = Scene.v().getSootClass(java.util.ArrayList::class.java.canonicalName).type val LINKED_LIST_TYPE: RefType - get() = Scene.v().getSootClass(LinkedList::class.java.canonicalName).type + get() = Scene.v().getSootClass(java.util.LinkedList::class.java.canonicalName).type val LINKED_HASH_SET_TYPE: RefType get() = Scene.v().getSootClass(java.util.LinkedHashSet::class.java.canonicalName).type @@ -411,6 +423,9 @@ val LINKED_HASH_MAP_TYPE: RefType val HASH_MAP_TYPE: RefType get() = Scene.v().getSootClass(java.util.HashMap::class.java.canonicalName).type +val STREAM_TYPE: RefType + get() = Scene.v().getSootClass(java.util.stream.Stream::class.java.canonicalName).type + internal fun UtBotSymbolicEngine.getArrayField( addr: UtAddrExpression, wrapperClass: SootClass, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index e75556b86b..636c8a7f91 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -351,6 +351,9 @@ class TypeRegistry { private val typeToInheritorsTypes = mutableMapOf>() private val typeToAncestorsTypes = mutableMapOf>() + // TODO docs + val genericTypeStorageByAddr = mutableMapOf>() + // A BiMap containing bijection from every type to an address of the object // presenting its classRef and vise versa private val classRefBiMap = HashBiMap.create() @@ -701,7 +704,24 @@ class TypeRegistry { firstAddr: UtAddrExpression, secondAddr: UtAddrExpression, vararg indexInjection: Pair - ) = UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) + ): UtEqGenericTypeParametersExpression { + // TODO docs + genericTypeStorageByAddr[secondAddr]?.let { existingGenericTypes -> + val currentGenericTypes = mutableMapOf() + + indexInjection.forEach { (from, to) -> + require(from < existingGenericTypes.size) { "TODO" } + currentGenericTypes[to] = existingGenericTypes[from] + } + + genericTypeStorageByAddr[firstAddr] = currentGenericTypes + .entries + .sortedBy { it.key } + .mapTo(mutableListOf()) { it.value } + } + + return UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) + } /** * returns constraint representing that type parameters of an object with address [firstAddr] are equal to @@ -712,7 +732,11 @@ class TypeRegistry { firstAddr: UtAddrExpression, secondAddr: UtAddrExpression, parameterSize: Int - ) = UtEqGenericTypeParametersExpression(firstAddr, secondAddr, (0 until parameterSize).associateWith { it }) + ) : UtEqGenericTypeParametersExpression { + val injections = (0 until parameterSize).associateWith { it }.toList().toTypedArray() + + return eqGenericTypeParametersConstraint(firstAddr, secondAddr, *injections) + } /** * returns constraint representing that the first type parameter of an object with address [firstAddr] are equal to @@ -720,7 +744,7 @@ class TypeRegistry { * @see UtEqGenericTypeParametersExpression */ fun eqGenericSingleTypeParameterConstraint(firstAddr: UtAddrExpression, secondAddr: UtAddrExpression) = - UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(0 to 0)) + eqGenericTypeParametersConstraint(firstAddr, secondAddr, 0 to 0) /** * Returns constraint representing that an object with address [addr] has the same type as the type parameter diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt index 0c6c704faf..ec4152de20 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt @@ -176,8 +176,14 @@ class Mocker( if (!isEngineClass(type) && type.sootClass.isPrivate) return false // could not mock private classes (even if it is in mock always list) if (mockAlways(type)) return true // always mock randoms and loggers if (mockInfo is UtFieldMockInfo) { + val declaringClass = mockInfo.fieldId.declaringClass + + if (Scene.v().getSootClass(declaringClass.name).isArtificialEntity) { + return false // see BaseStreamExample::minExample for an example; cannot load java class for such class + } + return when { - mockInfo.fieldId.declaringClass.packageName.startsWith("java.lang") -> false + declaringClass.packageName.startsWith("java.lang") -> false !mockInfo.fieldId.type.isRefType -> false // mocks are allowed for ref fields only else -> return strategy.eligibleToMock(mockInfo.fieldId.type, classUnderTest) // if we have a field with Integer type, we should not mock it } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index ccbf39e1a1..3a162ed092 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -8,6 +8,7 @@ import org.utbot.engine.UtOptionalClass.UT_OPTIONAL import org.utbot.engine.UtOptionalClass.UT_OPTIONAL_DOUBLE import org.utbot.engine.UtOptionalClass.UT_OPTIONAL_INT import org.utbot.engine.UtOptionalClass.UT_OPTIONAL_LONG +import org.utbot.engine.UtStreamClass.UT_STREAM import org.utbot.engine.overrides.collections.AssociativeArray import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray import org.utbot.engine.overrides.collections.UtHashMap @@ -27,16 +28,16 @@ import org.utbot.framework.plugin.api.util.constructorId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.util.nextModelName +import soot.RefType +import soot.Scene +import soot.SootClass +import soot.SootMethod import java.util.Optional import java.util.OptionalDouble import java.util.OptionalInt import java.util.OptionalLong import java.util.concurrent.CopyOnWriteArrayList import kotlin.reflect.KClass -import soot.RefType -import soot.Scene -import soot.SootClass -import soot.SootMethod typealias TypeToBeWrapped = RefType typealias WrapperType = RefType @@ -81,6 +82,10 @@ val classToWrapper: MutableMap = putSootClass(java.util.AbstractMap::class, UtHashMap::class) putSootClass(java.util.LinkedHashMap::class, UtHashMap::class) putSootClass(java.util.HashMap::class, UtHashMap::class) + + putSootClass(java.util.stream.BaseStream::class, UT_STREAM.className) + putSootClass(java.util.stream.Stream::class, UT_STREAM.className) + // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 } /** @@ -176,7 +181,12 @@ private val wrappers = mapOf( wrap(java.util.Map::class) { _, addr -> objectValue(LINKED_HASH_MAP_TYPE, addr, MapWrapper()) }, wrap(java.util.AbstractMap::class) { _, addr -> objectValue(LINKED_HASH_MAP_TYPE, addr, MapWrapper()) }, wrap(java.util.LinkedHashMap::class) { _, addr -> objectValue(LINKED_HASH_MAP_TYPE, addr, MapWrapper()) }, - wrap(java.util.HashMap::class) { _, addr -> objectValue(HASH_MAP_TYPE, addr, MapWrapper()) } + wrap(java.util.HashMap::class) { _, addr -> objectValue(HASH_MAP_TYPE, addr, MapWrapper()) }, + + // stream wrappers + wrap(java.util.stream.BaseStream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) }, + wrap(java.util.stream.Stream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) }, + // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 ).also { // check every `wrapped` class has a corresponding value in [classToWrapper] it.keys.all { key -> @@ -187,7 +197,7 @@ private val wrappers = mapOf( private fun wrap(kClass: KClass<*>, implementation: (RefType, UtAddrExpression) -> ObjectValue) = kClass.id to implementation -internal fun wrapper(type: RefType, addr: UtAddrExpression) = +internal fun wrapper(type: RefType, addr: UtAddrExpression): ObjectValue? = wrappers[type.id]?.invoke(type, addr) interface WrapperInterface { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt index f806f15305..3db35e0ded 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt @@ -29,7 +29,7 @@ import soot.Scene import soot.SootMethod /** - * Auxiliary enum class for specifying an implementation for [ListWrapper], that it will use. + * Auxiliary enum class for specifying an implementation for [OptionalWrapper], that it will use. */ enum class UtOptionalClass { UT_OPTIONAL, @@ -58,7 +58,8 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri private val AS_OPTIONAL_METHOD_SIGNATURE = overriddenClass.getMethodByName(UtOptional<*>::asOptional.name).signature - override fun UtBotSymbolicEngine.overrideInvoke( + override fun overrideInvoke( + engine: UtBotSymbolicEngine, wrapper: ObjectValue, method: SootMethod, parameters: List @@ -71,7 +72,7 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri return listOf( MethodResult( parameters.first(), - typeRegistry.typeConstraintToGenericTypeParameter( + engine.typeRegistry.typeConstraintToGenericTypeParameter( parameters.first().addr, wrapper.addr, i = 0 diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt new file mode 100644 index 0000000000..55b765b933 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt @@ -0,0 +1,127 @@ +package org.utbot.engine + +import org.utbot.engine.overrides.stream.UtStream +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.util.nextModelName +import soot.BooleanType +import soot.RefType +import soot.Scene +import soot.SootField +import soot.Type + +/** + * Auxiliary enum class for specifying an implementation for [CommonStreamWrapper], that it will use. + */ +// TODO introduce a base interface for all such enums after https://github.com/UnitTestBot/UTBotJava/issues/146? +enum class UtStreamClass { + UT_STREAM; + // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 +// UT_STREAM_INT, +// UT_STREAM_LONG, +// UT_STREAM_DOUBLE; + + val className: String + get() = when (this) { + UT_STREAM -> UtStream::class.java.canonicalName + // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 +// UT_STREAM_INT -> UtStreamInt::class.java.canonicalName +// UT_STREAM_LONG -> UtStreamLong::class.java.canonicalName +// UT_STREAM_DOUBLE -> UtStreamDouble::class.java.canonicalName + } + + val elementClassId: ClassId + get() = when (this) { + UT_STREAM -> objectClassId + // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 +// UT_STREAM_INT -> intClassId +// UT_STREAM_LONG -> longClassId +// UT_STREAM_DOUBLE -> doubleClassId + } + + val overriddenStreamClassId: ClassId + get() = when (this) { + UT_STREAM -> java.util.stream.Stream::class.java.id + // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 + } +} + +abstract class StreamWrapper( + private val utStreamClass: UtStreamClass +) : BaseGenericStorageBasedContainerWrapper(utStreamClass.className) { + override fun value(resolver: Resolver, wrapper: ObjectValue): UtAssembleModel = resolver.run { + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName(baseModelName) + val parametersArrayModel = resolveElementAsArrayModel(wrapper) + + val instantiationChain = mutableListOf() + val modificationsChain = emptyList() + + UtAssembleModel(addr, utStreamClass.overriddenStreamClassId, modelName, instantiationChain, modificationsChain) + .apply { + val (builder, params) = if (parametersArrayModel.length == 0) { + streamEmptyMethodId to emptyList() + } else { + streamOfMethodId to listOf(parametersArrayModel) + } + + instantiationChain += UtExecutableCallModel( + instance = null, + executable = builder, + params = params, + returnValue = this + ) + } + } + + override fun chooseClassIdWithConstructor(classId: ClassId): ClassId = error("No constructor for Stream") + + override val modificationMethodId: MethodId + get() = error("No modification method for Stream") + + private fun Resolver.resolveElementAsArrayModel(wrapper: ObjectValue): UtArrayModel { + val elementDataFieldId = FieldId(overriddenClass.type.classId, "elementData") + + return collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as UtArrayModel + } + + private val streamOfMethodId: MethodId = methodId( + classId = utStreamClass.overriddenStreamClassId, + name = "of", + returnType = utStreamClass.overriddenStreamClassId, + arguments = arrayOf(objectArrayClassId) // vararg + ) + + private val streamEmptyMethodId: MethodId = methodId( + classId = utStreamClass.overriddenStreamClassId, + name = "empty", + returnType = utStreamClass.overriddenStreamClassId, + arguments = emptyArray() + ) +} + +class CommonStreamWrapper : StreamWrapper(UtStreamClass.UT_STREAM) { + override val baseModelName: String = "stream" + + companion object { + internal val utStreamType: RefType + get() = Scene.v().getSootClass(UtStream::class.java.canonicalName).type + private val booleanType: Type + get() = BooleanType.v() + + internal val isParallelStreamFieldId: SootField + get() = SootField("isParallel", booleanType) + internal val isClosedStreamFieldId: SootField + get() = SootField("isClosed", booleanType) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt index 80610efa1a..0cfe3012c1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt @@ -67,78 +67,85 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { private fun UtBotSymbolicEngine.getValueArray(addr: UtAddrExpression) = getArrayField(addr, overriddenClass, STRING_VALUE) - override fun UtBotSymbolicEngine.overrideInvoke( + override fun overrideInvoke( + engine: UtBotSymbolicEngine, wrapper: ObjectValue, method: SootMethod, parameters: List - ): List? { - when (method.subSignature) { - toStringMethodSignature -> { - return listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) - } - matchesMethodSignature -> { - val arg = parameters[0] as ObjectValue - val matchingLengthExpr = getIntFieldValue(arg, STRING_LENGTH).accept(RewritingVisitor()) + ): List? = + with(engine) { + when (method.subSignature) { + toStringMethodSignature -> { + return listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) + } + matchesMethodSignature -> { + val arg = parameters[0] as ObjectValue + val matchingLengthExpr = getIntFieldValue(arg, STRING_LENGTH).accept(RewritingVisitor()) - if (!matchingLengthExpr.isConcrete) return null + if (!matchingLengthExpr.isConcrete) return null - val matchingValueExpr = selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) - val matchingLength = matchingLengthExpr.toConcrete() as Int - val matchingValue = CharArray(matchingLength) + val matchingValueExpr = + selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) + val matchingLength = matchingLengthExpr.toConcrete() as Int + val matchingValue = CharArray(matchingLength) - for (i in 0 until matchingLength) { - val charExpr = matchingValueExpr.select(mkInt(i)).accept(RewritingVisitor()) + for (i in 0 until matchingLength) { + val charExpr = matchingValueExpr.select(mkInt(i)).accept(RewritingVisitor()) - if (!charExpr.isConcrete) return null + if (!charExpr.isConcrete) return null - matchingValue[i] = (charExpr.toConcrete() as Number).toChar() - } + matchingValue[i] = (charExpr.toConcrete() as Number).toChar() + } - val rgxGen = RgxGen(String(matchingValue)) - val matching = (rgxGen.generate()) - val notMatching = rgxGen.generateNotMatching() + val rgxGen = RgxGen(String(matchingValue)) + val matching = (rgxGen.generate()) + val notMatching = rgxGen.generateNotMatching() - val thisLength = getIntFieldValue(wrapper, STRING_LENGTH) - val thisValue = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) + val thisLength = getIntFieldValue(wrapper, STRING_LENGTH) + val thisValue = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) - val matchingConstraints = mutableSetOf() - matchingConstraints += mkEq(thisLength, mkInt(matching.length)) - for (i in matching.indices) { - matchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(matching[i])) - } + val matchingConstraints = mutableSetOf() + matchingConstraints += mkEq(thisLength, mkInt(matching.length)) + for (i in matching.indices) { + matchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(matching[i])) + } - val notMatchingConstraints = mutableSetOf() - notMatchingConstraints += mkEq(thisLength, mkInt(notMatching.length)) - for (i in notMatching.indices) { - notMatchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(notMatching[i])) - } + val notMatchingConstraints = mutableSetOf() + notMatchingConstraints += mkEq(thisLength, mkInt(notMatching.length)) + for (i in notMatching.indices) { + notMatchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(notMatching[i])) + } - return listOf( - MethodResult(UtTrue.toBoolValue(), matchingConstraints.asHardConstraint()), - MethodResult(UtFalse.toBoolValue(), notMatchingConstraints.asHardConstraint()) - ) - } - charAtMethodSignature -> { - val index = parameters[0] as PrimitiveValue - val lengthExpr = getIntFieldValue(wrapper, STRING_LENGTH) - val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) - val failMethodResult = - MethodResult( - explicitThrown(StringIndexOutOfBoundsException(), findNewAddr(), isInNestedMethod()), - hardConstraints = mkNot(inBoundsCondition).asHardConstraint() + return listOf( + MethodResult(UtTrue.toBoolValue(), matchingConstraints.asHardConstraint()), + MethodResult(UtFalse.toBoolValue(), notMatchingConstraints.asHardConstraint()) ) - - val valueExpr = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) - - val returnResult = MethodResult( - valueExpr.select(index.expr).toCharValue(), - hardConstraints = inBoundsCondition.asHardConstraint() - ) - return listOf(returnResult, failMethodResult) + } + charAtMethodSignature -> { + val index = parameters[0] as PrimitiveValue + val lengthExpr = getIntFieldValue(wrapper, STRING_LENGTH) + val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) + val failMethodResult = + MethodResult( + explicitThrown( + StringIndexOutOfBoundsException(), + findNewAddr(), + isInNestedMethod() + ), + hardConstraints = mkNot(inBoundsCondition).asHardConstraint() + ) + + val valueExpr = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) + + val returnResult = MethodResult( + valueExpr.select(index.expr).toCharValue(), + hardConstraints = inBoundsCondition.asHardConstraint() + ) + return listOf(returnResult, failMethodResult) + } + else -> return null } - else -> return null } - } override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = resolver.run { val classId = STRING_TYPE.id @@ -287,7 +294,8 @@ sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenW private val asStringBuilderMethodSignature = overriddenClass.getMethodByName("asStringBuilder").subSignature - override fun UtBotSymbolicEngine.overrideInvoke( + override fun overrideInvoke( + engine: UtBotSymbolicEngine, wrapper: ObjectValue, method: SootMethod, parameters: List diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 9ba79b1b34..0e14865ccc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -1360,6 +1360,7 @@ class UtBotSymbolicEngine( queuedSymbolicStateUpdates += typeRegistry.genericTypeParameterConstraint(value.addr, typeStorages).asHardConstraint() parameterAddrToGenericType += value.addr to type + typeRegistry.genericTypeStorageByAddr += value.addr to typeStorages } } @@ -3470,18 +3471,10 @@ class UtBotSymbolicEngine( } } - val numDimensions = typeAfterCast.numDimensions - val inheritors = if (baseTypeAfterCast is PrimType) { - setOf(typeAfterCast) - } else { - typeResolver - .findOrConstructInheritorsIncludingTypes(baseTypeAfterCast as RefType) - .mapTo(mutableSetOf()) { if (numDimensions > 0) it.makeArrayType(numDimensions) else it } - } - val preferredTypesForCastException = valueToCast.possibleConcreteTypes.filterNot { it in inheritors } + val inheritorsTypeStorage = typeResolver.constructTypeStorage(typeAfterCast, useConcreteType = false) + val preferredTypesForCastException = valueToCast.possibleConcreteTypes.filterNot { it in inheritorsTypeStorage.possibleConcreteTypes } - val typeStorage = typeResolver.constructTypeStorage(typeAfterCast, inheritors) - val isExpression = typeRegistry.typeConstraint(addr, typeStorage).isConstraint() + val isExpression = typeRegistry.typeConstraint(addr, inheritorsTypeStorage).isConstraint() val notIsExpression = mkNot(isExpression) val nullEqualityConstraint = addrEq(addr, nullObjectAddr) @@ -3715,7 +3708,10 @@ class UtBotSymbolicEngine( // Still, we need overflows to act as implicit exceptions. (UtSettings.treatOverflowAsError && symbolicExecutionResult is UtOverflowFailure) ) { - logger.debug { "processResult<${methodUnderTest}>: no concrete execution allowed, emit purely symbolic result" } + logger.debug { + "processResult<${methodUnderTest}>: no concrete execution allowed, " + + "emit purely symbolic result $symbolicUtExecution" + } emit(symbolicUtExecution) return } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt index 4718f673c6..31fe3591a8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt @@ -226,7 +226,6 @@ open class Z3TranslatorVisitor( override fun visit(expr: UtGenericExpression): Expr = expr.run { val constraints = mutableListOf() for (i in types.indices) { - val symNumDimensions = translate(typeRegistry.genericNumDimensions(addr, i)) as BitVecExpr val symType = translate(typeRegistry.genericTypeId(addr, i)) if (types[i].leastCommonType.isJavaLangObject()) { @@ -245,20 +244,6 @@ open class Z3TranslatorVisitor( ) constraints += typeConstraint - val numDimensionsConstraint = z3Context.mkAnd( - *possibleBaseTypes.map { - val numDimensions = z3Context.mkBV(it.numDimensions, Int.SIZE_BITS) - - if (it.isJavaLangObject()) { - z3Context.mkBVSGE(symNumDimensions, numDimensions) - } else { - z3Context.mkEq(symNumDimensions, numDimensions) - } - }.toTypedArray() - ) - - constraints += numDimensionsConstraint - z3Context.mkAnd(*constraints.toTypedArray()) } z3Context.mkOr( 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 28210145bf..9169e42c44 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 @@ -33,6 +33,7 @@ internal val ClassId.possibleUtilMethodIds: Set deepEqualsMethodId, arraysDeepEqualsMethodId, iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, mapsDeepEqualsMethodId, hasCustomEqualsMethodId, getArrayLengthMethodId @@ -117,6 +118,13 @@ internal val ClassId.iterablesDeepEqualsMethodId: MethodId 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", 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 e2ae9ccafb..a7c3dc4216 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 @@ -54,6 +54,7 @@ import kotlinx.collections.immutable.PersistentSet import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf +import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId /** * Interface for all code generation context aware entities @@ -351,6 +352,9 @@ internal interface CgContextOwner { val iterablesDeepEquals: MethodId get() = currentTestClass.iterablesDeepEqualsMethodId + val streamsDeepEquals: MethodId + get() = currentTestClass.streamsDeepEqualsMethodId + val mapsDeepEquals: MethodId get() = currentTestClass.mapsDeepEqualsMethodId 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 4bd0e48791..19acd0ba98 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 @@ -26,6 +26,7 @@ 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 @@ -173,6 +174,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA deepEqualsMethodId, arraysDeepEqualsMethodId, iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, mapsDeepEqualsMethodId, hasCustomEqualsMethodId, getArrayLengthMethodId -> emptySet() 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 ed2ab2d6bc..9f7f8b288f 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 @@ -162,8 +162,8 @@ internal class CgTestClassConstructor(val context: CgContext) : */ private fun MethodId.dependencies(): List = when (this) { createInstance -> listOf(getUnsafeInstance) - deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, mapsDeepEquals, hasCustomEquals) - arraysDeepEquals, iterablesDeepEquals, mapsDeepEquals -> listOf(deepEquals) + deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals, hasCustomEquals) + arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals -> listOf(deepEquals) else -> emptyList() } 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 b671edef18..7b8ed610b9 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 @@ -9,6 +9,7 @@ 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 @@ -103,6 +104,7 @@ internal abstract class TestFrameworkManager(val context: CgContext) requiredUtilMethods += currentTestClass.deepEqualsMethodId requiredUtilMethods += currentTestClass.arraysDeepEqualsMethodId requiredUtilMethods += currentTestClass.iterablesDeepEqualsMethodId + requiredUtilMethods += currentTestClass.streamsDeepEqualsMethodId requiredUtilMethods += currentTestClass.mapsDeepEqualsMethodId requiredUtilMethods += currentTestClass.hasCustomEqualsMethodId 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 3e3ce86325..a0a430507d 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,6 +15,7 @@ import org.utbot.framework.codegen.model.constructor.builtin.iterablesDeepEquals 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.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.importIfNeeded @@ -42,6 +43,7 @@ internal fun ClassId.utilMethodById(id: MethodId, context: CgContext): String = deepEqualsMethodId -> deepEquals(codegenLanguage, mockFrameworkUsed, mockFramework) arraysDeepEqualsMethodId -> arraysDeepEquals(codegenLanguage) iterablesDeepEqualsMethodId -> iterablesDeepEquals(codegenLanguage) + streamsDeepEqualsMethodId -> streamsDeepEquals(codegenLanguage) mapsDeepEqualsMethodId -> mapsDeepEquals(codegenLanguage) hasCustomEqualsMethodId -> hasCustomEquals(codegenLanguage) getArrayLengthMethodId -> getArrayLength(codegenLanguage) @@ -433,6 +435,18 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew 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)) { @@ -513,6 +527,12 @@ fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramew } if (o2 is kotlin.collections.Iterable<*>) return false + + if (o1 is java.util.stream.Stream<*>) { + return if (o2 !is java.util.stream.Stream<*>) false else streamsDeepEquals(o1, o2, visited) + } + + if (o2 is java.util.stream.Stream<*>) return false if (o1 is kotlin.collections.Map<*, *>) { return if (o2 !is kotlin.collections.Map<*, *>) false else mapsDeepEquals(o1, o2, visited) @@ -646,6 +666,50 @@ fun iterablesDeepEquals(language: CodegenLanguage): String = } } +fun streamsDeepEquals(language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + private 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(); + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + private fun streamsDeepEquals( + s1: java.util.stream.Stream<*>, + s2: java.util.stream.Stream<*>, + visited: kotlin.collections.MutableSet> + ): Boolean { + val firstIterator = s1.iterator() + val secondIterator = s2.iterator() + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) return false + } + + return if (firstIterator.hasNext()) false else !secondIterator.hasNext() + } + """.trimIndent() + } + } + fun mapsDeepEquals(language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { @@ -796,6 +860,10 @@ private fun ClassId.regularImportsByUtilMethod(id: MethodId, codegenLanguage: Co CodegenLanguage.JAVA -> listOf(Iterable::class.id, Iterator::class.id, Set::class.id) CodegenLanguage.KOTLIN -> listOf() } + streamsDeepEqualsMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id, Set::class.id) + CodegenLanguage.KOTLIN -> listOf() + } mapsDeepEqualsMethodId -> when (codegenLanguage) { CodegenLanguage.JAVA -> listOf(Map::class.id, Iterator::class.id, Set::class.id) CodegenLanguage.KOTLIN -> listOf() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt index d040055de7..352af4b4bf 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt @@ -25,11 +25,14 @@ import org.utbot.engine.overrides.collections.UtHashMap import org.utbot.engine.overrides.collections.UtHashSet import org.utbot.engine.overrides.collections.UtLinkedList import org.utbot.engine.overrides.UtOverrideMock +import org.utbot.engine.overrides.collections.Collection import org.utbot.engine.overrides.collections.UtGenericStorage import org.utbot.engine.overrides.collections.UtOptional import org.utbot.engine.overrides.collections.UtOptionalDouble import org.utbot.engine.overrides.collections.UtOptionalInt import org.utbot.engine.overrides.collections.UtOptionalLong +import org.utbot.engine.overrides.stream.Stream +import org.utbot.engine.overrides.stream.UtStream import java.io.File import java.nio.file.Path import kotlin.reflect.KClass @@ -127,5 +130,9 @@ private val classesToLoad = arrayOf( UtNativeStringWrapper::class, UtString::class, UtStringBuilder::class, - UtStringBuffer::class + UtStringBuffer::class, + Stream::class, + Collection::class, + UtStream::class, + UtStream.UtStreamIterator::class ) \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt index e2318ae026..b329d79f91 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt @@ -2564,7 +2564,7 @@ internal fun invokeMatcher(matcher: Function, params: List) = whe fun ge(count: Int) = ExecutionsNumberMatcher("ge $count") { it >= count } fun eq(count: Int) = ExecutionsNumberMatcher("eq $count") { it == count } fun between(bounds: IntRange) = ExecutionsNumberMatcher("$bounds") { it in bounds } -val ignoreExecutionsNumber = ExecutionsNumberMatcher("Do not calculate") { true } +val ignoreExecutionsNumber = ExecutionsNumberMatcher("Do not calculate") { it > 0 } fun atLeast(percents: Int) = AtLeast(percents) @@ -2619,6 +2619,17 @@ class AtLeast(percents: Int) : CoverageMatcher("at least $percents% coverage", object DoNotCalculate : CoverageMatcher("Do not calculate", { true }) +class FullWithAssumptions(assumeCallsNumber: Int) : CoverageMatcher( + "full coverage except failed assume calls", + { it.instructionCounter.let { it.covered >= it.total - assumeCallsNumber } } +) { + init { + require(assumeCallsNumber > 0) { + "Non-positive number of assume calls $assumeCallsNumber passed (for zero calls use Full coverage matcher" + } + } +} + // simple matchers fun withResult(ex: UtValueExecution<*>) = ex.paramsBefore + ex.evaluatedResult fun withException(ex: UtValueExecution<*>) = ex.paramsBefore + ex.returnValue @@ -2704,7 +2715,7 @@ fun keyMatch(keyStmt: List) = { summary: List? -> fun Map>.findByName(name: String) = entries.single { name == it.key.name }.value.value fun Map>.singleValue() = values.single().value -private typealias StaticsType = Map> +internal typealias StaticsType = Map> private typealias Mocks = List private typealias Instrumentation = List diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt new file mode 100644 index 0000000000..62c63263b4 --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.examples.AbstractTestCaseGeneratorTest +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.eq +import org.utbot.examples.isException + +class SimpleLambdaExamplesTest : AbstractTestCaseGeneratorTest(testClass = SimpleLambdaExamples::class) { + @Test + fun testBiFunctionLambdaExample() { + checkWithException( + SimpleLambdaExamples::biFunctionLambdaExample, + eq(2), + { a, b, r -> b == 0 && r.isException() }, + { a, b, r -> b != 0 && r.getOrThrow() == a / b }, + ) + } + + @Test + @Disabled("TODO 0 executions https://github.com/UnitTestBot/UTBotJava/issues/192") + fun testChoosePredicate() { + check( + SimpleLambdaExamples::choosePredicate, + eq(2), + { b, r -> b && !r!!.test(null) && r.test(0) }, + { b, r -> !b && r!!.test(null) && !r.test(0) }, + // TODO coverage calculation fails https://github.com/UnitTestBot/UTBotJava/issues/192 + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt new file mode 100644 index 0000000000..a73a2d1ebe --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt @@ -0,0 +1,372 @@ +package org.utbot.examples.stream + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.examples.AbstractTestCaseGeneratorTest +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.Full +import org.utbot.examples.FullWithAssumptions +import org.utbot.examples.StaticsType +import org.utbot.examples.eq +import org.utbot.examples.ignoreExecutionsNumber +import org.utbot.examples.isException +import org.utbot.examples.withoutConcrete +import org.utbot.framework.codegen.CodeGeneration +import org.utbot.framework.plugin.api.CodegenLanguage +import java.util.Optional +import java.util.stream.Stream +import kotlin.streams.toList + +// TODO 1 instruction is always uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 +// TODO failed Kotlin compilation (generics) JIRA:1332 +class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( + testClass = BaseStreamExample::class, + testCodeGeneration = true, + languagePipelines = listOf( + CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), + CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) + ) +) { + @Test + fun testReturningStreamExample() { + withoutConcrete { + check( + BaseStreamExample::returningStreamExample, + eq(2), + // NOTE: the order of the matchers is important because Stream could be used only once + { c, r -> c.isNotEmpty() && c == r!!.toList() }, + { c, r -> c.isEmpty() && c == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testFilterExample() { + check( + BaseStreamExample::filterExample, + ignoreExecutionsNumber, + { c, r -> null !in c && r == false }, + { c, r -> null in c && r == true }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapExample() { + checkWithException( + BaseStreamExample::mapExample, + eq(2), + { c, r -> null in c && r.isException() }, + { c, r -> r.getOrThrow().contentEquals(c.map { it * 2 }.toTypedArray()) }, + coverage = DoNotCalculate + ) + } + + @Test + fun testFlatMapExample() { + check( + BaseStreamExample::flatMapExample, + ignoreExecutionsNumber, + { c, r -> r.contentEquals(c.flatMap { listOf(it, it) }.toTypedArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testDistinctExample() { + check( + BaseStreamExample::distinctExample, + ignoreExecutionsNumber, + { c, r -> c == c.distinct() && r == false }, + { c, r -> c != c.distinct() && r == true }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + @Tag("slow") + // TODO slow sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + fun testSortedExample() { + check( + BaseStreamExample::sortedExample, + ignoreExecutionsNumber, + { c, r -> c.last() < c.first() && r!!.asSequence().isSorted() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testPeekExample() { + checkThisAndStaticsAfter( + BaseStreamExample::peekExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testLimitExample() { + check( + BaseStreamExample::limitExample, + ignoreExecutionsNumber, + { c, r -> c.size <= 5 && c.toTypedArray().contentEquals(r) }, + { c, r -> c.size > 5 && c.take(5).toTypedArray().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSkipExample() { + check( + BaseStreamExample::skipExample, + ignoreExecutionsNumber, + { c, r -> c.size > 5 && c.drop(5).toTypedArray().contentEquals(r) }, + { c, r -> c.size <= 5 && r!!.isEmpty() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testForEachExample() { + checkThisAndStaticsAfter( + BaseStreamExample::forEachExample, + eq(2), + *streamConsumerStaticsMatchers, + coverage = DoNotCalculate + ) + } + + @Test + fun testToArrayExample() { + check( + BaseStreamExample::toArrayExample, + ignoreExecutionsNumber, + { c, r -> c.toTypedArray().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testReduceExample() { + check( + BaseStreamExample::reduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 42 }, + { c, r -> c.isNotEmpty() && r == c.sum() + 42 }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testOptionalReduceExample() { + checkWithException( + BaseStreamExample::optionalReduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == Optional.empty() }, + { c, r -> c.isNotEmpty() && c.single() == null && r.isException() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == Optional.of(c.sum()) }, + coverage = DoNotCalculate // TODO 2 instructions are uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 + ) + } + + @Test + fun testComplexReduceExample() { + check( + BaseStreamExample::complexReduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && c.sumOf { it.toDouble() } + 42.0 == r }, + { c: List, r -> c.isNotEmpty() && c.sumOf { it?.toDouble() ?: 0.0 } + 42.0 == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + @Disabled("TODO zero executions https://github.com/UnitTestBot/UTBotJava/issues/207") + fun testCollectorExample() { + check( + BaseStreamExample::collectorExample, + ignoreExecutionsNumber, + { c, r -> c.toSet() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testCollectExample() { + checkWithException( + BaseStreamExample::collectExample, + ignoreExecutionsNumber, // 3 executions instead of 2 expected + { c, r -> null in c && r.isException() }, + { c, r -> null !in c && c.sum() == r.getOrThrow() }, + coverage = DoNotCalculate // TODO 2 instructions are uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 + ) + } + + @Test + fun testMinExample() { + checkWithException( + BaseStreamExample::minExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == Optional.empty() }, + { c, r -> c.isNotEmpty() && c.all { it == null } && r.isException() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == Optional.of(c.minOrNull()!!) }, + coverage = DoNotCalculate // TODO 2 instructions are uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 + ) + } + + @Test + fun testMaxExample() { + checkWithException( + BaseStreamExample::maxExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == Optional.empty() }, + { c, r -> c.isNotEmpty() && c.all { it == null } && r.isException() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == Optional.of(c.maxOrNull()!!) }, + coverage = DoNotCalculate // TODO 2 instructions are uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 + ) + } + + @Test + fun testCountExample() { + check( + BaseStreamExample::countExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.size.toLong() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAnyMatchExample() { + check( + BaseStreamExample::anyMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == false }, + { c, r -> c.isNotEmpty() && c.all { it == null } && r == true }, + { c, r -> c.isNotEmpty() && c.first() != null && c.last() == null && r == true }, + { c, r -> c.isNotEmpty() && c.first() == null && c.last() != null && r == true }, + { c, r -> c.isNotEmpty() && c.none { it == null } && r == false }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testAllMatchExample() { + check( + BaseStreamExample::allMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.all { it == null } && r == true }, + { c, r -> c.isNotEmpty() && c.first() != null && c.last() == null && r == false }, + { c, r -> c.isNotEmpty() && c.first() == null && c.last() != null && r == false }, + { c, r -> c.isNotEmpty() && c.none { it == null } && r == false }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testNoneMatchExample() { + check( + BaseStreamExample::noneMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.all { it == null } && r == false }, + { c, r -> c.isNotEmpty() && c.first() != null && c.last() == null && r == false }, + { c, r -> c.isNotEmpty() && c.first() == null && c.last() != null && r == false }, + { c, r -> c.isNotEmpty() && c.none { it == null } && r == true }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testFindFirstExample() { + checkWithException( + BaseStreamExample::findFirstExample, + eq(3), + { c, r -> c.isEmpty() && r.getOrThrow() == Optional.empty() }, + { c: List, r -> c.isNotEmpty() && c.first() == null && r.isException() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == Optional.of(c.first()) }, + coverage = DoNotCalculate + ) + } + + @Test + fun testIteratorExample() { + checkWithException( + BaseStreamExample::iteratorSumExample, + eq(2), + { c, r -> null in c && r.isException() }, + { c, r -> null !in c && r.getOrThrow() == c.sum() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testStreamOfExample() { + withoutConcrete { + check( + BaseStreamExample::streamOfExample, + ignoreExecutionsNumber, + // NOTE: the order of the matchers is important because Stream could be used only once + { c, r -> c.isNotEmpty() && c.contentEquals(r!!.toArray()) }, + { c, r -> c.isEmpty() && Stream.empty().toArray().contentEquals(r!!.toArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testClosedStreamExample() { + checkWithException( + BaseStreamExample::closedStreamExample, + eq(1), + { _, r -> r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testGenerateExample() { + check( + BaseStreamExample::generateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(Array(10) { 42 }) }, + coverage = Full + ) + } + + @Test + fun testIterateExample() { + check( + BaseStreamExample::iterateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(Array(10) { i -> 42 + i }) }, + coverage = Full + ) + } + + @Test + fun testConcatExample() { + check( + BaseStreamExample::concatExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(Array(10) { 42 } + Array(10) { i -> 42 + i }) }, + coverage = Full + ) + } + + private val streamConsumerStaticsMatchers = arrayOf( + { _: BaseStreamExample, c: List, _: StaticsType, _: Int? -> null in c }, + { _: BaseStreamExample, c: List, statics: StaticsType, r: Int? -> + val x = statics.values.single().value as Int + + r!! + c.sumOf { it ?: 0 } == x + } + ) +} + +private fun > Sequence.isSorted(): Boolean = zipWithNext { a, b -> a <= b }.all { it } diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/SimpleLambdaExamples.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/SimpleLambdaExamples.java new file mode 100644 index 0000000000..c74a5f60dd --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/SimpleLambdaExamples.java @@ -0,0 +1,21 @@ +package org.utbot.examples.lambda; + +import java.util.function.BiFunction; +import java.util.function.Predicate; + +public class SimpleLambdaExamples { + public int biFunctionLambdaExample(int a, int b) { + BiFunction division = (numerator, divisor) -> numerator / divisor; + + return division.apply(a, b); + } + + @SuppressWarnings("Convert2MethodRef") + public Predicate choosePredicate(boolean isNotNullPredicate) { + if (isNotNullPredicate) { + return (o -> o != null); + } else { + return (o -> o == null); + } + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java new file mode 100644 index 0000000000..5efd151ef9 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java @@ -0,0 +1,458 @@ +package org.utbot.examples.stream; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@SuppressWarnings({"IfStatementWithIdenticalBranches", "RedundantOperationOnEmptyContainer"}) +public class BaseStreamExample { + Stream returningStreamExample(List list) { + UtMock.assume(list != null); + + if (list.isEmpty()) { + return list.stream(); + } else { + return list.stream(); + } + } + + @SuppressWarnings("Convert2MethodRef") + boolean filterExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + int newSize = list.stream().filter(value -> value != null).toArray().length; + + return prevSize != newSize; + } + + Integer[] mapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final Function mapper = value -> value * 2; + if (list.contains(null)) { + return list.stream().map(mapper).toArray(Integer[]::new); + } else { + return list.stream().map(mapper).toArray(Integer[]::new); + } + } + + // TODO mapToInt, etc https://github.com/UnitTestBot/UTBotJava/issues/146 + + Object[] flatMapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + return list.stream().flatMap(value -> Arrays.stream(new Object[]{value, value})).toArray(Object[]::new); + } + + boolean distinctExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + int newSize = list.stream().distinct().toArray().length; + + return prevSize != newSize; + } + + Integer[] sortedExample(List list) { + UtMock.assume(list != null && list.size() >= 2); + + Integer first = list.get(0); + + int lastIndex = list.size() - 1; + Integer last = list.get(lastIndex); + + UtMock.assume(last < first); + + return list.stream().sorted().toArray(Integer[]::new); + } + + // TODO sorted with custom Comparator + + static int x = 0; + + @SuppressWarnings("ResultOfMethodCallIgnored") + int peekExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final Consumer action = value -> x += value; + if (list.contains(null)) { + list.stream().peek(action); + } else { + list.stream().peek(action); + } + + return beforeStaticValue; + } + + @SuppressWarnings("IfStatementWithIdenticalBranches") + Integer[] limitExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + if (list.size() <= 5) { + return list.stream().limit(5).toArray(Integer[]::new); + } else { + return list.stream().limit(5).toArray(Integer[]::new); + } + } + + @SuppressWarnings("IfStatementWithIdenticalBranches") + Integer[] skipExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + if (list.size() > 5) { + return list.stream().skip(5).toArray(Integer[]::new); + } else { + return list.stream().skip(5).toArray(Integer[]::new); + } + } + + @SuppressWarnings("SimplifyStreamApiCallChains") + int forEachExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final Consumer action = value -> x += value; + if (list.contains(null)) { + list.stream().forEach(action); + } else { + list.stream().forEach(action); + } + + return beforeStaticValue; + } + + @SuppressWarnings("SimplifyStreamApiCallChains") + Object[] toArrayExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + if (size <= 1) { + return list.stream().toArray(); + } else { + return list.stream().toArray(Integer[]::new); + } + } + + Integer reduceExample(List list) { + UtMock.assume(list != null); + + final int identity = 42; + if (list.isEmpty()) { + return list.stream().reduce(identity, this::nullableSum); + } else { + return list.stream().reduce(identity, this::nullableSum); + } + } + + Optional optionalReduceExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + if (size == 0) { + return list.stream().reduce(this::nullableSum); + } + + if (size == 1 && list.get(0) == null) { + return list.stream().reduce(this::nullableSum); + } + + return list.stream().reduce(this::nullableSum); + } + + Double complexReduceExample(List list) { + UtMock.assume(list != null); + + final double identity = 42.0; + final BiFunction accumulator = (Double a, Integer b) -> a + (b != null ? b.doubleValue() : 0.0); + if (list.isEmpty()) { + return list.stream().reduce(identity, accumulator, Double::sum); + } + + // TODO this branch leads to almost infinite analysis +// if (list.contains(null)) { +// return list.stream().reduce(42.0, (Double a, Integer b) -> a + b.doubleValue(), Double::sum); +// } + + return list.stream().reduce( + identity, + accumulator, + Double::sum + ); + } + + Integer collectExample(List list) { + UtMock.assume(list != null); + + if (list.contains(null)) { + return list.stream().collect(IntWrapper::new, IntWrapper::plus, IntWrapper::plus).value; + } else { + return list.stream().collect(IntWrapper::new, IntWrapper::plus, IntWrapper::plus).value; + } + } + + @SuppressWarnings("SimplifyStreamApiCallChains") + Set collectorExample(List list) { + UtMock.assume(list != null); + + return list.stream().collect(Collectors.toSet()); + } + + @SuppressWarnings("RedundantOperationOnEmptyContainer") + Optional minExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + if (size == 0) { + return list.stream().min(this::nullableCompareTo); + } + + if (size == 1 && list.get(0) == null) { + return list.stream().min(this::nullableCompareTo); + } + + return list.stream().min(this::nullableCompareTo); + } + + @SuppressWarnings("RedundantOperationOnEmptyContainer") + Optional maxExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + if (size == 0) { + return list.stream().max(this::nullableCompareTo); + } + + if (size == 1 && list.get(0) == null) { + return list.stream().max(this::nullableCompareTo); + } + + return list.stream().max(this::nullableCompareTo); + } + + @SuppressWarnings({"ReplaceInefficientStreamCount", "ConstantConditions"}) + long countExample(List list) { + UtMock.assume(list != null); + + if (list.isEmpty()) { + return list.stream().count(); + } else { + return list.stream().count(); + } + } + + @SuppressWarnings({"Convert2MethodRef", "ConstantConditions", "RedundantOperationOnEmptyContainer"}) + boolean anyMatchExample(List list) { + UtMock.assume(list != null); + + final Predicate predicate = value -> value == null; + if (list.isEmpty()) { + return list.stream().anyMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Integer first = list.get(0); + Integer second = list.get(1); + + if (first == null && second == null) { + return list.stream().anyMatch(predicate); + } + + if (first == null) { + return list.stream().anyMatch(predicate); + } + + if (second == null) { + return list.stream().anyMatch(predicate); + } + + return list.stream().anyMatch(predicate); + } + + @SuppressWarnings({"Convert2MethodRef", "ConstantConditions", "RedundantOperationOnEmptyContainer"}) + boolean allMatchExample(List list) { + UtMock.assume(list != null); + + final Predicate predicate = value -> value == null; + if (list.isEmpty()) { + return list.stream().allMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Integer first = list.get(0); + Integer second = list.get(1); + + if (first == null && second == null) { + return list.stream().allMatch(predicate); + } + + if (first == null) { + return list.stream().allMatch(predicate); + } + + if (second == null) { + return list.stream().allMatch(predicate); + } + + return list.stream().allMatch(predicate); + } + + @SuppressWarnings({"Convert2MethodRef", "ConstantConditions", "RedundantOperationOnEmptyContainer"}) + boolean noneMatchExample(List list) { + UtMock.assume(list != null); + + final Predicate predicate = value -> value == null; + if (list.isEmpty()) { + return list.stream().noneMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Integer first = list.get(0); + Integer second = list.get(1); + + if (first == null && second == null) { + return list.stream().noneMatch(predicate); + } + + if (first == null) { + return list.stream().noneMatch(predicate); + } + + if (second == null) { + return list.stream().noneMatch(predicate); + } + + return list.stream().noneMatch(predicate); + } + + @SuppressWarnings("RedundantOperationOnEmptyContainer") + Optional findFirstExample(List list) { + UtMock.assume(list != null); + + if (list.isEmpty()) { + return list.stream().findFirst(); + } + + if (list.get(0) == null) { + return list.stream().findFirst(); + } else { + return list.stream().findFirst(); + } + } + + Integer iteratorSumExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int sum = 0; + Iterator streamIterator = list.stream().iterator(); + + if (list.isEmpty()) { + while (streamIterator.hasNext()) { + Integer value = streamIterator.next(); + sum += value; + } + } else { + while (streamIterator.hasNext()) { + Integer value = streamIterator.next(); + sum += value; + } + } + + return sum; + } + + Stream streamOfExample(Integer[] values) { + UtMock.assume(values != null); + + if (values.length == 0) { + return Stream.empty(); + } else { + return Stream.of(values); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + long closedStreamExample(List values) { + UtMock.assume(values != null); + + Stream stream = values.stream(); + stream.count(); + + return stream.count(); + } + + Integer[] generateExample() { + return Stream.generate(() -> 42).limit(10).toArray(Integer[]::new); + } + + Integer[] iterateExample() { + return Stream.iterate(42, x -> x + 1).limit(10).toArray(Integer[]::new); + } + + Integer[] concatExample() { + final int identity = 42; + Stream first = Stream.generate(() -> identity).limit(10); + Stream second = Stream.iterate(identity, x -> x + 1).limit(10); + + return Stream.concat(first, second).toArray(Integer[]::new); + } + + // avoid NPE + private int nullableSum(Integer a, Integer b) { + if (b == null) { + return a; + } + + return a + b; + } + + // avoid NPE + private int nullableCompareTo(Integer a, Integer b) { + if (a == null && b == null) { + return 0; + } + + if (a == null) { + return -1; + } + + if (b == null) { + return 1; + } + + return a.compareTo(b); + } + + private static class IntWrapper { + int value = 0; + + void plus(int other) { + value += other; + } + + void plus(IntWrapper other) { + value += other.value; + } + } +} From 42345394d7d0b0a3c06aa9fe28858afc24dbd100 Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Mon, 20 Jun 2022 18:28:25 +0300 Subject: [PATCH 4/7] Found bug with types --- .../examples/stream/BaseStreamExampleTest.kt | 12 ++ .../examples/stream/BaseStreamExample.java | 130 ++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt index a73a2d1ebe..da94a1b5bd 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt @@ -329,6 +329,18 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( ) } + @Test + @Disabled("TODO unsat type constraints https://github.com/UnitTestBot/UTBotJava/issues/253") + fun testCustomCollectionStreamExample() { + check( + BaseStreamExample::customCollectionStreamExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.size.toLong() == r }, + coverage = DoNotCalculate + ) + } + @Test fun testGenerateExample() { check( diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java index 5efd151ef9..a0b6d749ab 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java @@ -1,8 +1,10 @@ package org.utbot.examples.stream; +import org.jetbrains.annotations.NotNull; import org.utbot.api.mock.UtMock; import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -402,6 +404,25 @@ long closedStreamExample(List values) { return stream.count(); } + @SuppressWarnings({"ReplaceInefficientStreamCount", "ConstantConditions"}) + long customCollectionStreamExample(CustomCollection customCollection) { + UtMock.assume(customCollection != null && customCollection.data != null); + + if (customCollection.isEmpty()) { + return customCollection.stream().count(); + + // simplified example, does not generate branch too + /*customCollection.removeIf(Objects::isNull); + return customCollection.toArray().length;*/ + } else { + return customCollection.stream().count(); + + // simplified example, does not generate branch too + /*customCollection.removeIf(Objects::isNull); + return customCollection.toArray().length;*/ + } + } + Integer[] generateExample() { return Stream.generate(() -> 42).limit(10).toArray(Integer[]::new); } @@ -455,4 +476,113 @@ void plus(IntWrapper other) { value += other.value; } } + + public static class CustomCollection implements Collection { + private E[] data; + + public CustomCollection(@NotNull E[] data) { + this.data = data; + } + + @Override + public int size() { + return data.length; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return Arrays.asList(data).contains(o); + } + + @NotNull + @Override + public Iterator iterator() { + return Arrays.asList(data).iterator(); + } + + @SuppressWarnings({"ManualArrayCopy", "unchecked"}) + @NotNull + @Override + public Object[] toArray() { + final int size = size(); + E[] arr = (E[]) new Object[size]; + for (int i = 0; i < size; i++) { + arr[i] = data[i]; + } + + return arr; + } + + @NotNull + @Override + public T[] toArray(@NotNull T[] a) { + return Arrays.asList(data).toArray(a); + } + + @Override + public boolean add(E e) { + final int size = size(); + E[] newData = Arrays.copyOf(data, size + 1); + newData[size] = e; + data = newData; + + return true; + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object o) { + final List es = Arrays.asList(data); + final boolean removed = es.remove(o); + data = (E[]) es.toArray(); + + return removed; + } + + @Override + public boolean containsAll(@NotNull Collection c) { + return Arrays.asList(data).containsAll(c); + } + + @SuppressWarnings("unchecked") + @Override + public boolean addAll(@NotNull Collection c) { + final List es = Arrays.asList(data); + final boolean added = es.addAll(c); + data = (E[]) es.toArray(); + + return added; + } + + @SuppressWarnings("unchecked") + @Override + public boolean removeAll(@NotNull Collection c) { + final List es = Arrays.asList(data); + final boolean removed = es.removeAll(c); + data = (E[]) es.toArray(); + + return removed; + } + + @SuppressWarnings("unchecked") + @Override + public boolean retainAll(@NotNull Collection c) { + final List es = Arrays.asList(data); + final boolean retained = es.retainAll(c); + data = (E[]) es.toArray(); + + return retained; + } + + @SuppressWarnings("unchecked") + @Override + public void clear() { + data = (E[]) new Object[0]; + } + } } From 6a541523bf902ea525b3b7dc38a8e99c87c1a26b Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Mon, 20 Jun 2022 18:56:59 +0300 Subject: [PATCH 5/7] Added docs --- .../org/utbot/engine/ArrayObjectWrappers.kt | 4 +- .../main/kotlin/org/utbot/engine/Memory.kt | 60 ++++++++++++++----- .../org/utbot/engine/UtBotSymbolicEngine.kt | 3 +- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 1137de8d58..599ae5c002 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -248,7 +248,9 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { // the constructed model to avoid infinite recursion below resolver.addConstructedModel(concreteAddr, resultModel) - val typeStorage = resolver.typeRegistry.genericTypeStorageByAddr[wrapper.addr]?.singleOrNull() ?: TypeStorage(OBJECT_TYPE) + // try to retrieve type storage for the single type parameter + val typeStorage = + resolver.typeRegistry.getObjectParameterTypeStorages(wrapper.addr)?.singleOrNull() ?: TypeStorage(OBJECT_TYPE) (0 until sizeValue).associateWithTo(resultModel.stores) { i -> diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 636c8a7f91..85d9e71d59 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -351,8 +351,10 @@ class TypeRegistry { private val typeToInheritorsTypes = mutableMapOf>() private val typeToAncestorsTypes = mutableMapOf>() - // TODO docs - val genericTypeStorageByAddr = mutableMapOf>() + /** + * Contains types storages for type parameters of object by its address. + */ + private val genericTypeStorageByAddr = mutableMapOf>() // A BiMap containing bijection from every type to an address of the object // presenting its classRef and vise versa @@ -705,20 +707,7 @@ class TypeRegistry { secondAddr: UtAddrExpression, vararg indexInjection: Pair ): UtEqGenericTypeParametersExpression { - // TODO docs - genericTypeStorageByAddr[secondAddr]?.let { existingGenericTypes -> - val currentGenericTypes = mutableMapOf() - - indexInjection.forEach { (from, to) -> - require(from < existingGenericTypes.size) { "TODO" } - currentGenericTypes[to] = existingGenericTypes[from] - } - - genericTypeStorageByAddr[firstAddr] = currentGenericTypes - .entries - .sortedBy { it.key } - .mapTo(mutableListOf()) { it.value } - } + setParameterTypeStoragesEquality(firstAddr, secondAddr, indexInjection) return UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) } @@ -746,6 +735,45 @@ class TypeRegistry { fun eqGenericSingleTypeParameterConstraint(firstAddr: UtAddrExpression, secondAddr: UtAddrExpression) = eqGenericTypeParametersConstraint(firstAddr, secondAddr, 0 to 0) + /** + * Associates provided [typeStorages] with an object with the provided [addr]. + */ + fun saveObjectParameterTypeStorages(addr: UtAddrExpression, typeStorages: List) { + genericTypeStorageByAddr += addr to typeStorages + } + + /** + * Retrieves parameter type storages of an object with the given [addr] if present, or null otherwise. + */ + fun getObjectParameterTypeStorages(addr: UtAddrExpression): List? = genericTypeStorageByAddr[addr] + + /** + * Set types storages for [firstAddr]'s type parameters equal to type storages for [secondAddr]'s type parameters + * according to provided types injection represented by [indexInjection]. + */ + private fun setParameterTypeStoragesEquality( + firstAddr: UtAddrExpression, + secondAddr: UtAddrExpression, + indexInjection: Array> + ) { + genericTypeStorageByAddr[secondAddr]?.let { existingGenericTypes -> + val currentGenericTypes = mutableMapOf() + + indexInjection.forEach { (from, to) -> + require(from >= 0 && from < existingGenericTypes.size) { + "Type injection is out of bounds: should be in [0; ${existingGenericTypes.size}) but is $from" + } + + currentGenericTypes[to] = existingGenericTypes[from] + } + + genericTypeStorageByAddr[firstAddr] = currentGenericTypes + .entries + .sortedBy { it.key } + .mapTo(mutableListOf()) { it.value } + } + } + /** * Returns constraint representing that an object with address [addr] has the same type as the type parameter * with index [i] of an object with address [baseAddr]. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 0e14865ccc..b61d025081 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -1360,7 +1360,8 @@ class UtBotSymbolicEngine( queuedSymbolicStateUpdates += typeRegistry.genericTypeParameterConstraint(value.addr, typeStorages).asHardConstraint() parameterAddrToGenericType += value.addr to type - typeRegistry.genericTypeStorageByAddr += value.addr to typeStorages + + typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages) } } From d4c9199b91401addd6e53c218d9dc3022574d925 Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Mon, 20 Jun 2022 19:03:25 +0300 Subject: [PATCH 6/7] Added any collection test --- .../utbot/examples/lambda/SimpleLambdaExamplesTest.kt | 5 ++--- .../utbot/examples/stream/BaseStreamExampleTest.kt | 11 +++++++++++ .../org/utbot/examples/stream/BaseStreamExample.java | 10 ++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt index 62c63263b4..ecd4a8976e 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt @@ -3,7 +3,6 @@ package org.utbot.examples.lambda import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate import org.utbot.examples.eq import org.utbot.examples.isException @@ -13,7 +12,7 @@ class SimpleLambdaExamplesTest : AbstractTestCaseGeneratorTest(testClass = Simpl checkWithException( SimpleLambdaExamples::biFunctionLambdaExample, eq(2), - { a, b, r -> b == 0 && r.isException() }, + { _, b, r -> b == 0 && r.isException() }, { a, b, r -> b != 0 && r.getOrThrow() == a / b }, ) } @@ -29,4 +28,4 @@ class SimpleLambdaExamplesTest : AbstractTestCaseGeneratorTest(testClass = Simpl // TODO coverage calculation fails https://github.com/UnitTestBot/UTBotJava/issues/192 ) } -} \ No newline at end of file +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt index da94a1b5bd..d8a79b3f28 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt @@ -341,6 +341,17 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( ) } + @Test + fun testAnyCollectionStreamExample() { + check( + BaseStreamExample::anyCollectionStreamExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.size.toLong() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + @Test fun testGenerateExample() { check( diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java index a0b6d749ab..92bfac2c8c 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java @@ -423,6 +423,16 @@ long customCollectionStreamExample(CustomCollection customCollection) { } } + long anyCollectionStreamExample(Collection c) { + UtMock.assume(c != null); + + if (c.isEmpty()) { + return c.stream().count(); + } else { + return c.stream().count(); + } + } + Integer[] generateExample() { return Stream.generate(() -> 42).limit(10).toArray(Integer[]::new); } From 37305cc74aa5c5e55b2508ae2988e505efee3de6 Mon Sep 17 00:00:00 2001 From: Kamenev Yury Date: Tue, 21 Jun 2022 17:33:45 +0300 Subject: [PATCH 7/7] Fixed review issues --- .../utbot/engine/overrides/stream/Stream.java | 14 +- .../engine/overrides/stream/UtStream.java | 13 +- .../org/utbot/engine/ArrayObjectWrappers.kt | 7 +- .../org/utbot/engine/CollectionWrappers.kt | 89 +++++------- .../main/kotlin/org/utbot/engine/Memory.kt | 26 ++-- .../org/utbot/engine/OptionalWrapper.kt | 6 +- .../kotlin/org/utbot/engine/StreamWrappers.kt | 18 +-- .../main/kotlin/org/utbot/engine/Strings.kt | 127 +++++++++--------- .../kotlin/org/utbot/engine/TypeResolver.kt | 22 ++- .../utbot/engine/pc/Z3TranslatorVisitor.kt | 15 ++- .../codegen/model/visitor/UtilMethods.kt | 10 +- .../examples/stream/BaseStreamExampleTest.kt | 12 ++ .../examples/stream/BaseStreamExample.java | 6 + 13 files changed, 188 insertions(+), 177 deletions(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java index e56ecf308b..cf9b533a4d 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java @@ -8,11 +8,11 @@ import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; -@SuppressWarnings({"UnnecessaryInterfaceModifier", "unused"}) +@SuppressWarnings("unused") @UtClassMock(target = java.util.stream.Stream.class, internalUsage = true) public interface Stream extends BaseStream> { @SuppressWarnings("unchecked") - public static java.util.stream.Stream of(E element) { + static java.util.stream.Stream of(E element) { Object[] data = new Object[1]; data[0] = element; @@ -20,30 +20,30 @@ public static java.util.stream.Stream of(E element) { } @SuppressWarnings("unchecked") - public static java.util.stream.Stream of(E... elements) { + static java.util.stream.Stream of(E... elements) { int size = elements.length; return new UtStream<>(elements, size); } @SuppressWarnings("unchecked") - public static java.util.stream.Stream empty() { + static java.util.stream.Stream empty() { return new UtStream<>((E[]) new Object[]{}, 0); } - public static java.util.stream.Stream generate(Supplier s) { + static java.util.stream.Stream generate(Supplier s) { // as "generate" method produces an infinite stream, we cannot analyze it symbolically executeConcretely(); return null; } - public static java.util.stream.Stream iterate(final E seed, final UnaryOperator f) { + static java.util.stream.Stream iterate(final E seed, final UnaryOperator f) { // as "iterate" method produces an infinite stream, we cannot analyze it symbolically executeConcretely(); return null; } - public static java.util.stream.Stream concat( + static java.util.stream.Stream concat( java.util.stream.Stream a, java.util.stream.Stream b ) { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java index a379f4fe43..790a7ef16e 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java @@ -4,7 +4,12 @@ import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray; import org.utbot.engine.overrides.collections.UtGenericStorage; -import java.util.*; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BinaryOperator; @@ -24,6 +29,7 @@ import org.jetbrains.annotations.NotNull; import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; import static org.utbot.engine.ResolverKt.HARD_MAX_ARRAY_SIZE; import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; @@ -313,7 +319,8 @@ public Stream limit(long maxSize) { throw new IllegalArgumentException(); } - // TODO how to process long value correctly - assumeOrExecuteConcretely? + assumeOrExecuteConcretely(maxSize <= Integer.MAX_VALUE); + int newSize = (int) maxSize; int curSize = elementData.end; @@ -346,7 +353,7 @@ public Stream skip(long n) { return new UtStream<>(); } - // TODO how to process long value correctly - assumeOrExecuteConcretely? + // n is 1...Integer.MAX_VALUE here int newSize = (int) (curSize - n); return new UtStream<>((E[]) elementData.toArray((int) n, newSize), newSize); diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 599ae5c002..5c53d0c751 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -33,10 +33,6 @@ import soot.Scene import soot.SootClass import soot.SootField import soot.SootMethod -import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl -import sun.reflect.generics.reflectiveObjects.TypeVariableImpl -import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl val rangeModifiableArrayId: ClassId = RangeModifiableUnlimitedArray::class.id @@ -250,8 +246,7 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { // try to retrieve type storage for the single type parameter val typeStorage = - resolver.typeRegistry.getObjectParameterTypeStorages(wrapper.addr)?.singleOrNull() ?: TypeStorage(OBJECT_TYPE) - + resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)?.singleOrNull() ?: TypeRegistry.objectTypeStorage (0 until sizeValue).associateWithTo(resultModel.stores) { i -> val addr = UtAddrExpression(arrayExpression.select(mkInt(i + firstValue))) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index dc2dadeba6..602051458a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -1,24 +1,15 @@ package org.utbot.engine -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf import org.utbot.common.unreachableBranch -import org.utbot.engine.CommonStreamWrapper.Companion.isClosedStreamFieldId -import org.utbot.engine.CommonStreamWrapper.Companion.isParallelStreamFieldId -import org.utbot.engine.CommonStreamWrapper.Companion.utStreamType import org.utbot.engine.overrides.collections.AssociativeArray -import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray import org.utbot.engine.overrides.collections.UtArrayList import org.utbot.engine.overrides.collections.UtGenericAssociative import org.utbot.engine.overrides.collections.UtGenericStorage import org.utbot.engine.overrides.collections.UtHashMap import org.utbot.engine.overrides.collections.UtHashSet import org.utbot.engine.overrides.collections.UtLinkedList -import org.utbot.engine.overrides.stream.UtStream import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtExpression -import org.utbot.engine.pc.mkEq -import org.utbot.engine.pc.mkFalse import org.utbot.engine.pc.select import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.z3.intValue @@ -58,8 +49,7 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * @see invoke */ - protected abstract fun overrideInvoke( - engine: UtBotSymbolicEngine, + protected abstract fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -83,7 +73,7 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) method: SootMethod, parameters: List ): List { - val methodResults = overrideInvoke(this, wrapper, method, parameters) + val methodResults = overrideInvoke(wrapper, method, parameters) if (methodResults != null) { return methodResults } @@ -159,28 +149,25 @@ abstract class BaseContainerWrapper(containerClassName: String) : BaseOverridden } abstract class BaseGenericStorageBasedContainerWrapper(containerClassName: String) : BaseContainerWrapper(containerClassName) { - override fun overrideInvoke( - engine: UtBotSymbolicEngine, + override fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? = - with(engine) { - when (method.signature) { - UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> { - val equalGenericTypeConstraint = typeRegistry - .eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) - .asHardConstraint() - - val methodResult = MethodResult( - SymbolicSuccess(voidValue), - equalGenericTypeConstraint - ) + when (method.signature) { + UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> { + val equalGenericTypeConstraint = typeRegistry + .eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) + .asHardConstraint() + + val methodResult = MethodResult( + SymbolicSuccess(voidValue), + equalGenericTypeConstraint + ) - listOf(methodResult) - } - else -> null + listOf(methodResult) } + else -> null } override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { @@ -283,34 +270,32 @@ class SetWrapper : BaseGenericStorageBasedContainerWrapper(UtHashSet::class.qual * entries, then real behavior of generated test can differ from expected and undefined. */ class MapWrapper : BaseContainerWrapper(UtHashMap::class.qualifiedName!!) { - override fun overrideInvoke( - engine: UtBotSymbolicEngine, + override fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? = - with(engine) { - when (method.signature) { - UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) - .asHardConstraint() - ) + when (method.signature) { + UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( + MethodResult( + SymbolicSuccess(voidValue), + typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) + .asHardConstraint() ) - UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericTypeParametersConstraint( - parameters[0].addr, - wrapper.addr, - parameterSize = 2 - ).asHardConstraint() - ) + ) + UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( + MethodResult( + SymbolicSuccess(voidValue), + typeRegistry.eqGenericTypeParametersConstraint( + parameters[0].addr, + wrapper.addr, + parameterSize = 2 + ).asHardConstraint() ) - else -> null - } + ) + else -> null } + override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { val fieldModels = collectFieldModels(wrapper.addr, overriddenClass.type) val keyModels = fieldModels[overriddenClass.getFieldByName("keys").fieldId] as? UtArrayModel @@ -402,12 +387,6 @@ private val UT_GENERIC_ASSOCIATIVE_CLASS private val UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE = UT_GENERIC_ASSOCIATIVE_CLASS.getMethodByName(UtGenericAssociative<*, *>::setEqualGenericType.name).signature -private val COLLECTION_CLASS: SootClass - get() = Scene.v().getSootClass(java.util.Collection::class.java.canonicalName) - -private val UT_COLLECTION_STREAM_SIGNATURE: String = - COLLECTION_CLASS.getMethodByName("stream").signature - val ARRAY_LIST_TYPE: RefType get() = Scene.v().getSootClass(java.util.ArrayList::class.java.canonicalName).type val LINKED_LIST_TYPE: RefType diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 85d9e71d59..088848e0f6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -722,7 +722,7 @@ class TypeRegistry { secondAddr: UtAddrExpression, parameterSize: Int ) : UtEqGenericTypeParametersExpression { - val injections = (0 until parameterSize).associateWith { it }.toList().toTypedArray() + val injections = Array(parameterSize) { it to it } return eqGenericTypeParametersConstraint(firstAddr, secondAddr, *injections) } @@ -745,7 +745,7 @@ class TypeRegistry { /** * Retrieves parameter type storages of an object with the given [addr] if present, or null otherwise. */ - fun getObjectParameterTypeStorages(addr: UtAddrExpression): List? = genericTypeStorageByAddr[addr] + fun getTypeStoragesForObjectTypeParameters(addr: UtAddrExpression): List? = genericTypeStorageByAddr[addr] /** * Set types storages for [firstAddr]'s type parameters equal to type storages for [secondAddr]'s type parameters @@ -756,22 +756,22 @@ class TypeRegistry { secondAddr: UtAddrExpression, indexInjection: Array> ) { - genericTypeStorageByAddr[secondAddr]?.let { existingGenericTypes -> - val currentGenericTypes = mutableMapOf() + val existingGenericTypes = genericTypeStorageByAddr[secondAddr] ?: return - indexInjection.forEach { (from, to) -> - require(from >= 0 && from < existingGenericTypes.size) { - "Type injection is out of bounds: should be in [0; ${existingGenericTypes.size}) but is $from" - } + val currentGenericTypes = mutableMapOf() - currentGenericTypes[to] = existingGenericTypes[from] + indexInjection.forEach { (from, to) -> + require(from >= 0 && from < existingGenericTypes.size) { + "Type injection is out of bounds: should be in [0; ${existingGenericTypes.size}) but is $from" } - genericTypeStorageByAddr[firstAddr] = currentGenericTypes - .entries - .sortedBy { it.key } - .mapTo(mutableListOf()) { it.value } + currentGenericTypes[to] = existingGenericTypes[from] } + + genericTypeStorageByAddr[firstAddr] = currentGenericTypes + .entries + .sortedBy { it.key } + .mapTo(mutableListOf()) { it.value } } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt index 3db35e0ded..19348fc6f9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt @@ -58,8 +58,7 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri private val AS_OPTIONAL_METHOD_SIGNATURE = overriddenClass.getMethodByName(UtOptional<*>::asOptional.name).signature - override fun overrideInvoke( - engine: UtBotSymbolicEngine, + override fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -72,7 +71,7 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri return listOf( MethodResult( parameters.first(), - engine.typeRegistry.typeConstraintToGenericTypeParameter( + typeRegistry.typeConstraintToGenericTypeParameter( parameters.first().addr, wrapper.addr, i = 0 @@ -82,6 +81,7 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri } } + return null } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt index 55b765b933..e3cfe68a1b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt @@ -14,11 +14,8 @@ import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.util.nextModelName -import soot.BooleanType import soot.RefType import soot.Scene -import soot.SootField -import soot.Type /** * Auxiliary enum class for specifying an implementation for [CommonStreamWrapper], that it will use. @@ -62,14 +59,14 @@ abstract class StreamWrapper( override fun value(resolver: Resolver, wrapper: ObjectValue): UtAssembleModel = resolver.run { val addr = holder.concreteAddr(wrapper.addr) val modelName = nextModelName(baseModelName) - val parametersArrayModel = resolveElementAsArrayModel(wrapper) + val parametersArrayModel = resolveElementsAsArrayModel(wrapper) val instantiationChain = mutableListOf() val modificationsChain = emptyList() UtAssembleModel(addr, utStreamClass.overriddenStreamClassId, modelName, instantiationChain, modificationsChain) .apply { - val (builder, params) = if (parametersArrayModel.length == 0) { + val (builder, params) = if (parametersArrayModel == null || parametersArrayModel.length == 0) { streamEmptyMethodId to emptyList() } else { streamOfMethodId to listOf(parametersArrayModel) @@ -89,10 +86,10 @@ abstract class StreamWrapper( override val modificationMethodId: MethodId get() = error("No modification method for Stream") - private fun Resolver.resolveElementAsArrayModel(wrapper: ObjectValue): UtArrayModel { + private fun Resolver.resolveElementsAsArrayModel(wrapper: ObjectValue): UtArrayModel? { val elementDataFieldId = FieldId(overriddenClass.type.classId, "elementData") - return collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as UtArrayModel + return collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as? UtArrayModel } private val streamOfMethodId: MethodId = methodId( @@ -116,12 +113,5 @@ class CommonStreamWrapper : StreamWrapper(UtStreamClass.UT_STREAM) { companion object { internal val utStreamType: RefType get() = Scene.v().getSootClass(UtStream::class.java.canonicalName).type - private val booleanType: Type - get() = BooleanType.v() - - internal val isParallelStreamFieldId: SootField - get() = SootField("isParallel", booleanType) - internal val isClosedStreamFieldId: SootField - get() = SootField("isClosed", booleanType) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt index 0cfe3012c1..72695c927f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt @@ -67,85 +67,83 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { private fun UtBotSymbolicEngine.getValueArray(addr: UtAddrExpression) = getArrayField(addr, overriddenClass, STRING_VALUE) - override fun overrideInvoke( - engine: UtBotSymbolicEngine, + override fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List - ): List? = - with(engine) { - when (method.subSignature) { - toStringMethodSignature -> { - return listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) - } - matchesMethodSignature -> { - val arg = parameters[0] as ObjectValue - val matchingLengthExpr = getIntFieldValue(arg, STRING_LENGTH).accept(RewritingVisitor()) - - if (!matchingLengthExpr.isConcrete) return null + ): List? { + return when (method.subSignature) { + toStringMethodSignature -> { + listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) + } + matchesMethodSignature -> { + val arg = parameters[0] as ObjectValue + val matchingLengthExpr = getIntFieldValue(arg, STRING_LENGTH).accept(RewritingVisitor()) - val matchingValueExpr = - selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) - val matchingLength = matchingLengthExpr.toConcrete() as Int - val matchingValue = CharArray(matchingLength) + if (!matchingLengthExpr.isConcrete) return null - for (i in 0 until matchingLength) { - val charExpr = matchingValueExpr.select(mkInt(i)).accept(RewritingVisitor()) + val matchingValueExpr = + selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) + val matchingLength = matchingLengthExpr.toConcrete() as Int + val matchingValue = CharArray(matchingLength) - if (!charExpr.isConcrete) return null + for (i in 0 until matchingLength) { + val charExpr = matchingValueExpr.select(mkInt(i)).accept(RewritingVisitor()) - matchingValue[i] = (charExpr.toConcrete() as Number).toChar() - } + if (!charExpr.isConcrete) return null - val rgxGen = RgxGen(String(matchingValue)) - val matching = (rgxGen.generate()) - val notMatching = rgxGen.generateNotMatching() + matchingValue[i] = (charExpr.toConcrete() as Number).toChar() + } - val thisLength = getIntFieldValue(wrapper, STRING_LENGTH) - val thisValue = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) + val rgxGen = RgxGen(String(matchingValue)) + val matching = (rgxGen.generate()) + val notMatching = rgxGen.generateNotMatching() - val matchingConstraints = mutableSetOf() - matchingConstraints += mkEq(thisLength, mkInt(matching.length)) - for (i in matching.indices) { - matchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(matching[i])) - } + val thisLength = getIntFieldValue(wrapper, STRING_LENGTH) + val thisValue = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) - val notMatchingConstraints = mutableSetOf() - notMatchingConstraints += mkEq(thisLength, mkInt(notMatching.length)) - for (i in notMatching.indices) { - notMatchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(notMatching[i])) - } + val matchingConstraints = mutableSetOf() + matchingConstraints += mkEq(thisLength, mkInt(matching.length)) + for (i in matching.indices) { + matchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(matching[i])) + } - return listOf( - MethodResult(UtTrue.toBoolValue(), matchingConstraints.asHardConstraint()), - MethodResult(UtFalse.toBoolValue(), notMatchingConstraints.asHardConstraint()) - ) + val notMatchingConstraints = mutableSetOf() + notMatchingConstraints += mkEq(thisLength, mkInt(notMatching.length)) + for (i in notMatching.indices) { + notMatchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(notMatching[i])) } - charAtMethodSignature -> { - val index = parameters[0] as PrimitiveValue - val lengthExpr = getIntFieldValue(wrapper, STRING_LENGTH) - val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) - val failMethodResult = - MethodResult( - explicitThrown( - StringIndexOutOfBoundsException(), - findNewAddr(), - isInNestedMethod() - ), - hardConstraints = mkNot(inBoundsCondition).asHardConstraint() - ) - - val valueExpr = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) - - val returnResult = MethodResult( - valueExpr.select(index.expr).toCharValue(), - hardConstraints = inBoundsCondition.asHardConstraint() + + return listOf( + MethodResult(UtTrue.toBoolValue(), matchingConstraints.asHardConstraint()), + MethodResult(UtFalse.toBoolValue(), notMatchingConstraints.asHardConstraint()) + ) + } + charAtMethodSignature -> { + val index = parameters[0] as PrimitiveValue + val lengthExpr = getIntFieldValue(wrapper, STRING_LENGTH) + val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) + val failMethodResult = + MethodResult( + explicitThrown( + StringIndexOutOfBoundsException(), + findNewAddr(), + isInNestedMethod() + ), + hardConstraints = mkNot(inBoundsCondition).asHardConstraint() ) - return listOf(returnResult, failMethodResult) - } - else -> return null + + val valueExpr = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) + + val returnResult = MethodResult( + valueExpr.select(index.expr).toCharValue(), + hardConstraints = inBoundsCondition.asHardConstraint() + ) + return listOf(returnResult, failMethodResult) } + else -> return null } + } override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = resolver.run { val classId = STRING_TYPE.id @@ -294,8 +292,7 @@ sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenW private val asStringBuilderMethodSignature = overriddenClass.getMethodByName("asStringBuilder").subSignature - override fun overrideInvoke( - engine: UtBotSymbolicEngine, + override fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt index e9f35643cc..95565eb600 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt @@ -110,7 +110,7 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy fun constructTypeStorage(type: Type, possibleTypes: Collection): TypeStorage { val concretePossibleTypes = possibleTypes .map { (if (it is ArrayType) it.baseType else it) to it.numDimensions } - .filterNot { (baseType, numDimensions) -> numDimensions == 0 && baseType is RefType && baseType.sootClass.isInappropriate } + .filterNot { (baseType, numDimensions) -> isInappropriateOrArrayOfMocksOrLocals(numDimensions, baseType) } .mapTo(mutableSetOf()) { (baseType, numDimensions) -> if (numDimensions == 0) baseType else baseType.makeArrayType(numDimensions) } @@ -118,6 +118,26 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy return TypeStorage(type, concretePossibleTypes).filterInappropriateClassesForCodeGeneration() } + private fun isInappropriateOrArrayOfMocksOrLocals(numDimensions: Int, baseType: Type?): Boolean { + if (baseType !is RefType) { + return false + } + + val baseSootClass = baseType.sootClass + + if (numDimensions == 0 && baseSootClass.isInappropriate) { + // interface, abstract class, or local, or mock could not be constructed + return true + } + + if (numDimensions > 0 && (baseSootClass.isLocal || baseSootClass.findMockAnnotationOrNull != null)) { + // array of mocks or locals could not be constructed, but array of interfaces or abstract classes could be + return true + } + + return false + } + /** * Constructs a [TypeStorage] instance for given [type]. * Depending on [useConcreteType] it will or will not contain type's inheritors. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt index 31fe3591a8..4f0d7516d4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt @@ -242,10 +242,10 @@ open class Z3TranslatorVisitor( ) }.toTypedArray() ) - constraints += typeConstraint - z3Context.mkAnd(*constraints.toTypedArray()) + constraints += typeConstraint } + z3Context.mkOr( z3Context.mkAnd(*constraints.toTypedArray()), z3Context.mkEq(translate(expr.addr), translate(nullObjectAddr)) @@ -259,12 +259,17 @@ open class Z3TranslatorVisitor( val genericSymType = translate(typeRegistry.genericTypeId(baseAddr, parameterTypeIndex)) val genericNumDimensions = translate(typeRegistry.genericNumDimensions(baseAddr, parameterTypeIndex)) - val typeConstraint = z3Context.mkOr( + val dimensionsConstraint = z3Context.mkEq(symNumDimensions, genericNumDimensions) + + val equalTypeConstraint = z3Context.mkAnd( z3Context.mkEq(symType, genericSymType), - z3Context.mkEq(translate(expr.addr), translate(nullObjectAddr)) + dimensionsConstraint ) - val dimensionsConstraint = z3Context.mkEq(symNumDimensions, genericNumDimensions) + val typeConstraint = z3Context.mkOr( + equalTypeConstraint, + z3Context.mkEq(translate(expr.addr), translate(nullObjectAddr)) + ) z3Context.mkAnd(typeConstraint, dimensionsConstraint) } 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 a0a430507d..b8a95b5e59 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 @@ -858,17 +858,17 @@ private fun ClassId.regularImportsByUtilMethod(id: MethodId, codegenLanguage: Co } iterablesDeepEqualsMethodId -> when (codegenLanguage) { CodegenLanguage.JAVA -> listOf(Iterable::class.id, Iterator::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> listOf() + CodegenLanguage.KOTLIN -> emptyList() } streamsDeepEqualsMethodId -> when (codegenLanguage) { - CodegenLanguage.JAVA -> listOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> listOf() + CodegenLanguage.JAVA -> listOf(java.util.stream.Stream::class.id, Set::class.id) + CodegenLanguage.KOTLIN -> emptyList() } mapsDeepEqualsMethodId -> when (codegenLanguage) { CodegenLanguage.JAVA -> listOf(Map::class.id, Iterator::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> listOf() + CodegenLanguage.KOTLIN -> emptyList() } - hasCustomEqualsMethodId -> listOf() + hasCustomEqualsMethodId -> emptyList() getArrayLengthMethodId -> listOf(java.lang.reflect.Array::class.id) else -> error("Unknown util method for class $this: $id") } diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt index d8a79b3f28..5ab5a133b9 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt @@ -42,6 +42,18 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( } } + @Test + fun testReturningStreamAsParameterExample() { + withoutConcrete { + check( + BaseStreamExample::returningStreamAsParameterExample, + eq(1), + { s, r -> s != null && s.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + @Test fun testFilterExample() { check( diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java index 92bfac2c8c..326f3b12b9 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java @@ -28,6 +28,11 @@ Stream returningStreamExample(List list) { } } + Stream returningStreamAsParameterExample(Stream s) { + UtMock.assume(s != null); + return s; + } + @SuppressWarnings("Convert2MethodRef") boolean filterExample(List list) { UtMock.assume(list != null && !list.isEmpty()); @@ -423,6 +428,7 @@ long customCollectionStreamExample(CustomCollection customCollection) { } } + @SuppressWarnings({"ConstantConditions", "ReplaceInefficientStreamCount"}) long anyCollectionStreamExample(Collection c) { UtMock.assume(c != null);