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..cf9b533a4d --- /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("unused") +@UtClassMock(target = java.util.stream.Stream.class, internalUsage = true) +public interface Stream extends BaseStream> { + @SuppressWarnings("unchecked") + static java.util.stream.Stream of(E element) { + Object[] data = new Object[1]; + data[0] = element; + + return new UtStream<>((E[]) data, 1); + } + + @SuppressWarnings("unchecked") + static java.util.stream.Stream of(E... elements) { + int size = elements.length; + + return new UtStream<>(elements, size); + } + + @SuppressWarnings("unchecked") + static java.util.stream.Stream empty() { + return new UtStream<>((E[]) new Object[]{}, 0); + } + + static java.util.stream.Stream generate(Supplier s) { + // as "generate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + 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; + } + + 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..790a7ef16e --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java @@ -0,0 +1,689 @@ +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.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; +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.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; +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(); + } + + assumeOrExecuteConcretely(maxSize <= Integer.MAX_VALUE); + + 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<>(); + } + + // n is 1...Integer.MAX_VALUE here + 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..5c53d0c751 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,17 @@ 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 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 +167,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 +234,7 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { val resultModel = UtArrayModel( concreteAddr, - objectClassId, + objectArrayClassId, sizeValue, UtNullModel(objectClassId), mutableMapOf() @@ -243,17 +244,35 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { // the constructed model to avoid infinite recursion below resolver.addConstructedModel(concreteAddr, resultModel) + // try to retrieve type storage for the single type parameter + val typeStorage = + resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)?.singleOrNull() ?: TypeRegistry.objectTypeStorage + (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..602051458a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -33,7 +33,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 @@ -74,7 +73,6 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) method: SootMethod, parameters: List ): List { - val methodResults = overrideInvoke(wrapper, method, parameters) if (methodResults != null) { return methodResults @@ -94,25 +92,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 +148,36 @@ abstract class BaseCollectionWrapper(collectionClassName: String) : BaseOverridd override fun toString() = "$overriddenClassName()" } +abstract class BaseGenericStorageBasedContainerWrapper(containerClassName: String) : BaseContainerWrapper(containerClassName) { + override fun UtBotSymbolicEngine.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = + 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 +203,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 +218,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 +236,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 +251,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,32 +269,32 @@ 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!!) { +class MapWrapper : BaseContainerWrapper(UtHashMap::class.qualifiedName!!) { override fun UtBotSymbolicEngine.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List - ): List? { - return when (method.signature) { + ): List? = + when (method.signature) { UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( MethodResult( SymbolicSuccess(voidValue), - typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr).asHardConstraint() + 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() + typeRegistry.eqGenericTypeParametersConstraint( + parameters[0].addr, + wrapper.addr, + parameterSize = 2 + ).asHardConstraint() ) ) else -> null } - } override fun Resolver.resolveValueModels(wrapper: ObjectValue): List> { val fieldModels = collectFieldModels(wrapper.addr, overriddenClass.type) @@ -344,7 +335,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 +378,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 @@ -397,9 +388,9 @@ private val UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE = UT_GENERIC_ASSOCIATIVE_CLASS.getMethodByName(UtGenericAssociative<*, *>::setEqualGenericType.name).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 +402,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 0ca5359db9..088848e0f6 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,11 @@ class TypeRegistry { private val typeToInheritorsTypes = mutableMapOf>() private val typeToAncestorsTypes = 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 private val classRefBiMap = HashBiMap.create() @@ -431,7 +436,6 @@ class TypeRegistry { private val genericAddrToNumDimensionsArrays = mutableMapOf() - @Suppress("unused") private fun genericAddrToNumDimensions(i: Int) = genericAddrToNumDimensionsArrays.getOrPut(i) { mkArrayConst( "genericAddrToNumDimensions_$i", @@ -562,6 +566,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 */ @@ -700,7 +706,11 @@ class TypeRegistry { firstAddr: UtAddrExpression, secondAddr: UtAddrExpression, vararg indexInjection: Pair - ) = UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) + ): UtEqGenericTypeParametersExpression { + setParameterTypeStoragesEquality(firstAddr, secondAddr, indexInjection) + + return UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) + } /** * returns constraint representing that type parameters of an object with address [firstAddr] are equal to @@ -711,7 +721,11 @@ class TypeRegistry { firstAddr: UtAddrExpression, secondAddr: UtAddrExpression, parameterSize: Int - ) = UtEqGenericTypeParametersExpression(firstAddr, secondAddr, (0 until parameterSize).associateWith { it }) + ) : UtEqGenericTypeParametersExpression { + val injections = Array(parameterSize) { it to it } + + return eqGenericTypeParametersConstraint(firstAddr, secondAddr, *injections) + } /** * returns constraint representing that the first type parameter of an object with address [firstAddr] are equal to @@ -719,7 +733,46 @@ class TypeRegistry { * @see UtEqGenericTypeParametersExpression */ fun eqGenericSingleTypeParameterConstraint(firstAddr: UtAddrExpression, secondAddr: UtAddrExpression) = - UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(0 to 0)) + 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 getTypeStoragesForObjectTypeParameters(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> + ) { + val existingGenericTypes = genericTypeStorageByAddr[secondAddr] ?: return + + 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 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..19348fc6f9 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, @@ -81,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 new file mode 100644 index 0000000000..e3cfe68a1b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt @@ -0,0 +1,117 @@ +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.RefType +import soot.Scene + +/** + * 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 = resolveElementsAsArrayModel(wrapper) + + val instantiationChain = mutableListOf() + val modificationsChain = emptyList() + + UtAssembleModel(addr, utStreamClass.overriddenStreamClassId, modelName, instantiationChain, modificationsChain) + .apply { + val (builder, params) = if (parametersArrayModel == null || 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.resolveElementsAsArrayModel(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 + } +} 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..72695c927f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt @@ -72,9 +72,9 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { method: SootMethod, parameters: List ): List? { - when (method.subSignature) { + return when (method.subSignature) { toStringMethodSignature -> { - return listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) + listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) } matchesMethodSignature -> { val arg = parameters[0] as ObjectValue @@ -82,7 +82,8 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { if (!matchingLengthExpr.isConcrete) return null - val matchingValueExpr = selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) + val matchingValueExpr = + selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) val matchingLength = matchingLengthExpr.toConcrete() as Int val matchingValue = CharArray(matchingLength) @@ -124,7 +125,11 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) val failMethodResult = MethodResult( - explicitThrown(StringIndexOutOfBoundsException(), findNewAddr(), isInNestedMethod()), + explicitThrown( + StringIndexOutOfBoundsException(), + findNewAddr(), + isInNestedMethod() + ), hardConstraints = mkNot(inBoundsCondition).asHardConstraint() ) 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..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, _) -> 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/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 9ba79b1b34..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,6 +1360,8 @@ class UtBotSymbolicEngine( queuedSymbolicStateUpdates += typeRegistry.genericTypeParameterConstraint(value.addr, typeStorages).asHardConstraint() parameterAddrToGenericType += value.addr to type + + typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages) } } @@ -3470,18 +3472,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 +3709,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 0a8aea1f0b..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 @@ -227,9 +227,11 @@ open class Z3TranslatorVisitor( val constraints = mutableListOf() for (i in types.indices) { 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( @@ -240,8 +242,10 @@ open class Z3TranslatorVisitor( ) }.toTypedArray() ) + constraints += typeConstraint } + z3Context.mkOr( z3Context.mkAnd(*constraints.toTypedArray()), z3Context.mkEq(translate(expr.addr), translate(nullObjectAddr)) @@ -250,11 +254,24 @@ 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 dimensionsConstraint = z3Context.mkEq(symNumDimensions, genericNumDimensions) + + val equalTypeConstraint = z3Context.mkAnd( z3Context.mkEq(symType, genericSymType), + dimensionsConstraint + ) + + val typeConstraint = z3Context.mkOr( + equalTypeConstraint, z3Context.mkEq(translate(expr.addr), translate(nullObjectAddr)) ) + + z3Context.mkAnd(typeConstraint, dimensionsConstraint) } override fun visit(expr: UtEqGenericTypeParametersExpression): Expr = expr.run { @@ -263,6 +280,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()) } 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..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 @@ -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 -> { @@ -794,13 +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, 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/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..ecd4a8976e --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt @@ -0,0 +1,31 @@ +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.eq +import org.utbot.examples.isException + +class SimpleLambdaExamplesTest : AbstractTestCaseGeneratorTest(testClass = SimpleLambdaExamples::class) { + @Test + fun testBiFunctionLambdaExample() { + checkWithException( + SimpleLambdaExamples::biFunctionLambdaExample, + eq(2), + { _, 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 + ) + } +} 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..5ab5a133b9 --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt @@ -0,0 +1,407 @@ +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 testReturningStreamAsParameterExample() { + withoutConcrete { + check( + BaseStreamExample::returningStreamAsParameterExample, + eq(1), + { s, r -> s != null && s.toList() == 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 + @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 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( + 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..326f3b12b9 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java @@ -0,0 +1,604 @@ +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; +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(); + } + } + + Stream returningStreamAsParameterExample(Stream s) { + UtMock.assume(s != null); + return s; + } + + @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(); + } + + @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;*/ + } + } + + @SuppressWarnings({"ConstantConditions", "ReplaceInefficientStreamCount"}) + 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); + } + + 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; + } + } + + 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]; + } + } +}