aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/com/code_intelligence/jazzer/mutation
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/code_intelligence/jazzer/mutation')
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/ArgumentsMutatorTest.java298
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/BUILD.bazel15
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/combinator/BUILD.bazel14
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java526
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/engine/BUILD.bazel13
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandomTest.java143
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel28
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java588
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel17
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java237
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java280
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java355
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel18
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java74
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java189
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java103
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java785
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java85
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java101
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java224
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel60
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java87
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java447
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java603
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java92
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto161
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto144
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/BUILD.bazel35
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/ExceptionSupportTest.java43
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/HolderTest.java114
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/InputStreamSupportTest.java146
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java425
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java269
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/WeakIdentityHashMapTest.java61
34 files changed, 6780 insertions, 0 deletions
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/ArgumentsMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/ArgumentsMutatorTest.java
new file mode 100644
index 00000000..9a5bafd8
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/ArgumentsMutatorTest.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static java.util.Collections.singletonList;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.mutator.Mutators;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.ResourceLock;
+
+@SuppressWarnings("OptionalGetWithoutIsPresent")
+class ArgumentsMutatorTest {
+ private static List<List<Boolean>> fuzzThisFunctionArgument1;
+ private static List<Boolean> fuzzThisFunctionArgument2;
+
+ public static void fuzzThisFunction(List<List<@NotNull Boolean>> list, List<Boolean> otherList) {
+ fuzzThisFunctionArgument1 = list;
+ fuzzThisFunctionArgument2 = otherList;
+ }
+
+ @Test
+ @ResourceLock(value = "fuzzThisFunction")
+ void testStaticMethod() throws Throwable {
+ Method method =
+ ArgumentsMutatorTest.class.getMethod("fuzzThisFunction", List.class, List.class);
+ Optional<ArgumentsMutator> maybeMutator =
+ ArgumentsMutator.forStaticMethod(Mutators.newFactory(), method);
+ assertThat(maybeMutator).isPresent();
+ ArgumentsMutator mutator = maybeMutator.get();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // outer list not null
+ false,
+ // outer list size 1
+ 1,
+ // inner list not null
+ false,
+ // inner list size 1
+ 1,
+ // boolean
+ true,
+ // outer list not null
+ false,
+ // outer list size 1
+ 1,
+ // Boolean not null
+ false,
+ // boolean
+ false)) {
+ mutator.init(prng);
+ }
+
+ fuzzThisFunctionArgument1 = null;
+ fuzzThisFunctionArgument2 = null;
+ mutator.invoke(true);
+ assertThat(fuzzThisFunctionArgument1).containsExactly(singletonList(true));
+ assertThat(fuzzThisFunctionArgument2).containsExactly(false);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first argument
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate in outer list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get to inner list
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate inner list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get boolean value
+ 0)) {
+ mutator.mutate(prng);
+ }
+
+ fuzzThisFunctionArgument1 = null;
+ fuzzThisFunctionArgument2 = null;
+ mutator.invoke(true);
+ assertThat(fuzzThisFunctionArgument1).containsExactly(singletonList(false));
+ assertThat(fuzzThisFunctionArgument2).containsExactly(false);
+
+ // Modify the arguments passed to the function.
+ fuzzThisFunctionArgument1.get(0).clear();
+ fuzzThisFunctionArgument2.clear();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first argument
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate in outer list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get to inner list
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate inner list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get boolean value
+ 0)) {
+ mutator.mutate(prng);
+ }
+
+ fuzzThisFunctionArgument1 = null;
+ fuzzThisFunctionArgument2 = null;
+ mutator.invoke(false);
+ assertThat(fuzzThisFunctionArgument1).containsExactly(singletonList(true));
+ assertThat(fuzzThisFunctionArgument2).containsExactly(false);
+ }
+
+ private List<List<Boolean>> mutableFuzzThisFunctionArgument1;
+ private List<Boolean> mutableFuzzThisFunctionArgument2;
+
+ public void mutableFuzzThisFunction(List<List<@NotNull Boolean>> list, List<Boolean> otherList) {
+ mutableFuzzThisFunctionArgument1 = list;
+ mutableFuzzThisFunctionArgument2 = otherList;
+ }
+
+ @Test
+ void testInstanceMethod() throws Throwable {
+ Method method =
+ ArgumentsMutatorTest.class.getMethod("mutableFuzzThisFunction", List.class, List.class);
+ Optional<ArgumentsMutator> maybeMutator =
+ ArgumentsMutator.forInstanceMethod(Mutators.newFactory(), this, method);
+ assertThat(maybeMutator).isPresent();
+ ArgumentsMutator mutator = maybeMutator.get();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // outer list not null
+ false,
+ // outer list size 1
+ 1,
+ // inner list not null
+ false,
+ // inner list size 1
+ 1,
+ // boolean
+ true,
+ // outer list not null
+ false,
+ // outer list size 1
+ 1,
+ // Boolean not null
+ false,
+ // boolean
+ false)) {
+ mutator.init(prng);
+ }
+
+ mutableFuzzThisFunctionArgument1 = null;
+ mutableFuzzThisFunctionArgument2 = null;
+ mutator.invoke(true);
+ assertThat(mutableFuzzThisFunctionArgument1).containsExactly(singletonList(true));
+ assertThat(mutableFuzzThisFunctionArgument2).containsExactly(false);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first argument
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate in outer list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get to inner list
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate inner list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get boolean value
+ 0)) {
+ mutator.mutate(prng);
+ }
+
+ mutableFuzzThisFunctionArgument1 = null;
+ mutableFuzzThisFunctionArgument2 = null;
+ mutator.invoke(true);
+ assertThat(mutableFuzzThisFunctionArgument1).containsExactly(singletonList(false));
+ assertThat(mutableFuzzThisFunctionArgument2).containsExactly(false);
+
+ // Modify the arguments passed to the function.
+ mutableFuzzThisFunctionArgument1.get(0).clear();
+ mutableFuzzThisFunctionArgument2.clear();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first argument
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate in outer list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get to inner list
+ 0,
+ // Nullable mutator
+ false,
+ // Action mutate inner list
+ 2,
+ // Mutate one element,
+ 1,
+ // index to get boolean value
+ 0)) {
+ mutator.mutate(prng);
+ }
+
+ mutableFuzzThisFunctionArgument1 = null;
+ mutableFuzzThisFunctionArgument2 = null;
+ mutator.invoke(false);
+ assertThat(mutableFuzzThisFunctionArgument1).containsExactly(singletonList(true));
+ assertThat(mutableFuzzThisFunctionArgument2).containsExactly(false);
+ }
+
+ @SuppressWarnings("unused")
+ public void crossOverFunction(List<Boolean> list) {}
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testCrossOver() throws Throwable {
+ Method method = ArgumentsMutatorTest.class.getMethod("crossOverFunction", List.class);
+ Optional<ArgumentsMutator> maybeMutator =
+ ArgumentsMutator.forInstanceMethod(Mutators.newFactory(), this, method);
+ assertThat(maybeMutator).isPresent();
+ ArgumentsMutator mutator = maybeMutator.get();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list not null
+ false,
+ // list size 1
+ 1,
+ // not null,
+ false,
+ // boolean
+ true)) {
+ mutator.init(prng);
+ }
+ ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+ mutator.write(baos1);
+ byte[] out1 = baos1.toByteArray();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list not null
+ false,
+ // list size 1
+ 1,
+ // not null
+ false,
+ // boolean
+ false)) {
+ mutator.init(prng);
+ }
+ ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+ mutator.write(baos2);
+ byte[] out2 = baos1.toByteArray();
+
+ mutator.crossOver(new ByteArrayInputStream(out1), new ByteArrayInputStream(out2), 12345);
+ Object[] arguments = mutator.getArguments();
+
+ assertThat(arguments).isNotEmpty();
+ assertThat((List<Boolean>) arguments[0]).isNotEmpty();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/BUILD.bazel
new file mode 100644
index 00000000..9d397570
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/BUILD.bazel
@@ -0,0 +1,15 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
+
+java_test_suite(
+ name = "MutationTests",
+ size = "small",
+ srcs = glob(["*Test.java"]),
+ runner = "junit5",
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/combinator/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/combinator/BUILD.bazel
new file mode 100644
index 00000000..033c03b6
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/combinator/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
+
+java_test_suite(
+ name = "CompositeTests",
+ size = "small",
+ srcs = glob(["*.java"]),
+ runner = "junit5",
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/combinator",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java
new file mode 100644
index 00000000..d0d06f22
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.combinator;
+
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.assemble;
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.combine;
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateProduct;
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateProperty;
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateSumInPlace;
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateThenMapToImmutable;
+import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateViaView;
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.infiniteZeros;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockCrossOver;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockCrossOverInPlace;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockInitInPlace;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockInitializer;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockMutator;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.nullDataOutputStream;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.code_intelligence.jazzer.mutation.api.Debuggable;
+import com.code_intelligence.jazzer.mutation.api.InPlaceMutator;
+import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
+import com.code_intelligence.jazzer.mutation.api.Serializer;
+import com.code_intelligence.jazzer.mutation.api.SerializingInPlaceMutator;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+import org.junit.jupiter.api.Test;
+
+class MutatorCombinatorsTest {
+ @Test
+ void testMutateProperty() {
+ InPlaceMutator<Foo> mutator =
+ mutateProperty(Foo::getValue, mockMutator(21, value -> 2 * value), Foo::setValue);
+
+ assertThat(mutator.toString()).isEqualTo("Foo.Integer");
+
+ Foo foo = new Foo(0, singletonList(13));
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.initInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(21);
+ assertThat(foo.getList()).containsExactly(13);
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.mutateInPlace(foo, prng);
+ }
+
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(13);
+ }
+
+ @Test
+ void testCrossOverProperty() {
+ InPlaceMutator<Foo> mutator =
+ mutateProperty(Foo::getValue, mockCrossOver((a, b) -> 42), Foo::setValue);
+ Foo foo = new Foo(0);
+ Foo otherFoo = new Foo(1);
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // use foo value
+ 0)) {
+ mutator.crossOverInPlace(foo, otherFoo, prng);
+ assertThat(foo.getValue()).isEqualTo(0);
+ }
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // use otherFoo value
+ 1)) {
+ mutator.crossOverInPlace(foo, otherFoo, prng);
+ assertThat(foo.getValue()).isEqualTo(1);
+ }
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // use property type cross over
+ 2)) {
+ mutator.crossOverInPlace(foo, otherFoo, prng);
+ assertThat(foo.getValue()).isEqualTo(42);
+ }
+ }
+
+ @Test
+ void testMutateViaView() {
+ InPlaceMutator<Foo> mutator = mutateViaView(Foo::getList, new InPlaceMutator<List<Integer>>() {
+ @Override
+ public void initInPlace(List<Integer> reference, PseudoRandom prng) {
+ reference.clear();
+ reference.add(21);
+ }
+
+ @Override
+ public void mutateInPlace(List<Integer> reference, PseudoRandom prng) {
+ reference.add(reference.get(reference.size() - 1) + 1);
+ }
+
+ @Override
+ public void crossOverInPlace(
+ List<Integer> reference, List<Integer> otherReference, PseudoRandom prng) {}
+
+ @Override
+ public String toDebugString(Predicate<Debuggable> isInCycle) {
+ return "List<Integer>";
+ }
+ });
+
+ assertThat(mutator.toString()).isEqualTo("Foo via List<Integer>");
+
+ Foo foo = new Foo(13, singletonList(13));
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.initInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(13);
+ assertThat(foo.getList()).containsExactly(21);
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.mutateInPlace(foo, prng);
+ }
+
+ assertThat(foo.getValue()).isEqualTo(13);
+ assertThat(foo.getList()).containsExactly(21, 22);
+ }
+
+ @Test
+ void testCrossOverViaView() {
+ InPlaceMutator<Foo> mutator = mutateViaView(Foo::getList, mockCrossOverInPlace((a, b) -> {
+ a.clear();
+ a.add(42);
+ }));
+
+ Foo foo = new Foo(0, singletonList(0));
+ Foo otherFoo = new Foo(0, singletonList(1));
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.crossOverInPlace(foo, otherFoo, prng);
+ assertThat(foo.getList()).containsExactly(42);
+ }
+ }
+
+ @Test
+ void testMutateCombine() {
+ InPlaceMutator<Foo> valueMutator =
+ mutateProperty(Foo::getValue, mockMutator(21, value -> 2 * value), Foo::setValue);
+
+ InPlaceMutator<Foo> listMutator =
+ mutateViaView(Foo::getList, new InPlaceMutator<List<Integer>>() {
+ @Override
+ public void initInPlace(List<Integer> reference, PseudoRandom prng) {
+ reference.clear();
+ reference.add(21);
+ }
+
+ @Override
+ public void mutateInPlace(List<Integer> reference, PseudoRandom prng) {
+ reference.add(reference.get(reference.size() - 1) + 1);
+ }
+
+ @Override
+ public void crossOverInPlace(
+ List<Integer> reference, List<Integer> otherReference, PseudoRandom prng) {}
+
+ @Override
+ public String toDebugString(Predicate<Debuggable> isInCycle) {
+ return "List<Integer>";
+ }
+ });
+ InPlaceMutator<Foo> mutator = combine(valueMutator, listMutator);
+
+ assertThat(mutator.toString()).isEqualTo("{Foo.Integer, Foo via List<Integer>}");
+
+ Foo foo = new Foo(13, singletonList(13));
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.initInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(21);
+ assertThat(foo.getList()).containsExactly(21);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* use valueMutator */ 0)) {
+ mutator.mutateInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(21);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* use listMutator */ 1)) {
+ mutator.mutateInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(21, 22);
+ }
+
+ @Test
+ void testCrossOverCombine() {
+ InPlaceMutator<Foo> valueMutator =
+ mutateProperty(Foo::getValue, mockCrossOver((a, b) -> 42), Foo::setValue);
+ InPlaceMutator<Foo> listMutator = mutateViaView(Foo::getList, mockCrossOverInPlace((a, b) -> {
+ a.clear();
+ a.add(42);
+ }));
+ InPlaceMutator<Foo> mutator = combine(valueMutator, listMutator);
+
+ Foo foo = new Foo(0, singletonList(0));
+ Foo fooOther = new Foo(1, singletonList(1));
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // call cross over in property mutator
+ 2)) {
+ mutator.crossOverInPlace(foo, fooOther, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(42);
+ }
+
+ @Test
+ void testCrossOverEmptyCombine() {
+ Foo foo = new Foo(0, singletonList(0));
+ Foo fooOther = new Foo(1, singletonList(1));
+ InPlaceMutator<Foo> emptyCombineMutator = combine();
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ emptyCombineMutator.crossOverInPlace(foo, fooOther, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(0);
+ assertThat(foo.getList()).containsExactly(0);
+ }
+
+ @Test
+ void testMutateAssemble() {
+ InPlaceMutator<Foo> valueMutator =
+ mutateProperty(Foo::getValue, mockMutator(21, value -> 2 * value), Foo::setValue);
+
+ InPlaceMutator<Foo> listMutator =
+ mutateViaView(Foo::getList, new InPlaceMutator<List<Integer>>() {
+ @Override
+ public void initInPlace(List<Integer> reference, PseudoRandom prng) {
+ reference.clear();
+ reference.add(21);
+ }
+
+ @Override
+ public void mutateInPlace(List<Integer> reference, PseudoRandom prng) {
+ reference.add(reference.get(reference.size() - 1) + 1);
+ }
+
+ @Override
+ public void crossOverInPlace(
+ List<Integer> reference, List<Integer> otherReference, PseudoRandom prng) {}
+
+ @Override
+ public String toDebugString(Predicate<Debuggable> isInCycle) {
+ return "List<Integer>";
+ }
+ });
+
+ SerializingInPlaceMutator<Foo> mutator =
+ assemble((m) -> {}, () -> new Foo(0, singletonList(0)), new Serializer<Foo>() {
+ @Override
+ public Foo read(DataInputStream in) {
+ return null;
+ }
+
+ @Override
+ public void write(Foo value, DataOutputStream out) {}
+
+ @Override
+ public Foo detach(Foo value) {
+ return null;
+ }
+ }, () -> combine(valueMutator, listMutator));
+
+ assertThat(mutator.toString()).isEqualTo("{Foo.Integer, Foo via List<Integer>}");
+
+ Foo foo = new Foo(13, singletonList(13));
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.initInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(21);
+ assertThat(foo.getList()).containsExactly(21);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* use valueMutator */ 0)) {
+ mutator.mutateInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(21);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* use listMutator */ 1)) {
+ mutator.mutateInPlace(foo, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(21, 22);
+ }
+
+ @Test
+ void testCrossOverAssemble() {
+ InPlaceMutator<Foo> valueMutator =
+ mutateProperty(Foo::getValue, mockCrossOver((a, b) -> 42), Foo::setValue);
+
+ InPlaceMutator<Foo> listMutator = mutateViaView(Foo::getList, mockCrossOverInPlace((a, b) -> {
+ a.clear();
+ a.add(42);
+ }));
+
+ SerializingInPlaceMutator<Foo> mutator =
+ assemble((m) -> {}, () -> new Foo(0, singletonList(0)), new Serializer<Foo>() {
+ @Override
+ public Foo read(DataInputStream in) {
+ return null;
+ }
+
+ @Override
+ public void write(Foo value, DataOutputStream out) {}
+
+ @Override
+ public Foo detach(Foo value) {
+ return null;
+ }
+ }, () -> combine(valueMutator, listMutator));
+
+ Foo foo = new Foo(0, singletonList(0));
+ Foo fooOther = new Foo(1, singletonList(1));
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // cross over in property mutator
+ 2)) {
+ mutator.crossOverInPlace(foo, fooOther, prng);
+ }
+ assertThat(foo.getValue()).isEqualTo(42);
+ assertThat(foo.getList()).containsExactly(42);
+ }
+
+ @Test
+ void testMutateThenMapToImmutable() throws IOException {
+ SerializingMutator<char[]> charMutator =
+ mockMutator(new char[] {'H', 'e', 'l', 'l', 'o'}, chars -> {
+ for (int i = 0; i < chars.length; i++) {
+ chars[i] ^= (1 << 5);
+ }
+ chars[chars.length - 1]++;
+ return chars;
+ });
+ SerializingMutator<String> mutator =
+ mutateThenMapToImmutable(charMutator, String::new, String::toCharArray);
+
+ assertThat(mutator.toString()).isEqualTo("char[] -> String");
+
+ String value = mutator.read(new DataInputStream(infiniteZeros()));
+ assertThat(value).isEqualTo("Hello");
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ value = mutator.mutate(value, prng);
+ }
+ assertThat(value).isEqualTo("hELLP");
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ value = mutator.mutate(value, prng);
+ }
+ assertThat(value).isEqualTo("Hellq");
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ value = mutator.init(prng);
+ }
+ assertThat(value).isEqualTo("Hello");
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ value = mutator.mutate(value, prng);
+ }
+ assertThat(value).isEqualTo("hELLP");
+
+ final String capturedValue = value;
+ assertThrows(UnsupportedOperationException.class,
+ () -> mutator.write(capturedValue, nullDataOutputStream()));
+ }
+
+ @Test
+ void testCrossOverThenMapToImmutable() {
+ SerializingMutator<char[]> charMutator = mockCrossOver((a, b) -> {
+ assertThat(a).isEqualTo(new char[] {'H', 'e', 'l', 'l', 'o'});
+ assertThat(b).isEqualTo(new char[] {'W', 'o', 'r', 'l', 'd'});
+ return new char[] {'T', 'e', 's', 't', 'e', 'd'};
+ });
+ SerializingMutator<String> mutator =
+ mutateThenMapToImmutable(charMutator, String::new, String::toCharArray);
+
+ String crossedOver;
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ crossedOver = mutator.crossOver("Hello", "World", prng);
+ }
+ assertThat(crossedOver).isEqualTo("Tested");
+ }
+
+ @Test
+ void testCrossOverProduct() {
+ SerializingMutator<Boolean> mutator1 = mockCrossOver((a, b) -> true);
+ SerializingMutator<Integer> mutator2 = mockCrossOver((a, b) -> 42);
+ ProductMutator mutator = mutateProduct(mutator1, mutator2);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // use first value in mutator1
+ 0,
+ // use second value in mutator2
+ 0)) {
+ Object[] crossedOver =
+ mutator.crossOver(new Object[] {false, 0}, new Object[] {true, 1}, prng);
+ assertThat(crossedOver).isEqualTo(new Object[] {false, 0});
+ }
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // use first value in mutator1
+ 1,
+ // use second value in mutator2
+ 1)) {
+ Object[] crossedOver =
+ mutator.crossOver(new Object[] {false, 0}, new Object[] {true, 1}, prng);
+ assertThat(crossedOver).isEqualTo(new Object[] {true, 1});
+ }
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // use cross over in mutator1
+ 2,
+ // use cross over in mutator2
+ 2)) {
+ Object[] crossedOver =
+ mutator.crossOver(new Object[] {false, 0}, new Object[] {true, 2}, prng);
+ assertThat(crossedOver).isEqualTo(new Object[] {true, 42});
+ }
+ }
+
+ @Test
+ void testCrossOverSumInPlaceSameType() {
+ ToIntFunction<List<Integer>> mutotarIndexFromValue = (r) -> 0;
+ InPlaceMutator<List<Integer>> mutator1 = mockCrossOverInPlace((a, b) -> { a.add(42); });
+ InPlaceMutator<List<Integer>> mutator2 = mockCrossOverInPlace((a, b) -> {});
+ InPlaceMutator<List<Integer>> mutator =
+ mutateSumInPlace(mutotarIndexFromValue, mutator1, mutator2);
+
+ List<Integer> a = new ArrayList<>();
+ List<Integer> b = new ArrayList<>();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.crossOverInPlace(a, b, prng);
+ }
+ assertThat(a).containsExactly(42);
+ }
+
+ @Test
+ void testCrossOverSumInPlaceIndeterminate() {
+ InPlaceMutator<List<?>> mutator1 = mockCrossOverInPlace((a, b) -> {});
+ InPlaceMutator<List<?>> mutator2 = mockCrossOverInPlace((a, b) -> {});
+ ToIntFunction<List<?>> bothIndeterminate = (r) -> - 1;
+
+ InPlaceMutator<List<?>> mutator = mutateSumInPlace(bothIndeterminate, mutator1, mutator2);
+
+ List<Integer> a = new ArrayList<>();
+ a.add(42);
+ List<Integer> b = new ArrayList<>();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.crossOverInPlace(a, b, prng);
+ assertThat(a).containsExactly(42);
+ }
+ }
+
+ @Test
+ void testCrossOverSumInPlaceFirstIndeterminate() {
+ List<Integer> reference = new ArrayList<>();
+ List<Integer> otherReference = new ArrayList<>();
+
+ InPlaceMutator<List<Integer>> mutator1 = mockCrossOverInPlace((a, b) -> {});
+ InPlaceMutator<List<Integer>> mutator2 = mockInitInPlace((l) -> { l.add(42); });
+ ToIntFunction<List<Integer>> firstIndeterminate = (r) -> r == reference ? -1 : 1;
+
+ InPlaceMutator<List<Integer>> mutator =
+ mutateSumInPlace(firstIndeterminate, mutator1, mutator2);
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.crossOverInPlace(reference, otherReference, prng);
+ assertThat(reference).containsExactly(42);
+ }
+ }
+
+ static class Foo {
+ private int value;
+ private final List<Integer> list;
+
+ public Foo(int value) {
+ this(value, new ArrayList<>());
+ }
+ public Foo(int value, List<Integer> list) {
+ this.value = value;
+ this.list = new ArrayList<>(list);
+ }
+
+ public List<Integer> getList() {
+ return list;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/engine/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/engine/BUILD.bazel
new file mode 100644
index 00000000..9cb59dee
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/engine/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
+
+java_test_suite(
+ name = "EngineTests",
+ size = "small",
+ srcs = glob(["*.java"]),
+ runner = "junit5",
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/engine",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandomTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandomTest.java
new file mode 100644
index 00000000..38ab2eb2
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandomTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.engine;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.counting;
+import static java.util.stream.Collectors.groupingBy;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.google.common.truth.Correspondence;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class SeededPseudoRandomTest {
+ static Stream<Arguments> doubleClosedRange() {
+ return Stream.of(arguments(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false),
+ arguments(Double.MAX_VALUE, Double.POSITIVE_INFINITY, false),
+ arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, false),
+ arguments(-Double.MAX_VALUE, Double.MAX_VALUE, false),
+ arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, false),
+ arguments(-Double.MAX_VALUE * 0.5, Double.MAX_VALUE * 0.5, false),
+ arguments(-Double.MAX_VALUE * 0.5, Math.nextUp(Double.MAX_VALUE * 0.5), false),
+ arguments(Double.MAX_VALUE, Double.MAX_VALUE, false),
+ arguments(-Double.MIN_VALUE, Double.MIN_VALUE, false),
+ arguments(-Double.MIN_VALUE, 0, false), arguments(0, Double.MIN_VALUE, false),
+ arguments(-Double.MAX_VALUE, 0, false), arguments(0, Double.MAX_VALUE, false),
+ arguments(1000.0, Double.MAX_VALUE, false), arguments(0, Double.POSITIVE_INFINITY, false),
+ arguments(1e200, Double.POSITIVE_INFINITY, false),
+ arguments(Double.NEGATIVE_INFINITY, -1e200, false), arguments(0.0, 1.0, false),
+ arguments(-1.0, 1.0, false), arguments(-1e300, 1e300, false),
+ arguments(0.0, 0.0 + Double.MIN_VALUE, false),
+ arguments(-Double.MAX_VALUE, -Double.MAX_VALUE + 1e292, false),
+ arguments(-Double.NaN, 0.0, true), arguments(0.0, Double.NaN, true),
+ arguments(Double.NaN, Double.NaN, true));
+ }
+
+ static Stream<Arguments> floatClosedRange() {
+ return Stream.of(arguments(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, false),
+ arguments(Float.MAX_VALUE, Float.POSITIVE_INFINITY, false),
+ arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, false),
+ arguments(-Float.MAX_VALUE, Float.MAX_VALUE, false),
+ arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, false),
+ arguments(Float.MAX_VALUE, Float.MAX_VALUE, false),
+ arguments(-Float.MAX_VALUE / 2f, Float.MAX_VALUE / 2f, false),
+ arguments(-Float.MIN_VALUE, Float.MIN_VALUE, false), arguments(-Float.MIN_VALUE, 0f, false),
+ arguments(0f, Float.MIN_VALUE, false), arguments(-Float.MAX_VALUE, 0f, false),
+ arguments(0f, Float.MAX_VALUE, false), arguments(-Float.MAX_VALUE, -0f, false),
+ arguments(-0f, Float.MAX_VALUE, false), arguments(1000f, Float.MAX_VALUE, false),
+ arguments(0f, Float.POSITIVE_INFINITY, false),
+ arguments(1e38f, Float.POSITIVE_INFINITY, false),
+ arguments(Float.NEGATIVE_INFINITY, -1e38f, false), arguments(0f, 1f, false),
+ arguments(-1f, 1f, false), arguments(-1e38f, 1e38f, false),
+ arguments(0f, 0f + Float.MIN_VALUE, false),
+ arguments(-Float.MAX_VALUE, -Float.MAX_VALUE + 1e32f, false),
+ arguments(-Float.NaN, 0f, true), arguments(0f, Float.NaN, true),
+ arguments(Float.NaN, Float.NaN, true));
+ }
+
+ @ParameterizedTest
+ @MethodSource("doubleClosedRange")
+ void testDoubleForceInRange(double minValue, double maxValue, boolean throwsException) {
+ SeededPseudoRandom seededPseudoRandom = new SeededPseudoRandom(1337);
+ for (int i = 0; i < 1000; i++) {
+ if (throwsException) {
+ assertThrows(IllegalArgumentException.class,
+ ()
+ -> seededPseudoRandom.closedRange(minValue, maxValue),
+ "minValue: " + minValue + ", maxValue: " + maxValue);
+ } else {
+ double inClosedRange = seededPseudoRandom.closedRange(minValue, maxValue);
+ assertThat(inClosedRange).isAtLeast(minValue);
+ assertThat(inClosedRange).isAtMost(maxValue);
+ assertThat(inClosedRange).isFinite();
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("floatClosedRange")
+ void testFloatForceInRange(float minValue, float maxValue, boolean throwsException) {
+ SeededPseudoRandom seededPseudoRandom = new SeededPseudoRandom(1337);
+ for (int i = 0; i < 1000; i++) {
+ if (throwsException) {
+ assertThrows(IllegalArgumentException.class,
+ ()
+ -> seededPseudoRandom.closedRange(minValue, maxValue),
+ "minValue: " + minValue + ", maxValue: " + maxValue);
+ } else {
+ float inClosedRange = seededPseudoRandom.closedRange(minValue, maxValue);
+ assertThat(inClosedRange).isAtLeast(minValue);
+ assertThat(inClosedRange).isAtMost(maxValue);
+ assertThat(inClosedRange).isFinite();
+ }
+ }
+ }
+
+ @Test
+ void testClosedRangeBiasedTowardsSmall() {
+ SeededPseudoRandom prng = new SeededPseudoRandom(1337133371337L);
+
+ assertThrows(IllegalArgumentException.class, () -> prng.closedRangeBiasedTowardsSmall(-1));
+ assertThrows(IllegalArgumentException.class, () -> prng.closedRangeBiasedTowardsSmall(2, 1));
+ assertThat(prng.closedRangeBiasedTowardsSmall(0)).isEqualTo(0);
+ assertThat(prng.closedRangeBiasedTowardsSmall(5, 5)).isEqualTo(5);
+ }
+
+ @Test
+ void testClosedRangeBiasedTowardsSmall_distribution() {
+ int num = 5000000;
+ SeededPseudoRandom prng = new SeededPseudoRandom(1337133371337L);
+ Map<Integer, Double> frequencies =
+ Stream.generate(() -> prng.closedRangeBiasedTowardsSmall(9))
+ .limit(num)
+ .collect(
+ groupingBy(i -> i, collectingAndThen(counting(), count -> ((double) count) / num)));
+ // Reference values obtained from
+ // https://www.wolframalpha.com/input?i=N%5BTable%5BPDF%5BZipfDistribution%5B10%2C+1%5D%2C+i%5D%2C+%7Bi%2C+1%2C+10%7D%5D%5D
+ assertThat(frequencies)
+ .comparingValuesUsing(Correspondence.tolerance(0.0005))
+ .containsExactly(0, 0.645, 1, 0.161, 2, 0.072, 3, 0.040, 4, 0.026, 5, 0.018, 6, 0.013, 7,
+ 0.01, 8, 0.008, 9, 0.006);
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel
new file mode 100644
index 00000000..26943353
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel
@@ -0,0 +1,28 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_junit5_test")
+
+TEST_PARALLELISM = 4
+
+java_junit5_test(
+ name = "StressTest",
+ size = "large",
+ srcs = ["StressTest.java"],
+ env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"},
+ jvm_flags = [
+ "-Djunit.jupiter.execution.parallel.enabled=true",
+ "-Djunit.jupiter.execution.parallel.mode.default=concurrent",
+ "-Djunit.jupiter.execution.parallel.config.strategy=fixed",
+ "-Djunit.jupiter.execution.parallel.config.fixed.parallelism=" + str(TEST_PARALLELISM),
+ ],
+ tags = ["cpu:" + str(TEST_PARALLELISM)],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto:proto2_java_proto",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto:proto3_java_proto",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ "@com_google_protobuf_protobuf_java//jar",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java
new file mode 100644
index 00000000..3bf880a4
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator;
+
+import static com.code_intelligence.jazzer.mutation.mutator.Mutators.validateAnnotationUsage;
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithZeros;
+import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.anyPseudoRandom;
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asAnnotatedType;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.lang.Math.floor;
+import static java.lang.Math.pow;
+import static java.lang.Math.sqrt;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.IntStream.rangeClosed;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange;
+import com.code_intelligence.jazzer.mutation.annotation.FloatInRange;
+import com.code_intelligence.jazzer.mutation.annotation.InRange;
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.annotation.WithSize;
+import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource;
+import com.code_intelligence.jazzer.mutation.annotation.proto.WithDefaultInstance;
+import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
+import com.code_intelligence.jazzer.mutation.api.Serializer;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import com.code_intelligence.jazzer.protobuf.Proto2.TestProtobuf;
+import com.code_intelligence.jazzer.protobuf.Proto3.AnyField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.BytesField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.DoubleField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3.TestEnum;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3.TestEnumRepeated;
+import com.code_intelligence.jazzer.protobuf.Proto3.FloatField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.IntegralField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.MapField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.MessageField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.MessageMapField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.OptionalPrimitiveField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.PrimitiveField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedDoubleField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedFloatField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedIntegralField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedRecursiveMessageField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.SingleOptionOneOfField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.StringField3;
+import com.google.protobuf.Any;
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.Message;
+import com.google.protobuf.Message.Builder;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.AnnotatedType;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class StressTest {
+ private static final int NUM_INITS = 500;
+ private static final int NUM_MUTATE_PER_INIT = 100;
+ private static final double MANY_DISTINCT_ELEMENTS_RATIO = 0.5;
+
+ private enum TestEnumTwo { A, B }
+
+ private enum TestEnumThree { A, B, C }
+
+ @SuppressWarnings("unused")
+ static Message getTestProtobufDefaultInstance() {
+ return TestProtobuf.getDefaultInstance();
+ }
+
+ public static Stream<Arguments> stressTestCases() {
+ return Stream.of(arguments(asAnnotatedType(boolean.class), "Boolean", exactly(false, true),
+ exactly(false, true)),
+ arguments(new TypeHolder<@NotNull Boolean>() {}.annotatedType(), "Boolean",
+ exactly(false, true), exactly(false, true)),
+ arguments(new TypeHolder<Boolean>() {}.annotatedType(), "Nullable<Boolean>",
+ exactly(null, false, true), exactly(null, false, true)),
+ arguments(new TypeHolder<@NotNull List<@NotNull Boolean>>() {}.annotatedType(),
+ "List<Boolean>", exactly(emptyList(), singletonList(false), singletonList(true)),
+ manyDistinctElements()),
+ arguments(new TypeHolder<@NotNull List<Boolean>>() {}.annotatedType(),
+ "List<Nullable<Boolean>>",
+ exactly(emptyList(), singletonList(null), singletonList(false), singletonList(true)),
+ manyDistinctElements()),
+ arguments(new TypeHolder<List<@NotNull Boolean>>() {}.annotatedType(),
+ "Nullable<List<Boolean>>",
+ exactly(null, emptyList(), singletonList(false), singletonList(true)),
+ distinctElementsRatio(0.30)),
+ arguments(new TypeHolder<List<Boolean>>() {}.annotatedType(),
+ "Nullable<List<Nullable<Boolean>>>",
+ exactly(
+ null, emptyList(), singletonList(null), singletonList(false), singletonList(true)),
+ distinctElementsRatio(0.30)),
+ arguments(
+ new TypeHolder<@NotNull Map<@NotNull String, @NotNull String>>() {}.annotatedType(),
+ "Map<String,String>", distinctElementsRatio(0.45), distinctElementsRatio(0.45)),
+ arguments(new TypeHolder<Map<@NotNull String, @NotNull String>>() {}.annotatedType(),
+ "Nullable<Map<String,String>>", distinctElementsRatio(0.46),
+ distinctElementsRatio(0.48)),
+ arguments(
+ new TypeHolder<@WithSize(max = 3) @NotNull Map<@NotNull Integer, @NotNull Integer>>() {
+ }.annotatedType(),
+ "Map<Integer,Integer>",
+ // Half of all maps are empty, the other half is heavily biased towards special values.
+ all(mapSizeInClosedRange(0, 3), distinctElementsRatio(0.2)),
+ all(mapSizeInClosedRange(0, 3), manyDistinctElements())),
+ arguments(
+ new TypeHolder<@NotNull Map<@NotNull Boolean, @NotNull Boolean>>() {}.annotatedType(),
+ "Map<Boolean,Boolean>",
+ // 1 0-element map, 4 1-element maps
+ distinctElements(1 + 4),
+ // 1 0-element map, 4 1-element maps, 4 2-element maps
+ distinctElements(1 + 4 + 4)),
+ arguments(asAnnotatedType(byte.class), "Byte",
+ // init is heavily biased towards special values and only returns a uniformly random
+ // value in 1 out of 5 calls.
+ all(expectedNumberOfDistinctElements(1 << Byte.SIZE, boundHits(NUM_INITS, 0.2)),
+ contains((byte) 0, (byte) 1, Byte.MIN_VALUE, Byte.MAX_VALUE)),
+ // With mutations, we expect to reach all possible bytes.
+ exactly(rangeClosed(Byte.MIN_VALUE, Byte.MAX_VALUE).mapToObj(i -> (byte) i).toArray())),
+ arguments(asAnnotatedType(short.class), "Short",
+ // init is heavily biased towards special values and only returns a uniformly random
+ // value in 1 out of 5 calls.
+ all(expectedNumberOfDistinctElements(1 << Short.SIZE, boundHits(NUM_INITS, 0.2)),
+ contains((short) 0, (short) 1, Short.MIN_VALUE, Short.MAX_VALUE)),
+ // The integral type mutator does not always return uniformly random values and the
+ // random walk it uses is more likely to produce non-distinct elements, hence the test
+ // only passes with ~90% of the optimal parameters.
+ expectedNumberOfDistinctElements(
+ 1 << Short.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
+ arguments(asAnnotatedType(int.class), "Integer",
+ // init is heavily biased towards special values and only returns a uniformly random
+ // value in 1 out of 5 calls.
+ all(expectedNumberOfDistinctElements(1L << Integer.SIZE, boundHits(NUM_INITS, 0.2)),
+ contains(0, 1, Integer.MIN_VALUE, Integer.MAX_VALUE)),
+ // See "Short" case.
+ expectedNumberOfDistinctElements(
+ 1L << Integer.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
+ arguments(new TypeHolder<@NotNull @InRange(min = 0) Long>() {}.annotatedType(), "Long",
+ // init is heavily biased towards special values and only returns a uniformly random
+ // value in 1 out of 5 calls.
+ all(expectedNumberOfDistinctElements(1L << Long.SIZE - 1, boundHits(NUM_INITS, 0.2)),
+ contains(0L, 1L, Long.MAX_VALUE)),
+ // See "Short" case.
+ expectedNumberOfDistinctElements(
+ 1L << Integer.SIZE - 1, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
+ arguments(
+ new TypeHolder<@NotNull @InRange(max = Integer.MIN_VALUE + 5) Integer>() {
+ }.annotatedType(),
+ "Integer",
+ exactly(rangeClosed(Integer.MIN_VALUE, Integer.MIN_VALUE + 5).boxed().toArray()),
+ exactly(rangeClosed(Integer.MIN_VALUE, Integer.MIN_VALUE + 5).boxed().toArray())),
+ arguments(asAnnotatedType(TestEnumTwo.class), "Nullable<Enum<TestEnumTwo>>",
+ exactly(null, TestEnumTwo.A, TestEnumTwo.B),
+ exactly(null, TestEnumTwo.A, TestEnumTwo.B)),
+ arguments(asAnnotatedType(TestEnumThree.class), "Nullable<Enum<TestEnumThree>>",
+ exactly(null, TestEnumThree.A, TestEnumThree.B, TestEnumThree.C),
+ exactly(null, TestEnumThree.A, TestEnumThree.B, TestEnumThree.C)),
+ arguments(new TypeHolder<@NotNull @FloatInRange(min = 0f) Float>() {}.annotatedType(),
+ "Float",
+ all(distinctElementsRatio(0.45),
+ doesNotContain(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, -Float.MIN_VALUE),
+ contains(Float.NaN, Float.POSITIVE_INFINITY, Float.MAX_VALUE, Float.MIN_VALUE, 0.0f,
+ -0.0f)),
+ all(distinctElementsRatio(0.75),
+ doesNotContain(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, -Float.MIN_VALUE))),
+ arguments(new TypeHolder<@NotNull Float>() {}.annotatedType(), "Float",
+ all(distinctElementsRatio(0.45),
+ contains(Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY,
+ -Float.MAX_VALUE, Float.MAX_VALUE, -Float.MIN_VALUE, Float.MIN_VALUE, 0.0f,
+ -0.0f)),
+ distinctElementsRatio(0.76)),
+ arguments(
+ new TypeHolder<@NotNull @FloatInRange(
+ min = -1.0f, max = 1.0f, allowNaN = false) Float>() {
+ }.annotatedType(),
+ "Float",
+ all(distinctElementsRatio(0.45),
+ doesNotContain(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY),
+ contains(-Float.MIN_VALUE, Float.MIN_VALUE, 0.0f, -0.0f)),
+ all(distinctElementsRatio(0.525),
+ doesNotContain(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY),
+ contains(-Float.MIN_VALUE, Float.MIN_VALUE, 0.0f, -0.0f))),
+ arguments(new TypeHolder<@NotNull Double>() {}.annotatedType(), "Double",
+ all(distinctElementsRatio(0.45),
+ contains(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)),
+ distinctElementsRatio(0.75)),
+ arguments(
+ new TypeHolder<@NotNull @DoubleInRange(
+ min = -1.0, max = 1.0, allowNaN = false) Double>() {
+ }.annotatedType(),
+ "Double", all(distinctElementsRatio(0.45), doesNotContain(Double.NaN)),
+ all(distinctElementsRatio(0.55), doesNotContain(Double.NaN))));
+ }
+
+ public static Stream<Arguments> protoStressTestCases() {
+ return Stream.of(
+ arguments(new TypeHolder<@NotNull OptionalPrimitiveField3>() {}.annotatedType(),
+ "{Builder.Nullable<Boolean>} -> Message",
+ exactly(OptionalPrimitiveField3.newBuilder().build(),
+ OptionalPrimitiveField3.newBuilder().setSomeField(false).build(),
+ OptionalPrimitiveField3.newBuilder().setSomeField(true).build()),
+ exactly(OptionalPrimitiveField3.newBuilder().build(),
+ OptionalPrimitiveField3.newBuilder().setSomeField(false).build(),
+ OptionalPrimitiveField3.newBuilder().setSomeField(true).build())),
+ arguments(new TypeHolder<@NotNull RepeatedRecursiveMessageField3>() {}.annotatedType(),
+ "{Builder.Boolean, WithoutInit(Builder via List<(cycle) -> Message>)} -> Message",
+ // The message field is recursive and thus not initialized.
+ exactly(RepeatedRecursiveMessageField3.getDefaultInstance(),
+ RepeatedRecursiveMessageField3.newBuilder().setSomeField(true).build()),
+ manyDistinctElements()),
+ arguments(new TypeHolder<@NotNull IntegralField3>() {}.annotatedType(),
+ "{Builder.Integer} -> Message",
+ // init is heavily biased towards special values and only returns a uniformly random
+ // value in 1 out of 5 calls.
+ all(expectedNumberOfDistinctElements(1L << Integer.SIZE, boundHits(NUM_INITS, 0.2)),
+ contains(IntegralField3.newBuilder().build(),
+ IntegralField3.newBuilder().setSomeField(1).build(),
+ IntegralField3.newBuilder().setSomeField(Integer.MIN_VALUE).build(),
+ IntegralField3.newBuilder().setSomeField(Integer.MAX_VALUE).build())),
+ // Our mutations return uniformly random elements in ~3/8 of all cases.
+ expectedNumberOfDistinctElements(
+ 1L << Integer.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 3 / 8)),
+ arguments(new TypeHolder<@NotNull RepeatedIntegralField3>() {}.annotatedType(),
+ "{Builder via List<Integer>} -> Message",
+ contains(RepeatedIntegralField3.getDefaultInstance(),
+ RepeatedIntegralField3.newBuilder().addSomeField(0).build(),
+ RepeatedIntegralField3.newBuilder().addSomeField(1).build(),
+ RepeatedIntegralField3.newBuilder().addSomeField(Integer.MAX_VALUE).build(),
+ RepeatedIntegralField3.newBuilder().addSomeField(Integer.MIN_VALUE).build()),
+ // TODO: This ratio is on the lower end, most likely because of the strong bias towards
+ // special values combined with the small initial size of the list. When we improve the
+ // list mutator, this may be increased.
+ distinctElementsRatio(0.25)),
+ arguments(new TypeHolder<@NotNull BytesField3>() {}.annotatedType(),
+ "{Builder.byte[] -> ByteString} -> Message", manyDistinctElements(),
+ manyDistinctElements()),
+ arguments(new TypeHolder<@NotNull StringField3>() {}.annotatedType(),
+ "{Builder.String} -> Message", manyDistinctElements(), manyDistinctElements()),
+ arguments(new TypeHolder<@NotNull EnumField3>() {}.annotatedType(),
+ "{Builder.Enum<TestEnum>} -> Message",
+ exactly(EnumField3.getDefaultInstance(),
+ EnumField3.newBuilder().setSomeField(TestEnum.VAL2).build()),
+ exactly(EnumField3.getDefaultInstance(),
+ EnumField3.newBuilder().setSomeField(TestEnum.VAL2).build())),
+ arguments(new TypeHolder<@NotNull EnumFieldRepeated3>() {}.annotatedType(),
+ "{Builder via List<Enum<TestEnumRepeated>>} -> Message",
+ exactly(EnumFieldRepeated3.getDefaultInstance(),
+ EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.UNASSIGNED).build(),
+ EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.VAL1).build(),
+ EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.VAL2).build()),
+ manyDistinctElements()),
+ arguments(new TypeHolder<@NotNull MapField3>() {}.annotatedType(),
+ "{Builder.Map<Integer,String>} -> Message", distinctElementsRatio(0.47),
+ manyDistinctElements()),
+ arguments(new TypeHolder<@NotNull MessageMapField3>() {}.annotatedType(),
+ "{Builder.Map<String,{Builder.Map<Integer,String>} -> Message>} -> Message",
+ distinctElementsRatio(0.45), distinctElementsRatio(0.45)),
+ arguments(new TypeHolder<@NotNull DoubleField3>() {}.annotatedType(),
+ "{Builder.Double} -> Message", distinctElementsRatio(0.45), distinctElementsRatio(0.7)),
+ arguments(new TypeHolder<@NotNull RepeatedDoubleField3>() {}.annotatedType(),
+ "{Builder via List<Double>} -> Message", distinctElementsRatio(0.2),
+ distinctElementsRatio(0.9)),
+ arguments(new TypeHolder<@NotNull FloatField3>() {}.annotatedType(),
+ "{Builder.Float} -> Message", distinctElementsRatio(0.45), distinctElementsRatio(0.7)),
+ arguments(new TypeHolder<@NotNull RepeatedFloatField3>() {}.annotatedType(),
+ "{Builder via List<Float>} -> Message", distinctElementsRatio(0.20),
+ distinctElementsRatio(0.9), emptyList()),
+ arguments(new TypeHolder<@NotNull TestProtobuf>() {}.annotatedType(),
+ "{Builder.Nullable<Boolean>, Builder.Nullable<Integer>, Builder.Nullable<Integer>, Builder.Nullable<Long>, Builder.Nullable<Long>, Builder.Nullable<Float>, Builder.Nullable<Double>, Builder.Nullable<String>, Builder.Nullable<Enum<Enum>>, WithoutInit(Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>, WithoutInit(Builder.Nullable<(cycle) -> Message>)} -> Message>), Builder via List<Boolean>, Builder via List<Integer>, Builder via List<Integer>, Builder via List<Long>, Builder via List<Long>, Builder via List<Float>, Builder via List<Double>, Builder via List<String>, Builder via List<Enum<Enum>>, WithoutInit(Builder via List<(cycle) -> Message>), Builder.Map<Integer,Integer>, Builder.Nullable<FixedValue(OnlyLabel)>, Builder.Nullable<{<empty>} -> Message>, Builder.Nullable<Integer> | Builder.Nullable<Long> | Builder.Nullable<Integer>} -> Message",
+ manyDistinctElements(), manyDistinctElements()),
+ arguments(
+ new TypeHolder<@NotNull @WithDefaultInstance(
+ "com.code_intelligence.jazzer.mutation.mutator.StressTest#getTestProtobufDefaultInstance")
+ Message>() {
+ }.annotatedType(),
+ "{Builder.Nullable<Boolean>, Builder.Nullable<Integer>, Builder.Nullable<Integer>, Builder.Nullable<Long>, Builder.Nullable<Long>, Builder.Nullable<Float>, Builder.Nullable<Double>, Builder.Nullable<String>, Builder.Nullable<Enum<Enum>>, WithoutInit(Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>, WithoutInit(Builder.Nullable<(cycle) -> Message>)} -> Message>), Builder via List<Boolean>, Builder via List<Integer>, Builder via List<Integer>, Builder via List<Long>, Builder via List<Long>, Builder via List<Float>, Builder via List<Double>, Builder via List<String>, Builder via List<Enum<Enum>>, WithoutInit(Builder via List<(cycle) -> Message>), Builder.Map<Integer,Integer>, Builder.Nullable<FixedValue(OnlyLabel)>, Builder.Nullable<{<empty>} -> Message>, Builder.Nullable<Integer> | Builder.Nullable<Long> | Builder.Nullable<Integer>} -> Message",
+ manyDistinctElements(), manyDistinctElements()),
+ arguments(
+ new TypeHolder<@NotNull @AnySource(
+ {PrimitiveField3.class, MessageField3.class}) AnyField3>() {
+ }.annotatedType(),
+ "{Builder.Nullable<Builder.{Builder.Boolean} -> Message | Builder.{Builder.Nullable<(cycle) -> Message>} -> Message -> Message>} -> Message",
+ exactly(AnyField3.getDefaultInstance(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(PrimitiveField3.getDefaultInstance()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(PrimitiveField3.newBuilder().setSomeField(true).build()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(MessageField3.getDefaultInstance()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(
+ Any.pack(MessageField3.newBuilder()
+ .setMessageField(PrimitiveField3.getDefaultInstance())
+ .build()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(
+ MessageField3.newBuilder()
+ .setMessageField(PrimitiveField3.newBuilder().setSomeField(true))
+ .build()))
+ .build()),
+ exactly(AnyField3.getDefaultInstance(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(PrimitiveField3.getDefaultInstance()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(PrimitiveField3.newBuilder().setSomeField(true).build()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(MessageField3.getDefaultInstance()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(
+ Any.pack(MessageField3.newBuilder()
+ .setMessageField(PrimitiveField3.getDefaultInstance())
+ .build()))
+ .build(),
+ AnyField3.newBuilder()
+ .setSomeField(Any.pack(
+ MessageField3.newBuilder()
+ .setMessageField(PrimitiveField3.newBuilder().setSomeField(true))
+ .build()))
+ .build())),
+ arguments(new TypeHolder<@NotNull SingleOptionOneOfField3>() {}.annotatedType(),
+ "{Builder.Nullable<Boolean>} -> Message",
+ exactly(SingleOptionOneOfField3.getDefaultInstance(),
+ SingleOptionOneOfField3.newBuilder().setBoolField(false).build(),
+ SingleOptionOneOfField3.newBuilder().setBoolField(true).build()),
+ exactly(SingleOptionOneOfField3.getDefaultInstance(),
+ SingleOptionOneOfField3.newBuilder().setBoolField(false).build(),
+ SingleOptionOneOfField3.newBuilder().setBoolField(true).build())));
+ }
+
+ @SafeVarargs
+ private static Consumer<List<Object>> all(Consumer<List<Object>>... checks) {
+ return list -> {
+ for (Consumer<List<Object>> check : checks) {
+ check.accept(list);
+ }
+ };
+ }
+
+ private static Consumer<List<Object>> distinctElements(int num) {
+ return list -> assertThat(new HashSet<>(list).size()).isAtLeast(num);
+ }
+
+ private static Consumer<List<Object>> manyDistinctElements() {
+ return distinctElementsRatio(MANY_DISTINCT_ELEMENTS_RATIO);
+ }
+
+ /**
+ * Returns a lower bound on the expected number of hits when sampling from a domain of a given
+ * size with the given probability.
+ */
+ private static int boundHits(long domainSize, double probability) {
+ // Binomial distribution.
+ double expectedValue = domainSize * probability;
+ double variance = domainSize * probability * (1 - probability);
+ double standardDeviation = sqrt(variance);
+ // Allow missing the expected value by two standard deviations. For a normal distribution,
+ // this would correspond to 95% of all cases.
+ int almostCertainLowerBound = (int) floor(expectedValue - 2 * standardDeviation);
+ return almostCertainLowerBound;
+ }
+
+ /**
+ * Asserts that a given list contains at least as many distinct elements as can be expected when
+ * picking {@code picks} out of {@code domainSize} elements uniformly at random.
+ */
+ private static Consumer<List<Object>> expectedNumberOfDistinctElements(
+ long domainSize, int picks) {
+ // https://www.randomservices.org/random/urn/Birthday.html#mom2
+ double expectedValue = domainSize * (1 - pow(1 - 1.0 / domainSize, picks));
+ double variance = domainSize * (domainSize - 1) * pow(1 - 2.0 / domainSize, picks)
+ + domainSize * pow(1 - 1.0 / domainSize, picks)
+ - domainSize * domainSize * pow(1 - 1.0 / domainSize, 2 * picks);
+ double standardDeviation = sqrt(variance);
+ // Allow missing the expected value by two standard deviations. For a normal distribution,
+ // this would correspond to 95% of all cases.
+ int almostCertainLowerBound = (int) floor(expectedValue - 2 * standardDeviation);
+ return list
+ -> assertWithMessage("V=distinct elements among %s picked out of %s\nE[V]=%s\nσ[V]=%s",
+ picks, domainSize, expectedValue, standardDeviation)
+ .that(new HashSet<>(list).size())
+ .isAtLeast(almostCertainLowerBound);
+ }
+
+ private static Consumer<List<Object>> distinctElementsRatio(double ratio) {
+ require(ratio > 0);
+ require(ratio <= 1);
+ return list -> assertThat(new HashSet<>(list).size() / (double) list.size()).isAtLeast(ratio);
+ }
+
+ private static Consumer<List<Object>> exactly(Object... expected) {
+ return list -> assertThat(new HashSet<>(list)).containsExactly(expected);
+ }
+
+ private static Consumer<List<Object>> contains(Object... expected) {
+ return list -> assertThat(new HashSet<>(list)).containsAtLeastElementsIn(expected);
+ }
+
+ private static Consumer<List<Object>> doesNotContain(Object... expected) {
+ return list -> assertThat(new HashSet<>(list)).containsNoneIn(expected);
+ }
+
+ private static Consumer<List<Object>> mapSizeInClosedRange(int min, int max) {
+ return list -> {
+ list.forEach(map -> {
+ if (map instanceof Map) {
+ assertThat(((Map) map).size()).isAtLeast(min);
+ assertThat(((Map) map).size()).isAtMost(max);
+ } else {
+ throw new IllegalArgumentException(
+ "Expected a list of maps, got list of" + map.getClass().getName());
+ }
+ });
+ };
+ }
+
+ @ParameterizedTest(name = "{index} {0}, {1}")
+ @MethodSource({"stressTestCases", "protoStressTestCases"})
+ void genericMutatorStressTest(AnnotatedType type, String mutatorTree,
+ Consumer<List<Object>> expectedInitValues, Consumer<List<Object>> expectedMutatedValues)
+ throws IOException {
+ validateAnnotationUsage(type);
+ SerializingMutator mutator = Mutators.newFactory().createOrThrow(type);
+ assertThat(mutator.toString()).isEqualTo(mutatorTree);
+
+ // Even with a fallback to mutating map values when no new key can be constructed, the map
+ // {false: true, true: false} will not change its equality class when the fallback picks both
+ // values to mutate.
+ boolean mayPerformNoopMutations =
+ mutatorTree.contains("FixedValue(") || mutatorTree.contains("Map<Boolean,Boolean>");
+
+ PseudoRandom rng = anyPseudoRandom();
+
+ List<Object> initValues = new ArrayList<>();
+ List<Object> mutatedValues = new ArrayList<>();
+ for (int i = 0; i < NUM_INITS; i++) {
+ Object value = mutator.init(rng);
+
+ // For proto messages, each float field with value -0.0f, and double field with value -0.0
+ // will be converted to 0.0f and 0.0, respectively.
+ Object fixedValue = fixFloatingPointsForProtos(value);
+ testReadWriteRoundtrip(mutator, fixedValue);
+ testReadWriteExclusiveRoundtrip(mutator, fixedValue);
+
+ initValues.add(mutator.detach(value));
+ value = fixFloatingPointsForProtos(value);
+
+ for (int mutation = 0; mutation < NUM_MUTATE_PER_INIT; mutation++) {
+ Object detachedOldValue = mutator.detach(value);
+ value = mutator.mutate(value, rng);
+ if (!mayPerformNoopMutations) {
+ if (value instanceof Double) {
+ assertThat(Double.compare((Double) value, (Double) detachedOldValue)).isNotEqualTo(0);
+ } else if (value instanceof Float) {
+ assertThat(Float.compare((Float) value, (Float) detachedOldValue)).isNotEqualTo(0);
+ } else {
+ assertThat(detachedOldValue).isNotEqualTo(value);
+ }
+ }
+
+ mutatedValues.add(mutator.detach(value));
+
+ // For proto messages, each float field with value -0.0f, and double field with value -0.0
+ // will be converted to 0.0f and 0.0, respectively. This is because the values -0f and 0f
+ // and their double counterparts are serialized as default values (0f, and 0.0), which is
+ // relevant for mutation and the round trip tests. This means that the protos with float or
+ // double fields that equal to negative zero, will start mutation from positive zeros, and
+ // cause the assertion above to fail from time to time. To avoid this, we convert all
+ // negative zeros to positive zeros for float and double proto fields.
+ value = fixFloatingPointsForProtos(value);
+ testReadWriteRoundtrip(mutator, fixedValue);
+ testReadWriteExclusiveRoundtrip(mutator, fixedValue);
+ }
+ }
+
+ expectedInitValues.accept(initValues);
+ expectedMutatedValues.accept(mutatedValues);
+ }
+
+ private static <T> void testReadWriteExclusiveRoundtrip(Serializer<T> serializer, T value)
+ throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ serializer.writeExclusive(value, out);
+ T newValue = serializer.readExclusive(new ByteArrayInputStream(out.toByteArray()));
+ assertThat(newValue).isEqualTo(value);
+ }
+
+ private static <T> void testReadWriteRoundtrip(Serializer<T> serializer, T value)
+ throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ serializer.write(value, new DataOutputStream(out));
+ T newValue = serializer.read(
+ new DataInputStream(extendWithZeros(new ByteArrayInputStream(out.toByteArray()))));
+ assertThat(newValue).isEqualTo(value);
+ }
+
+ // Filter out floating point values -0.0f and -0.0 and replace them
+ // by 0.0f and 0.0 respectively.
+ // This is a workaround for a bug in the protobuf library that causes
+ // our "...RoundTrip" tests to fail for negative zero in floats and doubles.
+ private static <T> T fixFloatingPointsForProtos(T value) {
+ if (!(value instanceof Message)) {
+ return value;
+ }
+ Message.Builder builder = ((Message) value).toBuilder();
+ walkFields(builder, oldValue -> {
+ if (Objects.equals(oldValue, -0.0)) {
+ return 0.0;
+ } else if (Objects.equals(oldValue, -0.0f)) {
+ return 0.0f;
+ } else {
+ return oldValue;
+ }
+ });
+ return (T) builder.build();
+ }
+
+ private static void walkFields(Builder builder, Function<Object, Object> transform) {
+ for (FieldDescriptor field : builder.getDescriptorForType().getFields()) {
+ if (field.isRepeated()) {
+ int bound = builder.getRepeatedFieldCount(field);
+ for (int i = 0; i < bound; i++) {
+ if (field.getJavaType() == JavaType.MESSAGE) {
+ Builder repeatedFieldBuilder =
+ ((Message) builder.getRepeatedField(field, i)).toBuilder();
+ walkFields(repeatedFieldBuilder, transform);
+ builder.setRepeatedField(field, i, repeatedFieldBuilder.build());
+ } else {
+ builder.setRepeatedField(field, i, transform.apply(builder.getRepeatedField(field, i)));
+ }
+ }
+ } else if (field.getJavaType() == JavaType.MESSAGE) {
+ // Break up unbounded recursion.
+ if (!builder.hasField(field)) {
+ continue;
+ }
+ Builder fieldBuilder = ((Message) builder.getField(field)).toBuilder();
+ walkFields(fieldBuilder, transform);
+ builder.setField(field, fieldBuilder.build());
+ } else {
+ builder.setField(field, transform.apply(builder.getField(field)));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel
new file mode 100644
index 00000000..2e60b9d5
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel
@@ -0,0 +1,17 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
+
+java_test_suite(
+ name = "CollectionTests",
+ size = "small",
+ srcs = glob(["*.java"]),
+ env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"},
+ runner = "junit5",
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java
new file mode 100644
index 00000000..2fa0c1cf
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.collection;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMap;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMutableList;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockInitializer;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockMutator;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+
+class ChunkMutationsTest {
+ @Test
+ void testDeleteRandomChunk() {
+ List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6).collect(toList());
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ ChunkMutations.deleteRandomChunk(list, 2, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 3, 6).inOrder();
+ }
+
+ @Test
+ void testInsertRandomChunk() {
+ List<String> list = Stream.of("1", "2", "3", "4", "5", "6").collect(toList());
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ ChunkMutations.insertRandomChunk(list, 10, mockInitializer(() -> "7", String::new), prng);
+ }
+ assertThat(list).containsExactly("1", "2", "3", "7", "7", "4", "5", "6").inOrder();
+ String firstNewValue = list.get(3);
+ String secondNewValue = list.get(4);
+ assertThat(firstNewValue).isEqualTo(secondNewValue);
+ // Verify that the individual new elements were detached.
+ assertThat(firstNewValue).isNotSameInstanceAs(secondNewValue);
+ }
+
+ @Test
+ void testInsertRandomChunkSet() {
+ Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6).collect(toCollection(LinkedHashSet::new));
+
+ Queue<Integer> initReturnValues =
+ Stream.of(7, 7, 7, 8, 9, 9).collect(toCollection(ArrayDeque::new));
+ boolean result;
+ try (MockPseudoRandom prng = mockPseudoRandom(3)) {
+ result = ChunkMutations.insertRandomChunk(
+ set, set::add, 10, mockInitializer(initReturnValues::remove, v -> v), prng);
+ }
+ assertThat(result).isTrue();
+ assertThat(set).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9).inOrder();
+ }
+
+ @Test
+ void testInsertRandomChunkSet_largeChunk() {
+ Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6).collect(toCollection(LinkedHashSet::new));
+
+ Queue<Integer> initReturnValues =
+ IntStream.rangeClosed(1, 10000).boxed().collect(toCollection(ArrayDeque::new));
+ boolean result;
+ try (MockPseudoRandom prng = mockPseudoRandom(9994)) {
+ result = ChunkMutations.insertRandomChunk(
+ set, set::add, 10000, mockInitializer(initReturnValues::remove, v -> v), prng);
+ }
+ assertThat(result).isTrue();
+ assertThat(set)
+ .containsExactlyElementsIn(IntStream.rangeClosed(1, 10000).boxed().toArray())
+ .inOrder();
+ }
+
+ @Test
+ void testInsertRandomChunkSet_failsToConstructDistinctValues() {
+ Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6).collect(toCollection(LinkedHashSet::new));
+
+ Queue<Integer> initReturnValues =
+ Stream.concat(Stream.of(7, 7, 7, 8), Stream.generate(() -> 7).limit(1000))
+ .collect(toCollection(ArrayDeque::new));
+ boolean result;
+ try (MockPseudoRandom prng = mockPseudoRandom(3)) {
+ result = ChunkMutations.insertRandomChunk(
+ set, set::add, 10, mockInitializer(initReturnValues::remove, v -> v), prng);
+ }
+ assertThat(result).isFalse();
+ assertThat(set).containsExactly(1, 2, 3, 4, 5, 6, 7, 8).inOrder();
+ }
+
+ @Test
+ void testMutateChunk() {
+ List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6).collect(toList());
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ ChunkMutations.mutateRandomChunk(list, mockMutator(1, i -> 2 * i), prng);
+ }
+ assertThat(list).containsExactly(1, 2, 3, 8, 10, 6).inOrder();
+ }
+
+ @Test
+ void testMutateRandomValuesChunk() {
+ Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ ChunkMutations.mutateRandomValuesChunk(map, mockMutator(1, i -> 2 * i), prng);
+ }
+ assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 4, 80, 5, 100, 6, 60).inOrder();
+ }
+
+ @Test
+ void testMutateRandomKeysChunk() {
+ Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20,
+ asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60);
+ SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> {
+ List<Integer> newList = list.stream().map(i -> i + 1).collect(toList());
+ list.clear();
+ return newList;
+ }, ArrayList::new);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng);
+ assertThat(result).isTrue();
+ }
+ assertThat(map)
+ .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30,
+ asMutableList(6), 60, asMutableList(7), 40, asMutableList(8), 50)
+ .inOrder();
+ }
+
+ @Test
+ void testMutateRandomKeysChunk_failsToConstructSomeDistinctKeys() {
+ Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20,
+ asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60);
+ SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> {
+ list.clear();
+ List<Integer> newList = new ArrayList<>();
+ newList.add(7);
+ return newList;
+ }, ArrayList::new);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng);
+ assertThat(result).isTrue();
+ }
+ assertThat(map)
+ .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30,
+ asMutableList(5), 50, asMutableList(6), 60, asMutableList(7), 40)
+ .inOrder();
+ }
+
+ @Test
+ void testMutateRandomKeysChunk_failsToConstructAnyDistinctKeys() {
+ Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20,
+ asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60);
+ SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> {
+ list.clear();
+ List<Integer> newList = new ArrayList<>();
+ newList.add(1);
+ return newList;
+ }, ArrayList::new);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng);
+ assertThat(result).isFalse();
+ }
+ assertThat(map)
+ .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30,
+ asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60)
+ .inOrder();
+ }
+
+ @Test
+ void testMutateRandomKeysChunk_nullKeyAndValue() {
+ Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20,
+ asMutableList(3), 30, asMutableList(4), null, null, 50, asMutableList(6), 60);
+ SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> {
+ if (list != null) {
+ List<Integer> newList = list.stream().map(i -> i + 1).collect(toList());
+ list.clear();
+ return newList;
+ } else {
+ return asMutableList(10);
+ }
+ }, list -> list != null ? new ArrayList<>(list) : null);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) {
+ boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng);
+ assertThat(result).isTrue();
+ }
+ assertThat(map)
+ .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30,
+ asMutableList(6), 60, asMutableList(5), null, asMutableList(10), 50)
+ .inOrder();
+ }
+
+ @Test
+ void testMutateRandomKeysChunk_mutateKeyToNull() {
+ Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20,
+ asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60);
+ SerializingMutator<List<Integer>> keyMutator =
+ mockMutator(null, list -> null, list -> list != null ? new ArrayList<>(list) : null);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(1, 3)) {
+ boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng);
+ assertThat(result).isTrue();
+ }
+ assertThat(map)
+ .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30,
+ asMutableList(5), 50, asMutableList(6), 60, null, 40)
+ .inOrder();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java
new file mode 100644
index 00000000..24299f48
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.collection;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Collections.emptyList;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.annotation.WithSize;
+import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import java.lang.reflect.AnnotatedType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings("unchecked")
+public class ListMutatorTest {
+ public static final MutatorFactory FACTORY =
+ new ChainedMutatorFactory(LangMutators.newFactory(), CollectionMutators.newFactory());
+
+ private static SerializingMutator<@NotNull List<@NotNull Integer>> defaultListMutator() {
+ AnnotatedType type = new TypeHolder<@NotNull List<@NotNull Integer>>() {}.annotatedType();
+ return (SerializingMutator<@NotNull List<@NotNull Integer>>) FACTORY.createOrThrow(type);
+ }
+
+ @Test
+ void testInit() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+ assertThat(mutator.toString()).isEqualTo("List<Integer>");
+
+ List<Integer> list;
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // targetSize
+ 1,
+ // elementMutator.init
+ 1)) {
+ list = mutator.init(prng);
+ }
+ assertThat(list).containsExactly(0);
+ }
+
+ @Test
+ void testInitMaxSize() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull @WithSize(min = 2, max = 3) List<@NotNull Integer>>(){}
+ .annotatedType();
+
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator =
+ (SerializingMutator<@NotNull List<@NotNull Integer>>) FACTORY.createOrThrow(type);
+
+ assertThat(mutator.toString()).isEqualTo("List<Integer>");
+ List<Integer> list;
+ try (MockPseudoRandom prng = mockPseudoRandom(2, 4, 42L, 4, 43L)) {
+ list = mutator.init(prng);
+ }
+
+ assertThat(list).containsExactly(42, 43).inOrder();
+ }
+
+ @Test
+ void testRemoveSingleElement() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // action
+ 0,
+ // number of elements to remove
+ 1,
+ // index to remove
+ 2)) {
+ list = mutator.mutate(list, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 4, 5, 6, 7, 8, 9).inOrder();
+ }
+
+ @Test
+ void testRemoveChunk() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // action
+ 0,
+ // chunk size
+ 2,
+ // chunk offset
+ 3)) {
+ list = mutator.mutate(list, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 3, 6, 7, 8, 9).inOrder();
+ }
+
+ @Test
+ void testAddSingleElement() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // action
+ 1,
+ // add single element,
+ 1,
+ // offset,
+ 9,
+ // Integral initImpl sentinel value
+ 4,
+ // value
+ 42L)) {
+ list = mutator.mutate(list, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 42).inOrder();
+ }
+
+ @Test
+ void testAddChunk() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // action
+ 1,
+ // chunkSize
+ 2,
+ // chunkOffset
+ 3,
+ // Integral initImpl
+ 4,
+ // val
+ 42L)) {
+ list = mutator.mutate(list, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 3, 42, 42, 4, 5, 6, 7, 8, 9).inOrder();
+ }
+
+ @Test
+ void testChangeSingleElement() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // action
+ 2,
+ // number of elements to mutate
+ 1,
+ // first index to mutate at
+ 2,
+ // mutation choice based on `IntegralMutatorFactory`
+ // 2 == closedRange
+ 2,
+ // value
+ 55L)) {
+ list = mutator.mutate(list, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 55, 4, 5, 6, 7, 8, 9).inOrder();
+ }
+
+ @Test
+ void testChangeChunk() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // action
+ 2,
+ // number of elements to mutate
+ 2,
+ // first index to mutate at
+ 5,
+ // mutation: 0 == bitflip
+ 0,
+ // shift constant
+ 13,
+ // and again
+ 0, 12)) {
+ list = mutator.mutate(list, prng);
+ }
+ assertThat(list).containsExactly(1, 2, 3, 4, 5, 8198, 4103, 8, 9, 10, 11).inOrder();
+ }
+
+ @Test
+ void testCrossOverEmptyLists() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ List<Integer> list = mutator.crossOver(emptyList(), emptyList(), prng);
+ assertThat(list).isEmpty();
+ }
+ }
+
+ @Test
+ void testCrossOverInsertChunk() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+ List<Integer> otherList =
+ new ArrayList<>(Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // insert action
+ 0,
+ // chunk size
+ 3,
+ // fromPos
+ 2,
+ // toPos
+ 5)) {
+ list = mutator.crossOver(list, otherList, prng);
+ }
+ assertThat(list).containsExactly(0, 1, 2, 3, 4, 12, 13, 14, 5, 6, 7, 8, 9).inOrder();
+ }
+
+ @Test
+ void testCrossOverOverwriteChunk() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+ List<Integer> otherList =
+ new ArrayList<>(Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // overwrite action
+ 1,
+ // chunk size
+ 3,
+ // fromPos
+ 2,
+ // toPos
+ 5)) {
+ list = mutator.crossOver(list, otherList, prng);
+ }
+ assertThat(list).containsExactly(0, 1, 2, 3, 4, 12, 13, 14, 8, 9).inOrder();
+ }
+
+ @Test
+ void testCrossOverCrossOverChunk() {
+ SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator();
+
+ List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+ List<Integer> otherList =
+ new ArrayList<>(Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // overwrite action
+ 2,
+ // chunk size
+ 3,
+ // fromPos
+ 2,
+ // toPos
+ 2,
+ // mean value in sub cross over
+ 0,
+ // mean value in sub cross over
+ 0,
+ // mean value in sub cross over
+ 0)) {
+ list = mutator.crossOver(list, otherList, prng);
+ }
+ assertThat(list).containsExactly(0, 1, 7, 8, 9, 5, 6, 7, 8, 9).inOrder();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java
new file mode 100644
index 00000000..4c2c14f9
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.collection;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMap;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMutableList;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Collections.emptyMap;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.annotation.WithSize;
+import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import java.lang.reflect.AnnotatedType;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings("unchecked")
+class MapMutatorTest {
+ public static final MutatorFactory FACTORY =
+ new ChainedMutatorFactory(LangMutators.newFactory(), CollectionMutators.newFactory());
+
+ private static SerializingMutator<Map<Integer, Integer>> defaultTestMapMutator() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType();
+ return (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type);
+ }
+
+ @Test
+ void mapInitInsert() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull @WithSize(max = 3) Map<@NotNull String, @NotNull String>>(){}
+ .annotatedType();
+ SerializingMutator<Map<String, String>> mutator =
+ (SerializingMutator<Map<String, String>>) FACTORY.createOrThrow(type);
+ assertThat(mutator.toString()).isEqualTo("Map<String,String>");
+
+ // Initialize new map
+ Map<String, String> map;
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // Initial map size
+ 1,
+ // Key 1 size
+ 4,
+ // Key 1 value
+ "Key1".getBytes(),
+ // Value size
+ 6,
+ // Value value
+ "Value1".getBytes())) {
+ map = mutator.init(prng);
+ }
+ assertThat(map).containsExactly("Key1", "Value1");
+
+ // Add 2 new entries
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // grow chunk
+ 1,
+ // ChunkSize
+ 2,
+ // Key 2 size
+ 4,
+ // Key 2 value
+ "Key2".getBytes(),
+ // Value size
+ 6,
+ // Value value
+ "Value2".getBytes(),
+ // Key 3 size
+ 4,
+ // Key 3 value
+ "Key3".getBytes(),
+ // Value size
+ 6,
+ // Value value
+ "Value3".getBytes())) {
+ map = mutator.mutate(map, prng);
+ }
+ assertThat(map).containsExactly("Key1", "Value1", "Key2", "Value2", "Key3", "Value3").inOrder();
+ }
+
+ @Test
+ void mapDelete() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType();
+ SerializingMutator<Map<Integer, Integer>> mutator =
+ (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type);
+ assertThat(mutator.toString()).isEqualTo("Map<Integer,Integer>");
+
+ Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // delete chunk
+ 0,
+ // chunk size
+ 2,
+ // chunk position
+ 3)) {
+ map = mutator.mutate(map, prng);
+ }
+ assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 6, 60).inOrder();
+ }
+
+ @Test
+ void mapMutateValues() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType();
+ SerializingMutator<Map<Integer, Integer>> mutator =
+ (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type);
+ assertThat(mutator.toString()).isEqualTo("Map<Integer,Integer>");
+
+ Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // change chunk
+ 2,
+ // mutate values,
+ true,
+ // chunk size
+ 2,
+ // chunk position
+ 3,
+ // uniform pick
+ 2,
+ // random integer
+ 41L,
+ // uniform pick
+ 2,
+ // random integer
+ 51L)) {
+ map = mutator.mutate(map, prng);
+ }
+ assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 4, 41, 5, 51, 6, 60).inOrder();
+ }
+
+ @Test
+ void mapMutateKeys() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType();
+ SerializingMutator<Map<Integer, Integer>> mutator =
+ (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type);
+ assertThat(mutator.toString()).isEqualTo("Map<Integer,Integer>");
+
+ Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // change chunk
+ 2,
+ // mutate keys,
+ false,
+ // chunk size
+ 2,
+ // chunk position
+ 3,
+ // uniform pick
+ 2,
+ // integer
+ 7L,
+ // uniform pick
+ 2,
+ // random integer
+ 8L)) {
+ map = mutator.mutate(map, prng);
+ }
+ assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 6, 60, 7, 40, 8, 50).inOrder();
+ }
+
+ @Test
+ void mapMutateKeysFallbackToValues() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull Boolean, @NotNull Boolean>>() {}.annotatedType();
+ SerializingMutator<Map<Boolean, Boolean>> mutator =
+ (SerializingMutator<Map<Boolean, Boolean>>) FACTORY.createOrThrow(type);
+ assertThat(mutator.toString()).isEqualTo("Map<Boolean,Boolean>");
+
+ // No new keys can be generated for this map.
+ Map<Boolean, Boolean> map = asMap(false, false, true, false);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // change chunk
+ 2,
+ // mutate keys,
+ false,
+ // chunk size
+ 1,
+ // chunk position
+ 0,
+ // chunk size for fallback to mutate values
+ 2,
+ // chunk position for fallback
+ 0)) {
+ map = mutator.mutate(map, prng);
+ }
+ assertThat(map).containsExactly(false, true, true, true).inOrder();
+ }
+
+ @Test
+ void testCrossOverEmptyMaps() {
+ SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull Integer>> mutator =
+ defaultTestMapMutator();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ Map<Integer, Integer> map = mutator.crossOver(emptyMap(), emptyMap(), prng);
+ assertThat(map).isEmpty();
+ }
+ }
+
+ @Test
+ void testCrossOverInsertChunk() {
+ SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull Integer>> mutator =
+ defaultTestMapMutator();
+
+ Map<Integer, Integer> map = asMap(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6);
+ Map<Integer, Integer> otherMap = asMap(1, 1, 2, 2, 3, 3, 40, 40, 50, 50, 60, 60);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // insert action
+ 0,
+ // chunk size
+ 3,
+ // from chunk offset, will skip first element of chunk as it is already present in map
+ 3,
+ // to chunk offset, unused
+ 0)) {
+ map = mutator.crossOver(map, otherMap, prng);
+ assertThat(map)
+ .containsExactly(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 40, 40, 50, 50, 60, 60)
+ .inOrder();
+ }
+ }
+
+ @Test
+ void testCrossOverOverwriteChunk() {
+ SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull Integer>> mutator =
+ defaultTestMapMutator();
+
+ Map<Integer, Integer> map = asMap(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6);
+ Map<Integer, Integer> otherMap = asMap(1, 1, 2, 2, 3, 3, 40, 40, 50, 50, 60, 60);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // overwrite action
+ 1,
+ // chunk size
+ 3,
+ // from chunk offset
+ 2,
+ // to chunk offset, will not change first element as values are equal
+ 2)) {
+ map = mutator.crossOver(map, otherMap, prng);
+ assertThat(map).containsExactly(1, 1, 2, 2, 3, 3, 4, 40, 5, 50, 6, 6).inOrder();
+ }
+ }
+
+ @Test
+ void testCrossOverCrossOverChunkKeys() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull List<@NotNull Integer>, @NotNull Integer>>() {
+ }.annotatedType();
+ SerializingMutator<@NotNull Map<@NotNull List<@NotNull Integer>, @NotNull Integer>> mutator =
+ (SerializingMutator<@NotNull Map<@NotNull List<@NotNull Integer>, @NotNull Integer>>)
+ FACTORY.createOrThrow(type);
+
+ Map<List<Integer>, Integer> map = asMap(asMutableList(1), 1, asMutableList(2), 2,
+ asMutableList(3), 3, asMutableList(4), 4, asMutableList(5), 5, asMutableList(6), 6);
+ Map<List<Integer>, Integer> otherMap = asMap(asMutableList(1), 1, asMutableList(2), 2,
+ asMutableList(3), 3, asMutableList(40), 4, asMutableList(50), 5, asMutableList(60), 6);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // cross over action
+ 2,
+ // keys
+ true,
+ // chunk size
+ 3,
+ // from chunk offset
+ 2,
+ // to chunk offset,
+ // first keys ("3") are equal and will be overwritten
+ 2,
+ // first key, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0
+ 1, 1, 0, 0,
+ // second key, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0
+ 1, 1, 0, 0,
+ // third key, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0
+ 1, 1, 0, 0)) {
+ map = mutator.crossOver(map, otherMap, prng);
+ assertThat(map)
+ .containsExactly(asMutableList(1), 1, asMutableList(2), 2, asMutableList(6), 6,
+ // Overwritten keys after here
+ asMutableList(3), 3, asMutableList(40), 4, asMutableList(50), 5)
+ .inOrder();
+ }
+ }
+
+ @Test
+ void testCrossOverCrossOverChunkValues() {
+ AnnotatedType type =
+ new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull List<@NotNull Integer>>>() {
+ }.annotatedType();
+ SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull List<@NotNull Integer>>> mutator =
+ (SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull List<@NotNull Integer>>>)
+ FACTORY.createOrThrow(type);
+
+ Map<Integer, List<Integer>> map = asMap(1, asMutableList(1), 2, asMutableList(2), 3,
+ asMutableList(3), 4, asMutableList(4), 5, asMutableList(5), 6, asMutableList(6));
+ Map<Integer, List<Integer>> otherMap = asMap(1, asMutableList(1), 2, asMutableList(2), 3,
+ asMutableList(30), 40, asMutableList(40), 50, asMutableList(50), 60, asMutableList(60));
+
+ try (
+ MockPseudoRandom prng = mockPseudoRandom(
+ // cross over action
+ 2,
+ // values
+ false,
+ // chunk size
+ 3,
+ // from chunk offset
+ 2,
+ // to chunk offset,
+ 2,
+ // first value, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0
+ 1, 1, 0, 0,
+ // second value, delegate to list cross over, overwrite 1 entry at offset 0 from offset
+ // 0
+ 1, 1, 0, 0,
+ // third value, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0
+ 1, 1, 0, 0)) {
+ map = mutator.crossOver(map, otherMap, prng);
+ assertThat(map)
+ .containsExactly(1, asMutableList(1), 2, asMutableList(2), 3, asMutableList(30), 4,
+ asMutableList(40), 5, asMutableList(50), 6, asMutableList(6))
+ .inOrder();
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel
new file mode 100644
index 00000000..05e1d720
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
+
+java_test_suite(
+ name = "PrimitiveTests",
+ size = "small",
+ srcs = glob(["*.java"]),
+ env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"},
+ runner = "junit5",
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ "@com_google_protobuf//java/core",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java
new file mode 100644
index 00000000..3bf55bcf
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings("unchecked")
+class BooleanMutatorTest {
+ @Test
+ void testPrimitive() {
+ SerializingMutator<Boolean> mutator = LangMutators.newFactory().createOrThrow(boolean.class);
+ assertThat(mutator.toString()).isEqualTo("Boolean");
+
+ boolean bool;
+ try (MockPseudoRandom prng = mockPseudoRandom(true)) {
+ bool = mutator.init(prng);
+ }
+ assertThat(bool).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ bool = mutator.mutate(bool, prng);
+ }
+ assertThat(bool).isFalse();
+ }
+
+ @Test
+ void testBoxed() {
+ SerializingMutator<Boolean> mutator =
+ (SerializingMutator<Boolean>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Boolean>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("Boolean");
+
+ Boolean bool;
+ try (MockPseudoRandom prng = mockPseudoRandom(false)) {
+ bool = mutator.init(prng);
+ }
+ assertThat(bool).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ bool = mutator.mutate(bool, prng);
+ }
+ assertThat(bool).isTrue();
+ }
+
+ @Test
+ void testCrossOver() {
+ SerializingMutator<Boolean> mutator = LangMutators.newFactory().createOrThrow(boolean.class);
+ try (MockPseudoRandom prng = mockPseudoRandom(true, false)) {
+ assertThat(mutator.crossOver(true, false, prng)).isTrue();
+ assertThat(mutator.crossOver(true, false, prng)).isFalse();
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java
new file mode 100644
index 00000000..1592b17d
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.annotation.WithLength;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"})
+public class ByteArrayMutatorTest {
+ /**
+ * Some tests may set {@link LibFuzzerMutator#MOCK_SIZE_KEY} which can interfere with other tests
+ * unless cleared.
+ */
+ @AfterEach
+ void cleanMockSize() {
+ System.clearProperty(LibFuzzerMutator.MOCK_SIZE_KEY);
+ }
+
+ @Test
+ void testBasicFunction() {
+ SerializingMutator<byte[]> mutator =
+ (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<byte[]>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("Nullable<byte[]>");
+
+ byte[] arr;
+ try (MockPseudoRandom prng = mockPseudoRandom(false, 5, new byte[] {1, 2, 3, 4, 5})) {
+ arr = mutator.init(prng);
+ }
+ assertThat(arr).isEqualTo(new byte[] {1, 2, 3, 4, 5});
+
+ System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "10");
+ try (MockPseudoRandom prng = mockPseudoRandom(false)) {
+ arr = mutator.mutate(arr, prng);
+ }
+ assertThat(arr).isEqualTo(new byte[] {2, 4, 6, 8, 10, 6, 7, 8, 9, 10});
+ }
+
+ @Test
+ void testMaxLength() {
+ SerializingMutator<byte[]> mutator =
+ (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<byte @NotNull @WithLength(max = 10)[]>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("byte[]");
+
+ byte[] arr;
+ try (MockPseudoRandom prng = mockPseudoRandom(8, new byte[] {1, 2, 3, 4, 5, 6, 7, 8})) {
+ arr = mutator.init(prng);
+ }
+ assertThat(arr).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8});
+
+ System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "11");
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ // the ByteArrayMutator will limit the maximum size of the data requested from libfuzzer to
+ // WithLength::max so setting the mock mutator to make it bigger will cause an exception
+ assertThrows(ArrayIndexOutOfBoundsException.class, () -> { mutator.mutate(arr, prng); });
+ }
+ }
+
+ @Test
+ void testMaxLengthInitClamp() {
+ SerializingMutator<byte[]> mutator =
+ (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<byte @NotNull @WithLength(max = 5)[]>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("byte[]");
+
+ try (MockPseudoRandom prng = mockPseudoRandom(10)) {
+ // init will call closedRange(min, max) and the mock prng will assert that the given value
+ // above is between those values which we want to fail here to show that we're properly
+ // clamping the range
+ assertThrows(AssertionError.class, () -> { mutator.init(prng); });
+ }
+ }
+
+ @Test
+ void testMinLengthInitClamp() {
+ SerializingMutator<byte[]> mutator =
+ (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<byte @NotNull @WithLength(min = 5)[]>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("byte[]");
+
+ try (MockPseudoRandom prng = mockPseudoRandom(3)) {
+ // init will call closedrange(min, max) and the mock prng will assert that the given value
+ // above is between those values which we want to fail here to show that we're properly
+ // clamping the range
+ assertThrows(AssertionError.class, () -> { mutator.init(prng); });
+ }
+ }
+
+ @Test
+ void testMinLength() {
+ SerializingMutator<byte[]> mutator =
+ (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<byte @NotNull @WithLength(min = 5)[]>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("byte[]");
+
+ byte[] arr;
+ try (MockPseudoRandom prng = mockPseudoRandom(10, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
+ arr = mutator.init(prng);
+ }
+ assertThat(arr).hasLength(10);
+
+ System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "3");
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ arr = mutator.mutate(arr, prng);
+ }
+ assertThat(arr).hasLength(5);
+ assertThat(arr).isEqualTo(new byte[] {2, 4, 6, 0, 0});
+ }
+
+ @Test
+ void testCrossOver() {
+ SerializingMutator<byte[]> mutator =
+ (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<byte @NotNull[]>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("byte[]");
+
+ byte[] value = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ byte[] otherValue = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
+
+ byte[] crossedOver;
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // intersect arrays
+ 0,
+ // out length
+ 8,
+ // copy 3 from first
+ 3,
+ // copy 1 from second
+ 1,
+ // copy 1 from first,
+ 1,
+ // copy 3 from second
+ 3)) {
+ crossedOver = mutator.crossOver(value, otherValue, prng);
+ assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 2, 10, 3, 11, 12, 13});
+ }
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // insert into action
+ 1,
+ // copy size
+ 3,
+ // from position
+ 5,
+ // to position
+ 2)) {
+ crossedOver = mutator.crossOver(value, otherValue, prng);
+ assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 15, 16, 17, 2, 3, 4, 5, 6, 7, 8, 9});
+ }
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // overwrite action
+ 2,
+ // to position
+ 3,
+ // copy size
+ 3,
+ // from position
+ 4)) {
+ crossedOver = mutator.crossOver(value, otherValue, prng);
+ assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 2, 14, 15, 16, 6, 7, 8, 9});
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java
new file mode 100644
index 00000000..d2c61397
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+class EnumMutatorTest {
+ enum TestEnumOne { A }
+
+ enum TestEnum { A, B, C }
+
+ @Test
+ void testBoxed() {
+ SerializingMutator<TestEnum> mutator =
+ (SerializingMutator<TestEnum>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull TestEnum>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("Enum<TestEnum>");
+ TestEnum cl;
+ try (MockPseudoRandom prng = mockPseudoRandom(0)) {
+ cl = mutator.init(prng);
+ }
+ assertThat(cl).isEqualTo(TestEnum.A);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(1)) {
+ cl = mutator.mutate(cl, prng);
+ }
+ assertThat(cl).isEqualTo(TestEnum.B);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(0)) {
+ cl = mutator.mutate(cl, prng);
+ }
+ assertThat(cl).isEqualTo(TestEnum.A);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(2)) {
+ cl = mutator.mutate(cl, prng);
+ }
+ assertThat(cl).isEqualTo(TestEnum.C);
+
+ try (MockPseudoRandom prng = mockPseudoRandom(1)) {
+ cl = mutator.mutate(cl, prng);
+ }
+ assertThat(cl).isEqualTo(TestEnum.B);
+ }
+
+ @Test
+ void testEnumWithOneElementShouldThrow() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull TestEnumOne>() {}.annotatedType());
+ }, "When trying to build mutators for Enum with one value, an Exception should be thrown.");
+ }
+
+ @Test
+ void testEnumBasedOnInvalidInput() throws IOException {
+ SerializingMutator<TestEnum> mutator =
+ (SerializingMutator<TestEnum>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull TestEnum>() {}.annotatedType());
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
+ DataOutputStream os = new DataOutputStream(bo);
+ // Valid values
+ os.writeInt(0);
+ os.writeInt(1);
+ os.writeInt(2);
+ // Too high indices wrap around
+ os.writeInt(3);
+ // Abs. value is used to calculate the index
+ os.writeInt(-3);
+
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(bo.toByteArray()));
+ assertThat(mutator.read(is)).isEqualTo(TestEnum.A);
+ assertThat(mutator.read(is)).isEqualTo(TestEnum.B);
+ assertThat(mutator.read(is)).isEqualTo(TestEnum.C);
+ assertThat(mutator.read(is)).isEqualTo(TestEnum.A);
+ assertThat(mutator.read(is)).isEqualTo(TestEnum.A);
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java
new file mode 100644
index 00000000..9c03b467
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.mutator.lang.FloatingPointMutatorFactory.DoubleMutator;
+import static com.code_intelligence.jazzer.mutation.mutator.lang.FloatingPointMutatorFactory.FloatMutator;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange;
+import com.code_intelligence.jazzer.mutation.annotation.FloatInRange;
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class FloatingPointMutatorTest {
+ static final Float UNUSED_FLOAT = 0.0f;
+ static final Double UNUSED_DOUBLE = 0.0;
+
+ static Stream<Arguments> floatForceInRangeCases() {
+ float NaN1 = Float.intBitsToFloat(0x7f800001);
+ float NaN2 = Float.intBitsToFloat(0x7f800002);
+ float NaN3 = Float.intBitsToFloat(0x7f800003);
+ assertThat(Float.isNaN(NaN1) && Float.isNaN(NaN2) && Float.isNaN(NaN3)).isTrue();
+
+ return Stream.of(
+ // value is already in range: it should stay in range
+ arguments(0.0f, 0.0f, 1.0f, true), arguments(0.0f, 1.0f, 1.0f, true),
+ arguments(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 1.0f, true),
+ arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, true),
+ arguments(Float.NaN, 0.0f, 1.0f, true),
+ arguments(1e30f, -Float.MAX_VALUE, Float.MAX_VALUE, true),
+ arguments(-1e30f, -Float.MAX_VALUE, Float.MAX_VALUE, true),
+ arguments(0.0f, Float.NEGATIVE_INFINITY, Float.MAX_VALUE, true),
+ arguments(0.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, true),
+ arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, Float.MAX_VALUE, true),
+ arguments(Float.MAX_VALUE, -Float.MAX_VALUE, Float.MAX_VALUE, true),
+ arguments(-Float.MAX_VALUE, Float.MAX_VALUE - 3.4e30f, Float.MAX_VALUE, false),
+ arguments(Float.MAX_VALUE, -100.0f, Float.MAX_VALUE, true),
+ arguments(0.0f, -Float.MIN_VALUE, Float.MIN_VALUE, true),
+ // Special values and diff/ranges outside the range
+ arguments(Float.NEGATIVE_INFINITY, -1.0f, 1.0f, true),
+ arguments(Float.POSITIVE_INFINITY, -1.0f, 1.0f, true),
+ arguments(Float.POSITIVE_INFINITY, -Float.MAX_VALUE, Float.MAX_VALUE, true),
+ arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.MAX_VALUE, true),
+ arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, true),
+ arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, Float.MAX_VALUE, true),
+ arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, Float.POSITIVE_INFINITY, true),
+ arguments(Float.NEGATIVE_INFINITY, Float.MAX_VALUE, Float.POSITIVE_INFINITY, true),
+ // Values outside the range
+ arguments(-2e30f, -100000.0f, 100000.0f, true),
+ arguments(2e30f, Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, true),
+ arguments(-1.0f, 0.0f, 1.0f, false), arguments(5.0f, 0.0f, 1.0f, false),
+ arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, 100.0f, true),
+ // NaN not allowed
+ arguments(Float.NaN, 0.0f, 1.0f, false),
+ arguments(Float.NaN, -Float.MAX_VALUE, 1.0f, false),
+ arguments(Float.NaN, Float.NEGATIVE_INFINITY, 1.0f, false),
+ arguments(Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, false),
+ arguments(Float.NaN, 0f, Float.POSITIVE_INFINITY, false),
+ arguments(Float.NaN, 0f, Float.MAX_VALUE, false),
+ arguments(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE, false),
+ arguments(Float.NaN, -Float.MIN_VALUE, 0.0f, false),
+ arguments(Float.NaN, -Float.MIN_VALUE, Float.MIN_VALUE, false),
+ arguments(Float.NaN, 0.0f, Float.MIN_VALUE, false),
+ // There are many possible NaN values, test a few of them that are different from Float.NaN
+ // (0x7fc00000)
+ arguments(NaN1, 0.0f, 1.0f, false), arguments(NaN2, 0.0f, 1.0f, false),
+ arguments(NaN3, 0.0f, 1.0f, false));
+ }
+
+ static Stream<Arguments> doubleForceInRangeCases() {
+ double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L);
+ double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L);
+ double NaN3 = Double.longBitsToDouble(0x7ff0000000000003L);
+ double NaNdeadbeef = Double.longBitsToDouble(0x7ff00000deadbeefL);
+ assertThat(
+ Double.isNaN(NaN1) && Double.isNaN(NaN2) && Double.isNaN(NaN3) && Double.isNaN(NaNdeadbeef))
+ .isTrue();
+
+ return Stream.of(
+ // value is already in range: it should stay in range
+ arguments(0.0, 0.0, 1.0, true), arguments(0.0, 1.0, 1.0, true),
+ arguments(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0, true),
+ arguments(
+ Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, true),
+ arguments(Double.NaN, 0.0, 1.0, true),
+ arguments(1e30, -Double.MAX_VALUE, Double.MAX_VALUE, true),
+ arguments(-1e30, -Double.MAX_VALUE, Double.MAX_VALUE, true),
+ arguments(0.0, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, true),
+ arguments(0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, true),
+ arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, true),
+ arguments(Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, true),
+ arguments(-Double.MAX_VALUE, Double.MAX_VALUE - 3.4e30, Double.MAX_VALUE, false),
+ arguments(Double.MAX_VALUE, -100.0, Double.MAX_VALUE, true),
+ arguments(0.0, -Double.MIN_VALUE, Double.MIN_VALUE, true),
+ // Special values and diff/ranges outside the range
+ arguments(Double.NEGATIVE_INFINITY, -1.0, 1.0, true),
+ arguments(Double.POSITIVE_INFINITY, -1.0, 1.0, true),
+ arguments(Double.POSITIVE_INFINITY, -Double.MAX_VALUE, Double.MAX_VALUE, true),
+ arguments(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, true),
+ arguments(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, true),
+ arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, Double.MAX_VALUE, true),
+ arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, Double.POSITIVE_INFINITY, true),
+ arguments(Double.NEGATIVE_INFINITY, Double.MAX_VALUE, Double.POSITIVE_INFINITY, true),
+ // Values outside the range
+ arguments(-2e30, -100000.0, 100000.0, true),
+ arguments(2e30, Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, true),
+ arguments(-1.0, 0.0, 1.0, false), arguments(5.0, 0.0, 1.0, false),
+ arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, 100.0, true),
+ arguments(
+ Math.nextDown(Double.MAX_VALUE), -Double.MAX_VALUE * 0.5, Double.MAX_VALUE * 0.5, true),
+ arguments(Math.nextDown(Double.MAX_VALUE), -Double.MAX_VALUE * 0.5,
+ Math.nextUp(Double.MAX_VALUE * 0.5), true),
+ // NaN not allowed
+ arguments(Double.NaN, 0.0, 1.0, false),
+ arguments(Double.NaN, -Double.MAX_VALUE, 1.0, false),
+ arguments(Double.NaN, Double.NEGATIVE_INFINITY, 1.0, false),
+ arguments(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false),
+ arguments(Double.NaN, 0, Double.POSITIVE_INFINITY, false),
+ arguments(Double.NaN, 0, Double.MAX_VALUE, false),
+ arguments(Double.NaN, -Double.MAX_VALUE, Double.MAX_VALUE, false),
+ arguments(Double.NaN, -Double.MIN_VALUE, 0.0, false),
+ arguments(Double.NaN, -Double.MIN_VALUE, Double.MIN_VALUE, false),
+ arguments(Double.NaN, 0.0, Double.MIN_VALUE, false),
+ // There are many possible NaN values, test a few of them that are different from Double.NaN
+ // (0x7ff8000000000000L)
+ arguments(NaN1, 0.0, 1.0, false), arguments(NaN2, 0.0, 1.0, false),
+ arguments(NaN3, 0.0, 1.0, false),
+ arguments(NaNdeadbeef, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false));
+ }
+
+ @ParameterizedTest
+ @MethodSource("floatForceInRangeCases")
+ void testFloatForceInRange(float value, float minValue, float maxValue, boolean allowNaN) {
+ float inRange = FloatMutator.forceInRange(value, minValue, maxValue, allowNaN);
+
+ // inRange can become NaN only if allowNaN is true and value was NaN already
+ if (Float.isNaN(inRange)) {
+ if (allowNaN) {
+ assertThat(Float.isNaN(value)).isTrue();
+ return; // NaN is not in range of anything
+ } else {
+ throw new AssertionError("NaN is not allowed but was returned");
+ }
+ }
+
+ assertThat(inRange).isAtLeast(minValue);
+ assertThat(inRange).isAtMost(maxValue);
+ if (value >= minValue && value <= maxValue) {
+ assertThat(inRange).isEqualTo(value);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("doubleForceInRangeCases")
+ void testDoubleForceInRange(double value, double minValue, double maxValue, boolean allowNaN) {
+ double inRange = DoubleMutator.forceInRange(value, minValue, maxValue, allowNaN);
+
+ // inRange can become NaN only if allowNaN is true and value was NaN already
+ if (Double.isNaN(inRange)) {
+ if (allowNaN) {
+ assertThat(Double.isNaN(value)).isTrue();
+ return; // NaN is not in range of anything
+ } else {
+ throw new AssertionError("NaN is not allowed but was returned");
+ }
+ }
+
+ assertThat(inRange).isAtLeast(minValue);
+ assertThat(inRange).isAtMost(maxValue);
+ if (value >= minValue && value <= maxValue) {
+ assertThat(inRange).isEqualTo(value);
+ }
+ }
+
+ // Tests of mutators' special values after initialization use mocked PRNG to test one special
+ // value after another. This counter enables adding new special values and testcases for them
+ // without modifying all the other test cases.
+ static Supplier<Integer> makeCounter() {
+ return new Supplier<Integer>() {
+ private int counter = 0;
+
+ @Override
+ public Integer get() {
+ return counter++;
+ }
+ };
+ }
+
+ static Stream<Arguments> floatInitCasesFullRange() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Float>() {}.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false));
+ }
+
+ static Stream<Arguments> floatInitCasesMinusOneToOne() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(min = -1.0f, max = 1.0f) Float>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -1.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 1.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false));
+ }
+
+ static Stream<Arguments> floatInitCasesMinusMinToMin() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(
+ min = -Float.MIN_VALUE, max = Float.MIN_VALUE) Float>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false));
+ }
+
+ static Stream<Arguments> floatInitCasesMaxToInf() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(
+ min = Float.MAX_VALUE, max = Float.POSITIVE_INFINITY) Float>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false));
+ }
+
+ static Stream<Arguments> floatInitCasesMinusInfToMinusMax() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(
+ min = Float.NEGATIVE_INFINITY, max = -Float.MAX_VALUE) Float>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false));
+ }
+
+ static Stream<Arguments> floatInitCasesFullRangeWithoutNaN() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(min = Float.NEGATIVE_INFINITY,
+ max = Float.POSITIVE_INFINITY, allowNaN = true) Float>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false));
+ }
+
+ @ParameterizedTest
+ @MethodSource({"floatInitCasesMinusOneToOne", "floatInitCasesFullRange",
+ "floatInitCasesMinusMinToMin", "floatInitCasesMaxToInf", "floatInitCasesMinusInfToMinusMax",
+ "floatInitCasesFullRangeWithoutNaN"})
+ void
+ testFloatInitCases(SerializingMutator<Float> mutator, Stream<Object> prngValues, float expected,
+ boolean specialValueIndexExists) {
+ assertThat(mutator.toString()).isEqualTo("Float");
+ if (specialValueIndexExists) {
+ Float n = null;
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) {
+ n = mutator.init(prng);
+ }
+ assertThat(n).isEqualTo(expected);
+ } else { // should throw
+ assertThrows(AssertionError.class, () -> {
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) {
+ mutator.init(prng);
+ }
+ });
+ }
+ }
+
+ static Stream<Arguments> floatMutateSanityChecksFullRangeCases() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(min = Float.NEGATIVE_INFINITY,
+ max = Float.POSITIVE_INFINITY, allowNaN = true) Float>() {
+ }.annotatedType());
+ // Init value can be set to desired one by giving this to the init method: (false, <desired
+ // value>)
+ return Stream.of(
+ // Bit flips
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 0), 1.4e-45f, true),
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 30), 2.0f, true),
+ arguments(mutator, Stream.of(false, 2f), Stream.of(false, 0, 31), -2.0f, true),
+ arguments(mutator, Stream.of(false, -2f), Stream.of(false, 0, 22), -3.0f, true),
+ // mutateExponent
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111100), 0.125f, true),
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111110), 0.5f, true),
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111111), 1.0f, true),
+ // mutateMantissa
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 2, 0, 100), 1.4e-43f, true),
+ arguments(mutator, Stream.of(false, Float.intBitsToFloat(1)), Stream.of(false, 2, 0, -1), 0,
+ true),
+ // mutateWithMathematicalFn
+ arguments(
+ mutator, Stream.of(false, 10.1f), Stream.of(false, 3, 4), 11f, true), // Math::ceil
+ arguments(
+ mutator, Stream.of(false, 1000f), Stream.of(false, 3, 11), 3f, true), // Math::log10
+ // skip libfuzzer
+ // random in range
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 5, 10f), 10f, true),
+ // unknown mutation case exception
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 6), UNUSED_FLOAT, false));
+ }
+
+ static Stream<Arguments> floatMutateLimitedRangeCases() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(min = -1f, max = 1f, allowNaN = false) Float>() {
+ }.annotatedType());
+ // Init value can be set to desired one by giving this to the init method: (false, <desired
+ // value>)
+ return Stream.of(
+ // Bit flip; forceInRange(); result equals previous value; adjust value
+ arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 30, true),
+ 0f - Float.MIN_VALUE, true),
+ arguments(mutator, Stream.of(false, 1f), Stream.of(false, 0, 30), Math.nextDown(1f), true),
+ arguments(mutator, Stream.of(false, -1f), Stream.of(false, 0, 30), Math.nextUp(-1f), true),
+ // NaN after mutateWithMathematicalFn with NaN not allowed; forceInRange will return
+ // (min+max)/2
+ arguments(mutator, Stream.of(false, -1f), Stream.of(false, 3, 16), 0.0f, true));
+ }
+
+ static Stream<Arguments> floatMutateLimitedRangeCasesWithNaN() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @FloatInRange(min = -1f, max = 1f, allowNaN = true) Float>() {
+ }.annotatedType());
+ // Init value can be set to desired one by giving this to the init method: (false, <desired
+ // value>)
+ return Stream.of(
+ // NaN after mutation and forceInRange(); all good!
+ arguments(mutator, Stream.of(false, -1f), Stream.of(false, 3, 16), Float.NaN, true),
+ // NaN (with a set bit #8) after init, mutation, and forceInRange(); need to change NaN to
+ // something else
+ arguments(mutator, Stream.of(true, 6), Stream.of(false, 0, 8, 0.3f), 0.3f, true));
+ }
+
+ @ParameterizedTest
+ @MethodSource({"floatMutateSanityChecksFullRangeCases", "floatMutateLimitedRangeCases",
+ "floatMutateLimitedRangeCasesWithNaN"})
+ void
+ testFloatMutateCases(SerializingMutator<Float> mutator, Stream<Object> initValues,
+ Stream<Object> mutationValues, float expected, boolean knownMutatorSwitchCase) {
+ assertThat(mutator.toString()).isEqualTo("Float");
+ Float n;
+
+ // Init
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(initValues.toArray())) {
+ n = mutator.init(prng);
+ }
+
+ // Mutate
+ if (knownMutatorSwitchCase) {
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) {
+ n = mutator.mutate(n, prng);
+ }
+ assertThat(n).isEqualTo(expected);
+
+ if (!((FloatMutator) mutator).allowNaN) {
+ assertThat(n).isNotEqualTo(Float.NaN);
+ }
+
+ if (!Float.isNaN(n)) {
+ assertThat(n).isAtLeast(((FloatMutator) mutator).minValue);
+ assertThat(n).isAtMost(((FloatMutator) mutator).maxValue);
+ }
+ } else { // Invalid mutation because a case is not handled
+ assertThrows(AssertionError.class, () -> {
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) {
+ mutator.mutate(UNUSED_FLOAT, prng);
+ }
+ });
+ }
+ }
+
+ @Test
+ void testFloatCrossOverMean() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Float>() {}.annotatedType());
+ try (TestSupport.MockPseudoRandom prng =
+ mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) {
+ assertThat(mutator.crossOver(0f, 0f, prng)).isWithin(0).of(0f);
+ assertThat(mutator.crossOver(-0f, 0f, prng)).isWithin(0).of(0f);
+ assertThat(mutator.crossOver(0f, 2f, prng)).isWithin(1e-10f).of(1.0f);
+ assertThat(mutator.crossOver(1f, 2f, prng)).isWithin(1e-10f).of(1.5f);
+ assertThat(mutator.crossOver(1f, 3f, prng)).isWithin(1e-10f).of(2f);
+ assertThat(mutator.crossOver(Float.MAX_VALUE, Float.MAX_VALUE, prng))
+ .isWithin(1e-10f)
+ .of(Float.MAX_VALUE);
+
+ assertThat(mutator.crossOver(0f, -2f, prng)).isWithin(1e-10f).of(-1.0f);
+ assertThat(mutator.crossOver(-1f, -2f, prng)).isWithin(1e-10f).of(-1.5f);
+ assertThat(mutator.crossOver(-1f, -3f, prng)).isWithin(1e-10f).of(-2f);
+ assertThat(mutator.crossOver(-Float.MAX_VALUE, -Float.MAX_VALUE, prng))
+ .isWithin(1e-10f)
+ .of(-Float.MAX_VALUE);
+
+ assertThat(mutator.crossOver(-100f, 200f, prng)).isWithin(1e-10f).of(50.0f);
+ assertThat(mutator.crossOver(100f, -200f, prng)).isWithin(1e-10f).of(-50f);
+ assertThat(mutator.crossOver(-Float.MAX_VALUE, Float.MAX_VALUE, prng))
+ .isWithin(1e-10f)
+ .of(0f);
+
+ assertThat(mutator.crossOver(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, prng)).isNaN();
+ assertThat(mutator.crossOver(Float.POSITIVE_INFINITY, 0f, prng)).isPositiveInfinity();
+ assertThat(mutator.crossOver(0f, Float.POSITIVE_INFINITY, prng)).isPositiveInfinity();
+ assertThat(mutator.crossOver(Float.NEGATIVE_INFINITY, 0f, prng)).isNegativeInfinity();
+ assertThat(mutator.crossOver(0f, Float.NEGATIVE_INFINITY, prng)).isNegativeInfinity();
+ assertThat(mutator.crossOver(Float.NaN, 0f, prng)).isNaN();
+ assertThat(mutator.crossOver(0f, Float.NaN, prng)).isNaN();
+ }
+ }
+
+ @Test
+ void testFloatCrossOverExponent() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Float>() {}.annotatedType());
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(1, 1, 1)) {
+ assertThat(mutator.crossOver(2.0f, -1.5f, prng)).isWithin(1e-10f).of(1.0f);
+ assertThat(mutator.crossOver(2.0f, Float.POSITIVE_INFINITY, prng)).isPositiveInfinity();
+ assertThat(mutator.crossOver(-1.5f, Float.NEGATIVE_INFINITY, prng)).isNaN();
+ }
+ }
+
+ @Test
+ void testFloatCrossOverMantissa() {
+ SerializingMutator<Float> mutator =
+ (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Float>() {}.annotatedType());
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(2, 2, 2)) {
+ assertThat(mutator.crossOver(4.0f, 3.5f, prng)).isWithin(1e-10f).of(7.0f);
+ assertThat(mutator.crossOver(Float.POSITIVE_INFINITY, 3.0f, prng)).isNaN();
+ assertThat(mutator.crossOver(Float.MAX_VALUE, 0.0f, prng)).isWithin(1e-10f).of(1.7014118e38f);
+ }
+ }
+
+ static Stream<Arguments> doubleInitCasesFullRange() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Double>() {}.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false));
+ }
+
+ static Stream<Arguments> doubleInitCasesMinusOneToOne() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(min = -1.0, max = 1.0) Double>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -1.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 1.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false));
+ }
+
+ static Stream<Arguments> doubleInitCasesMinusMinToMin() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(
+ min = -Double.MIN_VALUE, max = Double.MIN_VALUE) Double>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false));
+ }
+
+ static Stream<Arguments> doubleInitCasesMaxToInf() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(
+ min = Double.MAX_VALUE, max = Double.POSITIVE_INFINITY) Double>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false));
+ }
+
+ static Stream<Arguments> doubleInitCasesMinusInfToMinusMax() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(
+ min = Double.NEGATIVE_INFINITY, max = -Double.MAX_VALUE) Double>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false));
+ }
+
+ static Stream<Arguments> doubleInitCasesFullRangeWithoutNaN() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(min = Double.NEGATIVE_INFINITY,
+ max = Double.POSITIVE_INFINITY, allowNaN = true) Double>() {
+ }.annotatedType());
+ Supplier<Integer> ctr = makeCounter();
+ return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), -0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), 0.0, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true),
+ arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true),
+ arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false));
+ }
+
+ @ParameterizedTest
+ @MethodSource({"doubleInitCasesMinusOneToOne", "doubleInitCasesFullRange",
+ "doubleInitCasesMinusMinToMin", "doubleInitCasesMaxToInf",
+ "doubleInitCasesMinusInfToMinusMax", "doubleInitCasesFullRangeWithoutNaN"})
+ void
+ testDoubleInitCases(SerializingMutator<Double> mutator, Stream<Object> prngValues,
+ double expected, boolean knownSwitchCase) {
+ assertThat(mutator.toString()).isEqualTo("Double");
+ if (knownSwitchCase) {
+ Double n = null;
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) {
+ n = mutator.init(prng);
+ }
+ assertThat(n).isEqualTo(expected);
+ } else {
+ assertThrows(AssertionError.class, () -> {
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) {
+ mutator.init(prng);
+ }
+ });
+ }
+ }
+
+ static Stream<Arguments> doubleMutateSanityChecksFullRangeCases() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(min = Double.NEGATIVE_INFINITY,
+ max = Double.POSITIVE_INFINITY, allowNaN = true) Double>() {
+ }.annotatedType());
+ // Init value can be set to desired one by giving this to the init method: (false, <desired
+ // value>)
+ return Stream.of(
+ // Bit flips
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 0), Double.MIN_VALUE, true),
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 62), 2.0, true),
+ arguments(mutator, Stream.of(false, 2.0), Stream.of(false, 0, 63), -2.0, true),
+ arguments(mutator, Stream.of(false, -2.0), Stream.of(false, 0, 51), -3.0, true),
+ // mutateExponent
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111100), 0.125, true),
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111110), 0.5, true),
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111111), 1.0, true),
+ // mutateMantissa
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 2, 0, 100L), 4.94e-322, true),
+ arguments(mutator, Stream.of(false, Double.longBitsToDouble(1)),
+ Stream.of(false, 2, 0, -1L), 0, true),
+ // mutateWithMathematicalFn
+ arguments(mutator, Stream.of(false, 10.1), Stream.of(false, 3, 4), 11, true), // Math::ceil
+ arguments(
+ mutator, Stream.of(false, 1000.0), Stream.of(false, 3, 11), 3, true), // Math::log10
+ // skip libfuzzer
+ // random in range
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 5, 10.0), 10, true),
+ // unknown mutation case exception
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 6), UNUSED_DOUBLE, false));
+ }
+
+ static Stream<Arguments> doubleMutateLimitedRangeCases() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(min = -1, max = 1, allowNaN = false) Double>() {
+ }.annotatedType());
+ // Init value can be set to desired one by giving this to the init method: (false, <desired
+ // value>)
+ return Stream.of(
+ // Bit flip; forceInRange(); result equals previous value; adjust value
+ arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 62, true),
+ 0.0 - Double.MIN_VALUE, true),
+ arguments(
+ mutator, Stream.of(false, 1.0), Stream.of(false, 0, 62), Math.nextDown(1.0), true),
+ arguments(
+ mutator, Stream.of(false, -1.0), Stream.of(false, 0, 62), Math.nextUp(-1.0), true),
+ // NaN after mutateWithMathematicalFn: sqrt(-1.0); NaN not allowed; forceInRange will return
+ // (min+max)/2
+ arguments(mutator, Stream.of(false, -1.0), Stream.of(false, 3, 16), 0.0, true));
+ }
+
+ static Stream<Arguments> doubleMutateLimitedRangeCasesWithNaN() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @DoubleInRange(min = -1, max = 1, allowNaN = true) Double>() {
+ }.annotatedType());
+ // Init value can be set to desired one by giving this to the init method: (false, <desired
+ // value>)
+ return Stream.of(
+ // NaN after mutation and forceInRange(); all good!
+ arguments(mutator, Stream.of(false, -1.0), Stream.of(false, 3, 16), Double.NaN, true),
+ // NaN (with a set bit #8) after init, mutation, and forceInRange(); need to change NaN to
+ // something else
+ arguments(mutator, Stream.of(true, 6), Stream.of(false, 0, 8, 0.3), 0.3, true));
+ }
+
+ @ParameterizedTest
+ @MethodSource({"doubleMutateSanityChecksFullRangeCases", "doubleMutateLimitedRangeCases",
+ "doubleMutateLimitedRangeCasesWithNaN"})
+ void
+ testDoubleMutateCases(SerializingMutator<Double> mutator, Stream<Object> initValues,
+ Stream<Object> mutationValues, double expected, boolean knownSwitchCase) {
+ assertThat(mutator.toString()).isEqualTo("Double");
+ Double n;
+
+ // Init
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(initValues.toArray())) {
+ n = mutator.init(prng);
+ }
+
+ // Mutate
+ if (knownSwitchCase) {
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) {
+ n = mutator.mutate(n, prng);
+ }
+ assertThat(n).isEqualTo(expected);
+
+ if (!((DoubleMutator) mutator).allowNaN) {
+ assertThat(n).isNotEqualTo(Double.NaN);
+ }
+
+ if (!Double.isNaN(n)) {
+ assertThat(n).isAtLeast(((DoubleMutator) mutator).minValue);
+ assertThat(n).isAtMost(((DoubleMutator) mutator).maxValue);
+ }
+ } else { // Invalid mutation because a case is not handled
+ assertThrows(AssertionError.class, () -> {
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) {
+ mutator.mutate(UNUSED_DOUBLE, prng);
+ }
+ });
+ }
+ }
+
+ @Test
+ void testDoubleCrossOverMean() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Double>() {}.annotatedType());
+ try (TestSupport.MockPseudoRandom prng =
+ mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) {
+ assertThat(mutator.crossOver(0.0, 0.0, prng)).isWithin(0).of(0f);
+ assertThat(mutator.crossOver(-0.0, 0.0, prng)).isWithin(0).of(0f);
+ assertThat(mutator.crossOver(0.0, 2.0, prng)).isWithin(1e-10f).of(1.0f);
+ assertThat(mutator.crossOver(1.0, 2.0, prng)).isWithin(1e-10f).of(1.5f);
+ assertThat(mutator.crossOver(1.0, 3.0, prng)).isWithin(1e-10f).of(2f);
+ assertThat(mutator.crossOver(Double.MAX_VALUE, Double.MAX_VALUE, prng))
+ .isWithin(1e-10f)
+ .of(Double.MAX_VALUE);
+
+ assertThat(mutator.crossOver(0.0, -2.0, prng)).isWithin(1e-10f).of(-1.0f);
+ assertThat(mutator.crossOver(-1.0, -2.0, prng)).isWithin(1e-10f).of(-1.5f);
+ assertThat(mutator.crossOver(-1.0, -3.0, prng)).isWithin(1e-10f).of(-2f);
+ assertThat(mutator.crossOver(-Double.MAX_VALUE, -Double.MAX_VALUE, prng))
+ .isWithin(1e-10f)
+ .of(-Double.MAX_VALUE);
+
+ assertThat(mutator.crossOver(-100.0, 200.0, prng)).isWithin(1e-10f).of(50.0f);
+ assertThat(mutator.crossOver(100.0, -200.0, prng)).isWithin(1e-10f).of(-50f);
+ assertThat(mutator.crossOver(-Double.MAX_VALUE, Double.MAX_VALUE, prng))
+ .isWithin(1e-10f)
+ .of(0f);
+
+ assertThat(mutator.crossOver(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, prng))
+ .isNaN();
+ assertThat(mutator.crossOver(Double.POSITIVE_INFINITY, 0.0, prng)).isPositiveInfinity();
+ assertThat(mutator.crossOver(0.0, Double.POSITIVE_INFINITY, prng)).isPositiveInfinity();
+ assertThat(mutator.crossOver(Double.NEGATIVE_INFINITY, 0.0, prng)).isNegativeInfinity();
+ assertThat(mutator.crossOver(0.0, Double.NEGATIVE_INFINITY, prng)).isNegativeInfinity();
+ assertThat(mutator.crossOver(Double.NaN, 0.0, prng)).isNaN();
+ assertThat(mutator.crossOver(0.0, Double.NaN, prng)).isNaN();
+ }
+ }
+
+ @Test
+ void testDoubleCrossOverExponent() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Double>() {}.annotatedType());
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(1, 1, 1)) {
+ assertThat(mutator.crossOver(2.0, -1.5, prng)).isWithin(1e-10f).of(1.0f);
+ assertThat(mutator.crossOver(2.0, Double.POSITIVE_INFINITY, prng)).isPositiveInfinity();
+ assertThat(mutator.crossOver(-1.5, Double.NEGATIVE_INFINITY, prng)).isNaN();
+ }
+ }
+
+ @Test
+ void testDoubleCrossOverMantissa() {
+ SerializingMutator<Double> mutator =
+ (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Double>() {}.annotatedType());
+ try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(2, 2, 2)) {
+ assertThat(mutator.crossOver(4.0, 3.5, prng)).isWithin(1e-10f).of(7.0f);
+ assertThat(mutator.crossOver(Double.POSITIVE_INFINITY, 3.0, prng)).isNaN();
+ assertThat(mutator.crossOver(Double.MAX_VALUE, 0.0, prng))
+ .isWithin(1e-10f)
+ .of(8.98846567431158e307);
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java
new file mode 100644
index 00000000..dda8cfed
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.mutator.lang.IntegralMutatorFactory.AbstractIntegralMutator.forceInRange;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@SuppressWarnings("unchecked")
+class IntegralMutatorTest {
+ static Stream<Arguments> forceInRangeCases() {
+ return Stream.of(arguments(0, 0, 1), arguments(5, 0, 1), arguments(-5, -10, -1),
+ arguments(-200, -10, -1), arguments(10, 0, 3), arguments(-5, 0, 3), arguments(10, -7, 7),
+ arguments(Long.MIN_VALUE, Long.MIN_VALUE, Long.MAX_VALUE),
+ arguments(Long.MIN_VALUE, Long.MIN_VALUE, 100),
+ arguments(Long.MIN_VALUE + 100, Long.MIN_VALUE, 100),
+ arguments(Long.MAX_VALUE, -100, Long.MAX_VALUE),
+ arguments(Long.MAX_VALUE - 100, -100, Long.MAX_VALUE),
+ arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE),
+ arguments(Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MAX_VALUE),
+ arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE - 1),
+ arguments(Long.MIN_VALUE, Long.MAX_VALUE - 5, Long.MAX_VALUE),
+ arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MIN_VALUE + 5));
+ }
+
+ @ParameterizedTest
+ @MethodSource("forceInRangeCases")
+ void testForceInRange(long value, long minValue, long maxValue) {
+ long inRange = forceInRange(value, minValue, maxValue);
+ assertThat(inRange).isAtLeast(minValue);
+ assertThat(inRange).isAtMost(maxValue);
+ if (value >= minValue && value <= maxValue) {
+ assertThat(inRange).isEqualTo(value);
+ }
+ }
+
+ @Test
+ void testCrossOver() {
+ SerializingMutator<Long> mutator =
+ (SerializingMutator<Long>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull Long>() {}.annotatedType());
+ // cross over mean values
+ try (MockPseudoRandom prng = mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) {
+ assertThat(mutator.crossOver(0L, 0L, prng)).isEqualTo(0);
+ assertThat(mutator.crossOver(0L, 2L, prng)).isEqualTo(1);
+ assertThat(mutator.crossOver(1L, 2L, prng)).isEqualTo(1);
+ assertThat(mutator.crossOver(1L, 3L, prng)).isEqualTo(2);
+ assertThat(mutator.crossOver(Long.MAX_VALUE, Long.MAX_VALUE, prng)).isEqualTo(Long.MAX_VALUE);
+
+ assertThat(mutator.crossOver(0L, -2L, prng)).isEqualTo(-1);
+ assertThat(mutator.crossOver(-1L, -2L, prng)).isEqualTo(-1);
+ assertThat(mutator.crossOver(-1L, -3L, prng)).isEqualTo(-2);
+ assertThat(mutator.crossOver(Long.MIN_VALUE, Long.MIN_VALUE, prng)).isEqualTo(Long.MIN_VALUE);
+
+ assertThat(mutator.crossOver(-100L, 200L, prng)).isEqualTo(50);
+ assertThat(mutator.crossOver(100L, -200L, prng)).isEqualTo(-50);
+ assertThat(mutator.crossOver(Long.MIN_VALUE, Long.MAX_VALUE, prng)).isEqualTo(0);
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java
new file mode 100644
index 00000000..bc9a65b2
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import java.lang.reflect.AnnotatedType;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings("unchecked")
+class NullableMutatorTest {
+ @Test
+ void testNullable() {
+ SerializingMutator<Boolean> mutator =
+ new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory())
+ .createOrThrow(Boolean.class);
+ assertThat(mutator.toString()).isEqualTo("Nullable<Boolean>");
+
+ Boolean bool;
+ try (MockPseudoRandom prng = mockPseudoRandom(/* init to null */ true)) {
+ bool = mutator.init(prng);
+ }
+ assertThat(bool).isNull();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* init for non-null Boolean */ false)) {
+ bool = mutator.mutate(bool, prng);
+ }
+ assertThat(bool).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* mutate to non-null Boolean */ false)) {
+ bool = mutator.mutate(bool, prng);
+ }
+ assertThat(bool).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* mutate to null */ true)) {
+ bool = mutator.mutate(bool, prng);
+ }
+ assertThat(bool).isNull();
+ }
+
+ @Test
+ void testNotNull() {
+ ChainedMutatorFactory factory =
+ new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory());
+ AnnotatedType notNullBoolean = new TypeHolder<@NotNull Boolean>() {}.annotatedType();
+ SerializingMutator<Boolean> mutator =
+ (SerializingMutator<Boolean>) factory.createOrThrow(notNullBoolean);
+ assertThat(mutator.toString()).isEqualTo("Boolean");
+ }
+
+ @Test
+ void testPrimitive() {
+ ChainedMutatorFactory factory =
+ new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory());
+ SerializingMutator<Boolean> mutator = factory.createOrThrow(boolean.class);
+ assertThat(mutator.toString()).isEqualTo("Boolean");
+ }
+
+ @Test
+ void testCrossOver() {
+ SerializingMutator<Boolean> mutator =
+ new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory())
+ .createOrThrow(Boolean.class);
+ try (MockPseudoRandom prng = mockPseudoRandom(true)) {
+ Boolean valueCrossedOver = mutator.crossOver(Boolean.TRUE, Boolean.TRUE, prng);
+ assertThat(valueCrossedOver).isNotNull();
+ }
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ Boolean bothNull = mutator.crossOver(null, null, prng);
+ assertThat(bothNull).isNull();
+ }
+ try (MockPseudoRandom prng = mockPseudoRandom(false)) {
+ Boolean oneNotNull = mutator.crossOver(null, Boolean.TRUE, prng);
+ assertThat(oneNotNull).isNotNull();
+ }
+ try (MockPseudoRandom prng = mockPseudoRandom(true)) {
+ Boolean nullFrequency = mutator.crossOver(null, Boolean.TRUE, prng);
+ assertThat(nullFrequency).isNull();
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java
new file mode 100644
index 00000000..23060359
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.lang;
+
+import static com.code_intelligence.jazzer.mutation.mutator.lang.StringMutatorFactory.fixUpAscii;
+import static com.code_intelligence.jazzer.mutation.mutator.lang.StringMutatorFactory.fixUpUtf8;
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator;
+import com.code_intelligence.jazzer.mutation.support.RandomSupport;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import com.google.protobuf.ByteString;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.SplittableRandom;
+import org.junit.jupiter.api.*;
+
+class StringMutatorTest {
+ /**
+ * Some tests may set {@link LibFuzzerMutator#MOCK_SIZE_KEY} which can interfere with other tests
+ * unless cleared.
+ */
+ @AfterEach
+ void cleanMockSize() {
+ System.clearProperty(LibFuzzerMutator.MOCK_SIZE_KEY);
+ }
+
+ @RepeatedTest(10)
+ void testFixAscii_randomInputFixed(RepetitionInfo info) {
+ SplittableRandom random = new SplittableRandom(
+ (long) "testFixAscii_randomInputFixed".hashCode() * info.getCurrentRepetition());
+
+ for (int length = 0; length < 1000; length++) {
+ byte[] randomBytes = generateRandomBytes(random, length);
+ byte[] copy = Arrays.copyOf(randomBytes, randomBytes.length);
+ fixUpAscii(copy);
+ if (isValidAscii(randomBytes)) {
+ assertThat(copy).isEqualTo(randomBytes);
+ } else {
+ assertThat(isValidAscii(copy)).isTrue();
+ }
+ }
+ }
+
+ @RepeatedTest(10)
+ void testFixAscii_validInputNotChanged(RepetitionInfo info) {
+ SplittableRandom random = new SplittableRandom(
+ (long) "testFixAscii_validInputNotChanged".hashCode() * info.getCurrentRepetition());
+
+ for (int codePoints = 0; codePoints < 1000; codePoints++) {
+ byte[] validAscii = generateValidAsciiBytes(random, codePoints);
+ byte[] copy = Arrays.copyOf(validAscii, validAscii.length);
+ fixUpAscii(copy);
+ assertThat(copy).isEqualTo(validAscii);
+ }
+ }
+
+ @RepeatedTest(20)
+ void testFixUtf8_randomInputFixed(RepetitionInfo info) {
+ SplittableRandom random = new SplittableRandom(
+ (long) "testFixUtf8_randomInputFixed".hashCode() * info.getCurrentRepetition());
+
+ for (int length = 0; length < 1000; length++) {
+ byte[] randomBytes = generateRandomBytes(random, length);
+ byte[] copy = Arrays.copyOf(randomBytes, randomBytes.length);
+ fixUpUtf8(copy);
+ if (isValidUtf8(randomBytes)) {
+ assertThat(copy).isEqualTo(randomBytes);
+ } else {
+ assertThat(isValidUtf8(copy)).isTrue();
+ }
+ }
+ }
+
+ @RepeatedTest(20)
+ void testFixUtf8_validInputNotChanged(RepetitionInfo info) {
+ SplittableRandom random = new SplittableRandom(
+ (long) "testFixUtf8_validInputNotChanged".hashCode() * info.getCurrentRepetition());
+
+ for (int codePoints = 0; codePoints < 1000; codePoints++) {
+ byte[] validUtf8 = generateValidUtf8Bytes(random, codePoints);
+ byte[] copy = Arrays.copyOf(validUtf8, validUtf8.length);
+ fixUpUtf8(copy);
+ assertThat(copy).isEqualTo(validUtf8);
+ }
+ }
+
+ @Test
+ void testMinLengthInit() {
+ SerializingMutator<String> mutator =
+ (SerializingMutator<String>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("String");
+
+ try (MockPseudoRandom prng = mockPseudoRandom(5)) {
+ // mock prng should throw an assert error when given a lower value than min
+ Assertions.assertThrows(AssertionError.class, () -> { String s = mutator.init(prng); });
+ }
+ }
+
+ @Test
+ void testMaxLengthInit() {
+ SerializingMutator<String> mutator =
+ (SerializingMutator<String>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @WithUtf8Length(max = 50) String>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("String");
+
+ try (MockPseudoRandom prng = mockPseudoRandom(60)) {
+ // mock prng should throw an assert error when given a value higher than max
+ Assertions.assertThrows(AssertionError.class, () -> { String s = mutator.init(prng); });
+ }
+ }
+
+ @Test
+ void testMinLengthMutate() {
+ SerializingMutator<String> mutator =
+ (SerializingMutator<String>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("String");
+
+ String s;
+ try (MockPseudoRandom prng = mockPseudoRandom(10, "foobarbazf".getBytes())) {
+ s = mutator.init(prng);
+ }
+ assertThat(s).isEqualTo("foobarbazf");
+
+ System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "5");
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ s = mutator.mutate(s, prng);
+ }
+ assertThat(s).isEqualTo("gqrff\0\0\0\0\0");
+ }
+
+ @Test
+ void testMaxLengthMutate() {
+ SerializingMutator<String> mutator =
+ (SerializingMutator<String>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @WithUtf8Length(max = 15) String>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("String");
+
+ String s;
+ try (MockPseudoRandom prng = mockPseudoRandom(10, "foobarbazf".getBytes())) {
+ s = mutator.init(prng);
+ }
+ assertThat(s).isEqualTo("foobarbazf");
+
+ System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "20");
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ Assertions.assertThrows(
+ ArrayIndexOutOfBoundsException.class, () -> { String s2 = mutator.mutate(s, prng); });
+ }
+ }
+
+ @Test
+ void testMultibyteCharacters() {
+ SerializingMutator<String> mutator =
+ (SerializingMutator<String>) LangMutators.newFactory().createOrThrow(
+ new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("String");
+
+ String s;
+ try (
+ MockPseudoRandom prng = mockPseudoRandom(10, "foobarÖÖ".getBytes(StandardCharsets.UTF_8))) {
+ s = mutator.init(prng);
+ }
+ assertThat(s).hasLength(8);
+ assertThat(s).isEqualTo("foobarÖÖ");
+ }
+
+ private static boolean isValidUtf8(byte[] data) {
+ return ByteString.copyFrom(data).isValidUtf8();
+ }
+
+ private static boolean isValidAscii(byte[] data) {
+ for (byte b : data) {
+ if ((b & 0xFF) > 0x7F) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static byte[] generateRandomBytes(SplittableRandom random, int length) {
+ byte[] bytes = new byte[length];
+ RandomSupport.nextBytes(random, bytes);
+ return bytes;
+ }
+
+ private static byte[] generateValidAsciiBytes(SplittableRandom random, int length) {
+ return random.ints(0, 0x7F)
+ .limit(length)
+ .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+ .toString()
+ .getBytes(StandardCharsets.UTF_8);
+ }
+
+ private static byte[] generateValidUtf8Bytes(SplittableRandom random, long codePoints) {
+ return random.ints(0, Character.MAX_CODE_POINT + 1)
+ .filter(code -> code < Character.MIN_SURROGATE || code > Character.MAX_SURROGATE)
+ .limit(codePoints)
+ .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+ .toString()
+ .getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel
new file mode 100644
index 00000000..bf8b551d
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel
@@ -0,0 +1,60 @@
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
+
+proto_library(
+ name = "proto3_proto",
+ srcs = ["proto3.proto"],
+ deps = [
+ "@com_google_protobuf//:any_proto",
+ ],
+)
+
+java_proto_library(
+ name = "proto3_java_proto",
+ testonly = True,
+ visibility = ["//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__"],
+ deps = [":proto3_proto"],
+)
+
+proto_library(
+ name = "proto2_proto",
+ srcs = ["proto2.proto"],
+)
+
+java_proto_library(
+ name = "proto2_java_proto",
+ testonly = True,
+ visibility = [
+ "//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__",
+ "//tests:__pkg__",
+ ],
+ deps = [":proto2_proto"],
+)
+
+cc_proto_library(
+ name = "proto2_cc_proto",
+ testonly = True,
+ visibility = [
+ "//tests:__pkg__",
+ ],
+ deps = [":proto2_proto"],
+)
+
+java_test_suite(
+ name = "ProtoTests",
+ size = "small",
+ srcs = glob(["*.java"]),
+ runner = "junit5",
+ deps = [
+ ":proto2_java_proto",
+ ":proto3_java_proto",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support",
+ "@com_google_protobuf//java/core",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java
new file mode 100644
index 00000000..7722a6ad
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.proto;
+
+import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.makeMutableRepeatedFieldView;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedIntegralField3;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class BuilderAdaptersTest {
+ @Test
+ void testMakeMutableRepeatedFieldView() {
+ RepeatedIntegralField3.Builder builder = RepeatedIntegralField3.newBuilder();
+ FieldDescriptor someField = builder.getDescriptorForType().findFieldByNumber(1);
+ assertThat(someField).isNotNull();
+
+ List<Integer> view = makeMutableRepeatedFieldView(builder, someField);
+ assertThat(builder.build().getSomeFieldList()).isEmpty();
+
+ assertThat(view.add(1)).isTrue();
+ assertThat(view.get(0)).isEqualTo(1);
+ assertThat(view).hasSize(1);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1).inOrder();
+ assertThrows(IndexOutOfBoundsException.class, () -> view.get(1));
+
+ assertThat(view.add(2)).isTrue();
+ assertThat(view.add(3)).isTrue();
+ assertThat(view).hasSize(3);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 2, 3).inOrder();
+ assertThrows(IndexOutOfBoundsException.class, () -> view.get(3));
+
+ assertThat(view.set(1, 4)).isEqualTo(2);
+ assertThat(view).hasSize(3);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 4, 3).inOrder();
+
+ assertThat(view.set(1, 5)).isEqualTo(4);
+ assertThat(view).hasSize(3);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 5, 3).inOrder();
+
+ assertThat(view.remove(1)).isEqualTo(5);
+ assertThat(view).hasSize(2);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 3).inOrder();
+
+ assertThrows(IndexOutOfBoundsException.class, () -> view.remove(-1));
+ assertThrows(IndexOutOfBoundsException.class, () -> view.remove(2));
+
+ assertThat(view.addAll(1, Collections.emptyList())).isFalse();
+ assertThat(view).hasSize(2);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 3).inOrder();
+
+ assertThat(view.addAll(1, Arrays.asList(6, 7, 8))).isTrue();
+ assertThat(view).hasSize(5);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 6, 7, 8, 3).inOrder();
+
+ view.subList(2, 4).clear();
+ assertThat(view).hasSize(3);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 6, 3).inOrder();
+
+ assertThat(view.addAll(3, Arrays.asList(9, 10))).isTrue();
+ assertThat(view).hasSize(5);
+ assertThat(builder.build().getSomeFieldList()).containsExactly(1, 6, 3, 9, 10).inOrder();
+
+ view.clear();
+ assertThat(view).hasSize(0);
+ assertThat(builder.build().getSomeFieldList()).isEmpty();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java
new file mode 100644
index 00000000..9492bcec
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.proto;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.InPlaceMutator;
+import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
+import com.code_intelligence.jazzer.mutation.mutator.collection.CollectionMutators;
+import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import com.code_intelligence.jazzer.protobuf.Proto2.MessageField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.OneOfField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.PrimitiveField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.RecursiveMessageField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.RepeatedMessageField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.RepeatedOptionalMessageField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.RepeatedPrimitiveField2;
+import com.code_intelligence.jazzer.protobuf.Proto2.RequiredPrimitiveField2;
+import org.junit.jupiter.api.Test;
+
+class BuilderMutatorProto2Test {
+ private static final MutatorFactory FACTORY = new ChainedMutatorFactory(
+ LangMutators.newFactory(), CollectionMutators.newFactory(), ProtoMutators.newFactory());
+
+ @Test
+ void testPrimitiveField() {
+ InPlaceMutator<PrimitiveField2.Builder> mutator =
+ (InPlaceMutator<PrimitiveField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<PrimitiveField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<Boolean>}");
+
+ PrimitiveField2.Builder builder = PrimitiveField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // present
+ false,
+ // boolean
+ false)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isTrue();
+ assertThat(builder.getSomeField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // present
+ false,
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isTrue();
+ assertThat(builder.getSomeField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate as non-null Boolean
+ false)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isTrue();
+ assertThat(builder.getSomeField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // not present
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isFalse();
+ assertThat(builder.getSomeField()).isFalse();
+ }
+
+ @Test
+ void testRequiredPrimitiveField() {
+ InPlaceMutator<RequiredPrimitiveField2.Builder> mutator =
+ (InPlaceMutator<RequiredPrimitiveField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RequiredPrimitiveField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Boolean}");
+
+ RequiredPrimitiveField2.Builder builder = RequiredPrimitiveField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* mutate first field */ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isFalse();
+ }
+
+ @Test
+ void testRepeatedPrimitiveField() {
+ InPlaceMutator<RepeatedPrimitiveField2.Builder> mutator =
+ (InPlaceMutator<RepeatedPrimitiveField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RepeatedPrimitiveField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder via List<Boolean>}");
+
+ RepeatedPrimitiveField2.Builder builder = RepeatedPrimitiveField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list size 1
+ 1,
+ // boolean,
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).containsExactly(true).inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by adding an entry
+ 1,
+ // add a single element
+ 1,
+ // add the element at the end
+ 1,
+ // value to add
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).containsExactly(true, true).inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by changing an entry
+ 2,
+ // mutate a single element
+ 1,
+ // mutate the second element
+ 1)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).containsExactly(true, false).inOrder();
+ }
+
+ @Test
+ void testMessageField() {
+ InPlaceMutator<MessageField2.Builder> mutator =
+ (InPlaceMutator<MessageField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<MessageField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<{Builder.Boolean} -> Message>}");
+
+ MessageField2.Builder builder = MessageField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // init submessage
+ false,
+ // boolean submessage field
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+
+ assertThat(builder.getMessageField())
+ .isEqualTo(RequiredPrimitiveField2.newBuilder().setSomeField(true).build());
+ assertThat(builder.hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate submessage as non-null
+ false,
+ // mutate first field
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageField())
+ .isEqualTo(RequiredPrimitiveField2.newBuilder().setSomeField(false).build());
+ assertThat(builder.hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate submessage to null
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.hasMessageField()).isFalse();
+ }
+
+ @Test
+ void testRepeatedOptionalMessageField() {
+ InPlaceMutator<RepeatedOptionalMessageField2.Builder> mutator =
+ (InPlaceMutator<RepeatedOptionalMessageField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RepeatedOptionalMessageField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo("{Builder via List<{Builder.Nullable<Boolean>} -> Message>}");
+
+ RepeatedOptionalMessageField2.Builder builder = RepeatedOptionalMessageField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list size 1
+ 1,
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList().toString()).isEqualTo("[]");
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by adding an entry
+ 1,
+ // add a single element
+ 1,
+ // add the element at the end
+ 1,
+ // Nullable mutator init
+ false,
+ // duplicate entry
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList().size()).isEqualTo(2);
+ }
+
+ @Test
+ void testRepeatedRequiredMessageField() {
+ InPlaceMutator<RepeatedMessageField2.Builder> mutator =
+ (InPlaceMutator<RepeatedMessageField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RepeatedMessageField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder via List<{Builder.Boolean} -> Message>}");
+
+ RepeatedMessageField2.Builder builder = RepeatedMessageField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list size 1
+ 1,
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList())
+ .containsExactly(RequiredPrimitiveField2.newBuilder().setSomeField(true).build())
+ .inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by adding an entry
+ 1,
+ // add a single element
+ 1,
+ // add the element at the end
+ 1,
+ // value to add
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList())
+ .containsExactly(RequiredPrimitiveField2.newBuilder().setSomeField(true).build(),
+ RequiredPrimitiveField2.newBuilder().setSomeField(true).build())
+ .inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // change an entry
+ 2,
+ // mutate a single element
+ 1,
+ // mutate the second element,
+ 1,
+ // mutate the first element
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList())
+ .containsExactly(RequiredPrimitiveField2.newBuilder().setSomeField(true).build(),
+ RequiredPrimitiveField2.newBuilder().setSomeField(false).build())
+ .inOrder();
+ }
+
+ @Test
+ void testRecursiveMessageField() {
+ InPlaceMutator<RecursiveMessageField2.Builder> mutator =
+ (InPlaceMutator<RecursiveMessageField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RecursiveMessageField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo("{Builder.Boolean, WithoutInit(Builder.Nullable<(cycle) -> Message>)}");
+ RecursiveMessageField2.Builder builder = RecursiveMessageField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+
+ assertThat(builder.build())
+ .isEqualTo(RecursiveMessageField2.newBuilder().setSomeField(true).build());
+ assertThat(builder.hasMessageField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate message field (causes init to non-null)
+ 1,
+ // bool field in message field
+ false)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ // Nested message field *is* set explicitly and implicitly equal to the default
+ // instance.
+ assertThat(builder.build())
+ .isEqualTo(RecursiveMessageField2.newBuilder()
+ .setSomeField(true)
+ .setMessageField(RecursiveMessageField2.newBuilder().setSomeField(false))
+ .build());
+ assertThat(builder.hasMessageField()).isTrue();
+ assertThat(builder.getMessageField().hasMessageField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate message field
+ 1,
+ // message field as not null
+ false,
+ // mutate message field
+ 1,
+ // nested boolean,
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(RecursiveMessageField2.newBuilder()
+ .setSomeField(true)
+ .setMessageField(
+ RecursiveMessageField2.newBuilder().setSomeField(false).setMessageField(
+ RecursiveMessageField2.newBuilder().setSomeField(true)))
+ .build());
+ assertThat(builder.hasMessageField()).isTrue();
+ assertThat(builder.getMessageField().hasMessageField()).isTrue();
+ assertThat(builder.getMessageField().getMessageField().hasMessageField()).isFalse();
+ }
+
+ @Test
+ void testOneOfField2() {
+ InPlaceMutator<OneOfField2.Builder> mutator =
+ (InPlaceMutator<OneOfField2.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<OneOfField2.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo(
+ "{Builder.Boolean, Builder.Nullable<Boolean>, Builder.Nullable<Boolean> | Builder.Nullable<{Builder.Boolean} -> Message>}");
+ OneOfField2.Builder builder = OneOfField2.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // other_field
+ true,
+ // yet_another_field
+ true,
+ // oneof: first field
+ 0,
+ // bool_field present
+ false,
+ // bool_field
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField2.newBuilder().setOtherField(true).setBoolField(true).build());
+ assertThat(builder.build().hasBoolField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // preserve oneof state
+ false,
+ // mutate bool_field as non-null
+ false)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField2.newBuilder().setOtherField(true).setBoolField(false).build());
+ assertThat(builder.build().hasBoolField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // switch oneof state
+ true,
+ // new oneof state
+ 1,
+ // init message_field as non-null
+ false,
+ // init some_field as true
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField2.newBuilder()
+ .setOtherField(true)
+ .setMessageField(RequiredPrimitiveField2.newBuilder().setSomeField(true))
+ .build());
+ assertThat(builder.build().hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // preserve oneof state
+ false,
+ // mutate message_field as non-null
+ false,
+ // mutate some_field
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField2.newBuilder()
+ .setOtherField(true)
+ .setMessageField(RequiredPrimitiveField2.newBuilder().setSomeField(false))
+ .build());
+ assertThat(builder.build().hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // preserve oneof state
+ false,
+ // mutate message_field to null (and thus oneof state to indeterminate)
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build()).isEqualTo(OneOfField2.newBuilder().setOtherField(true).build());
+ assertThat(builder.build().hasMessageField()).isFalse();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java
new file mode 100644
index 00000000..ff298540
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.proto;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource;
+import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.InPlaceMutator;
+import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
+import com.code_intelligence.jazzer.mutation.mutator.collection.CollectionMutators;
+import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import com.code_intelligence.jazzer.protobuf.Proto3.AnyField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.AnyField3.Builder;
+import com.code_intelligence.jazzer.protobuf.Proto3.EmptyMessage3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3.TestEnum;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldOne3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldOne3.TestEnumOne;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldOutside3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3;
+import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3.TestEnumRepeated;
+import com.code_intelligence.jazzer.protobuf.Proto3.MessageField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.OneOfField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.OptionalPrimitiveField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.PrimitiveField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RecursiveMessageField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedMessageField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedPrimitiveField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.TestEnumOutside3;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+
+class BuilderMutatorProto3Test {
+ private static final MutatorFactory FACTORY = new ChainedMutatorFactory(
+ LangMutators.newFactory(), CollectionMutators.newFactory(), ProtoMutators.newFactory());
+
+ @Test
+ void testPrimitiveField() {
+ InPlaceMutator<PrimitiveField3.Builder> mutator =
+ (InPlaceMutator<PrimitiveField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<PrimitiveField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Boolean}");
+
+ PrimitiveField3.Builder builder = PrimitiveField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(/* mutate first field */ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isFalse();
+ }
+
+ @Test
+ void testEnumField() {
+ InPlaceMutator<EnumField3.Builder> mutator =
+ (InPlaceMutator<EnumField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<EnumField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Enum<TestEnum>}");
+ EnumField3.Builder builder = EnumField3.newBuilder();
+ try (MockPseudoRandom prng = mockPseudoRandom(0)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isEqualTo(TestEnum.VAL1);
+ try (MockPseudoRandom prng = mockPseudoRandom(0, 1)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isEqualTo(TestEnum.VAL2);
+ }
+
+ @Test
+ void testEnumFieldOutside() {
+ InPlaceMutator<EnumFieldOutside3.Builder> mutator =
+ (InPlaceMutator<EnumFieldOutside3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<EnumFieldOutside3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Enum<TestEnumOutside3>}");
+ EnumFieldOutside3.Builder builder = EnumFieldOutside3.newBuilder();
+ try (MockPseudoRandom prng = mockPseudoRandom(0)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isEqualTo(TestEnumOutside3.VAL1);
+ try (MockPseudoRandom prng = mockPseudoRandom(0, 2)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isEqualTo(TestEnumOutside3.VAL3);
+ }
+
+ @Test
+ void testEnumFieldWithOneValue() {
+ InPlaceMutator<EnumFieldOne3.Builder> mutator =
+ (InPlaceMutator<EnumFieldOne3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<EnumFieldOne3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.FixedValue(ONE)}");
+ EnumFieldOne3.Builder builder = EnumFieldOne3.newBuilder();
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isEqualTo(TestEnumOne.ONE);
+ try (MockPseudoRandom prng = mockPseudoRandom(0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeField()).isEqualTo(TestEnumOne.ONE);
+ }
+
+ @Test
+ void testRepeatedEnumField() {
+ InPlaceMutator<EnumFieldRepeated3.Builder> mutator =
+ (InPlaceMutator<EnumFieldRepeated3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<EnumFieldRepeated3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder via List<Enum<TestEnumRepeated>>}");
+ EnumFieldRepeated3.Builder builder = EnumFieldRepeated3.newBuilder();
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list size
+ 1, // Only possible start value
+ // enum values
+ 2)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).isEqualTo(Arrays.asList(TestEnumRepeated.VAL2));
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // change an entry
+ 2,
+ // mutate a single element
+ 1,
+ // mutate to first enum field
+ 0,
+ // mutate to first enum value
+ 1)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).isEqualTo(Arrays.asList(TestEnumRepeated.VAL1));
+ }
+
+ @Test
+ void testOptionalPrimitiveField() {
+ InPlaceMutator<OptionalPrimitiveField3.Builder> mutator =
+ (InPlaceMutator<OptionalPrimitiveField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<OptionalPrimitiveField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<Boolean>}");
+
+ OptionalPrimitiveField3.Builder builder = OptionalPrimitiveField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // present
+ false,
+ // boolean
+ false)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isTrue();
+ assertThat(builder.getSomeField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // present
+ false,
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isTrue();
+ assertThat(builder.getSomeField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate as non-null Boolean
+ false)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isTrue();
+ assertThat(builder.getSomeField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // not present
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.hasSomeField()).isFalse();
+ assertThat(builder.getSomeField()).isFalse();
+ }
+
+ @Test
+ void testRepeatedPrimitiveField() {
+ InPlaceMutator<RepeatedPrimitiveField3.Builder> mutator =
+ (InPlaceMutator<RepeatedPrimitiveField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RepeatedPrimitiveField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder via List<Boolean>}");
+
+ RepeatedPrimitiveField3.Builder builder = RepeatedPrimitiveField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list size 1
+ 1,
+ // boolean,
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).containsExactly(true).inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by adding an entry
+ 1,
+ // add a single element
+ 1,
+ // add the element at the end
+ 1,
+ // value to add
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).containsExactly(true, true).inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by changing an entry
+ 2,
+ // mutate a single element
+ 1,
+ // mutate the second element
+ 1)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getSomeFieldList()).containsExactly(true, false).inOrder();
+ }
+
+ @Test
+ void testMessageField() {
+ InPlaceMutator<MessageField3.Builder> mutator =
+ (InPlaceMutator<MessageField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<MessageField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<{Builder.Boolean} -> Message>}");
+
+ MessageField3.Builder builder = MessageField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // init submessage
+ false,
+ // boolean submessage field
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageField())
+ .isEqualTo(PrimitiveField3.newBuilder().setSomeField(true).build());
+ assertThat(builder.hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate submessage as non-null
+ false,
+ // mutate first field
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageField())
+ .isEqualTo(PrimitiveField3.newBuilder().setSomeField(false).build());
+ assertThat(builder.hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate submessage to null
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.hasMessageField()).isFalse();
+ }
+
+ @Test
+ void testRepeatedMessageField() {
+ InPlaceMutator<RepeatedMessageField3.Builder> mutator =
+ (InPlaceMutator<RepeatedMessageField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RepeatedMessageField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{Builder via List<{Builder.Boolean} -> Message>}");
+
+ RepeatedMessageField3.Builder builder = RepeatedMessageField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // list size 1
+ 1,
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList())
+ .containsExactly(PrimitiveField3.newBuilder().setSomeField(true).build())
+ .inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // mutate the list itself by adding an entry
+ 1,
+ // add a single element
+ 1,
+ // add the element at the end
+ 1,
+ // value to add
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList())
+ .containsExactly(PrimitiveField3.newBuilder().setSomeField(true).build(),
+ PrimitiveField3.newBuilder().setSomeField(true).build())
+ .inOrder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate first field
+ 0,
+ // change an entry
+ 2,
+ // mutate a single element
+ 1,
+ // mutate the second element,
+ 1,
+ // mutate the first element
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.getMessageFieldList())
+ .containsExactly(PrimitiveField3.newBuilder().setSomeField(true).build(),
+ PrimitiveField3.newBuilder().setSomeField(false).build())
+ .inOrder();
+ }
+
+ @Test
+ void testRecursiveMessageField() {
+ InPlaceMutator<RecursiveMessageField3.Builder> mutator =
+ (InPlaceMutator<RecursiveMessageField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<RecursiveMessageField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo("{Builder.Boolean, WithoutInit(Builder.Nullable<(cycle) -> Message>)}");
+ RecursiveMessageField3.Builder builder = RecursiveMessageField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // boolean
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+
+ assertThat(builder.build())
+ .isEqualTo(RecursiveMessageField3.newBuilder().setSomeField(true).build());
+ assertThat(builder.hasMessageField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate message field (causes init to non-null)
+ 1,
+ // bool field in message field
+ false)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ // Nested message field *is* set explicitly and implicitly equal to the default
+ // instance.
+ assertThat(builder.build())
+ .isEqualTo(RecursiveMessageField3.newBuilder()
+ .setSomeField(true)
+ .setMessageField(RecursiveMessageField3.newBuilder().setSomeField(false))
+ .build());
+ assertThat(builder.hasMessageField()).isTrue();
+ assertThat(builder.getMessageField().hasMessageField()).isFalse();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate message field
+ 1,
+ // message field as not null
+ false,
+ // mutate message field
+ 1,
+ // nested boolean,
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(RecursiveMessageField3.newBuilder()
+ .setSomeField(true)
+ .setMessageField(
+ RecursiveMessageField3.newBuilder().setSomeField(false).setMessageField(
+ RecursiveMessageField3.newBuilder().setSomeField(true)))
+ .build());
+ assertThat(builder.hasMessageField()).isTrue();
+ assertThat(builder.getMessageField().hasMessageField()).isTrue();
+ assertThat(builder.getMessageField().getMessageField().hasMessageField()).isFalse();
+ }
+
+ @Test
+ void testOneOfField3() {
+ InPlaceMutator<OneOfField3.Builder> mutator =
+ (InPlaceMutator<OneOfField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<OneOfField3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo(
+ "{Builder.Boolean, Builder.Boolean, Builder.Nullable<Boolean> | Builder.Nullable<{Builder.Boolean} -> Message>}");
+ OneOfField3.Builder builder = OneOfField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // other_field
+ true,
+ // yet_another_field
+ true,
+ // oneof: first field
+ 0,
+ // bool_field present
+ false,
+ // bool_field
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField3.newBuilder()
+ .setOtherField(true)
+ .setBoolField(true)
+ .setYetAnotherField(true)
+ .build());
+ assertThat(builder.build().hasBoolField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // preserve oneof state
+ false,
+ // mutate bool_field as non-null
+ false)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField3.newBuilder()
+ .setOtherField(true)
+ .setBoolField(false)
+ .setYetAnotherField(true)
+ .build());
+ assertThat(builder.build().hasBoolField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // switch oneof state
+ true,
+ // new oneof state
+ 1,
+ // init message_field as non-null
+ false,
+ // init some_field as true
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField3.newBuilder()
+ .setOtherField(true)
+ .setMessageField(PrimitiveField3.newBuilder().setSomeField(true))
+ .setYetAnotherField(true)
+ .build());
+ assertThat(builder.build().hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // preserve oneof state
+ false,
+ // mutate message_field as non-null
+ false,
+ // mutate some_field
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField3.newBuilder()
+ .setOtherField(true)
+ .setMessageField(PrimitiveField3.newBuilder().setSomeField(false))
+ .setYetAnotherField(true)
+ .build());
+ assertThat(builder.build().hasMessageField()).isTrue();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate oneof
+ 2,
+ // preserve oneof state
+ false,
+ // mutate message_field to null (and thus oneof state to indeterminate)
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build())
+ .isEqualTo(OneOfField3.newBuilder().setOtherField(true).setYetAnotherField(true).build());
+ assertThat(builder.build().hasMessageField()).isFalse();
+ }
+
+ @Test
+ void testEmptyMessage3() {
+ InPlaceMutator<EmptyMessage3.Builder> mutator =
+ (InPlaceMutator<EmptyMessage3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<EmptyMessage3.@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString()).isEqualTo("{<empty>}");
+ EmptyMessage3.Builder builder = EmptyMessage3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.build()).isEqualTo(EmptyMessage3.getDefaultInstance());
+
+ try (MockPseudoRandom prng = mockPseudoRandom()) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build()).isEqualTo(EmptyMessage3.getDefaultInstance());
+ }
+
+ @Test
+ void testAnyField3() throws InvalidProtocolBufferException {
+ InPlaceMutator<AnyField3.Builder> mutator =
+ (InPlaceMutator<AnyField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<@NotNull @AnySource(
+ {PrimitiveField3.class, MessageField3.class}) Builder>() {
+ }.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo(
+ "{Builder.Nullable<Builder.{Builder.Boolean} -> Message | Builder.{Builder.Nullable<(cycle) -> Message>} -> Message -> Message>}");
+ AnyField3.Builder builder = AnyField3.newBuilder();
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // initialize message field
+ false,
+ // PrimitiveField3
+ 0,
+ // boolean field
+ true)) {
+ mutator.initInPlace(builder, prng);
+ }
+ assertThat(builder.build().getSomeField().unpack(PrimitiveField3.class))
+ .isEqualTo(PrimitiveField3.newBuilder().setSomeField(true).build());
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate Any field
+ 0,
+ // keep non-null message field
+ false,
+ // keep Any state,
+ false,
+ // mutate boolean field
+ 0)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build().getSomeField().unpack(PrimitiveField3.class))
+ .isEqualTo(PrimitiveField3.newBuilder().setSomeField(false).build());
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // mutate Any field
+ 0,
+ // keep non-null message field
+ false,
+ // switch Any state
+ true,
+ // new Any state
+ 1,
+ // non-null message
+ false,
+ // boolean field,
+ true)) {
+ mutator.mutateInPlace(builder, prng);
+ }
+ assertThat(builder.build().getSomeField().unpack(MessageField3.class))
+ .isEqualTo(MessageField3.newBuilder()
+ .setMessageField(PrimitiveField3.newBuilder().setSomeField(true))
+ .build());
+ }
+
+ @Test
+ void testAnyField3WithoutAnySourceDoesNotCrash() throws InvalidProtocolBufferException {
+ InPlaceMutator<AnyField3.Builder> mutator =
+ (InPlaceMutator<AnyField3.Builder>) FACTORY.createInPlaceOrThrow(
+ new TypeHolder<@NotNull Builder>() {}.annotatedType());
+ assertThat(mutator.toString())
+ .isEqualTo("{Builder.Nullable<{Builder.String, Builder.byte[] -> ByteString} -> Message>}");
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java
new file mode 100644
index 00000000..b804c7fb
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.mutator.proto;
+
+import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.mutator.collection.CollectionMutators;
+import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators;
+import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.TypeHolder;
+import com.code_intelligence.jazzer.protobuf.Proto2.ExtendedMessage2;
+import com.code_intelligence.jazzer.protobuf.Proto2.ExtendedSubmessage2;
+import com.code_intelligence.jazzer.protobuf.Proto2.OriginalMessage2;
+import com.code_intelligence.jazzer.protobuf.Proto2.OriginalSubmessage2;
+import com.code_intelligence.jazzer.protobuf.Proto3.PrimitiveField3;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+class MessageMutatorTest {
+ private static final MutatorFactory FACTORY = new ChainedMutatorFactory(
+ LangMutators.newFactory(), CollectionMutators.newFactory(), ProtoMutators.newFactory());
+
+ @Test
+ void testSimpleMessage() {
+ SerializingMutator<PrimitiveField3> mutator = FACTORY.createOrThrow(PrimitiveField3.class);
+
+ PrimitiveField3 msg;
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // not null
+ false,
+ // boolean
+ false)) {
+ msg = mutator.init(prng);
+ assertThat(msg).isEqualTo(PrimitiveField3.getDefaultInstance());
+ }
+
+ try (MockPseudoRandom prng = mockPseudoRandom(
+ // not null,
+ false,
+ // mutate first field
+ 0)) {
+ msg = mutator.mutate(msg, prng);
+ assertThat(msg).isNotEqualTo(PrimitiveField3.getDefaultInstance());
+ }
+ }
+
+ @Test
+ void testIncompleteMessageWithRequiredFields() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OriginalMessage2.newBuilder()
+ .setMessageField(OriginalSubmessage2.newBuilder().setNumericField(42).build())
+ .setBoolField(true)
+ .build()
+ .writeTo(out);
+ byte[] bytes = out.toByteArray();
+
+ SerializingMutator<ExtendedMessage2> mutator =
+ (SerializingMutator<ExtendedMessage2>) FACTORY.createOrThrow(
+ new TypeHolder<@NotNull ExtendedMessage2>() {}.annotatedType());
+ ExtendedMessage2 extendedMessage = mutator.readExclusive(new ByteArrayInputStream(bytes));
+ assertThat(extendedMessage)
+ .isEqualTo(ExtendedMessage2.newBuilder()
+ .setMessageField(
+ ExtendedSubmessage2.newBuilder().setNumericField(42).setMessageField(
+ OriginalSubmessage2.newBuilder().setNumericField(0).build()))
+ .setBoolField(true)
+ .setFloatField(0)
+ .build());
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto
new file mode 100644
index 00000000..ea7c9999
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto
@@ -0,0 +1,161 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package com.code_intelligence.jazzer.protobuf;
+option java_package = "com.code_intelligence.jazzer.protobuf";
+
+message PrimitiveField2 {
+optional bool some_field = 1;
+}
+
+message RequiredPrimitiveField2 {
+required bool some_field = 1;
+}
+
+message RepeatedPrimitiveField2 {
+repeated bool some_field = 1;
+}
+
+message MessageField2 {
+optional RequiredPrimitiveField2 message_field = 1;
+}
+
+message RepeatedMessageField2 {
+repeated RequiredPrimitiveField2 message_field = 1;
+}
+
+message RepeatedOptionalMessageField2 {
+repeated PrimitiveField2 message_field = 1;
+}
+
+message RecursiveMessageField2 {
+required bool some_field = 1;
+optional RecursiveMessageField2 message_field = 2;
+}
+
+message RepeatedRecursiveMessageField2 {
+optional bool some_field = 1;
+repeated RepeatedRecursiveMessageField2 message_field = 2;
+}
+
+message OneOfField2 {
+required bool other_field = 4;
+oneof oneof_field {
+ bool bool_field = 7;
+ RequiredPrimitiveField2 message_field = 2;
+}
+optional bool yet_another_field = 1;
+}
+
+message IntegralField2 {
+optional uint32 some_field = 1;
+}
+
+message RepeatedIntegralField2 {
+repeated uint32 some_field = 1;
+}
+
+message BytesField2 {
+optional bytes some_field = 1;
+}
+
+message StringField2 {
+optional string some_field = 1;
+}
+
+message Parent {
+ optional Child child = 1;
+}
+
+message Child {
+ optional Parent parent = 1;
+}
+
+// Taken from
+// https://github.com/google/fuzztest/blob/c5fde4baee6134c84d4f2b618def9f60c7505151/fuzztest/internal/test_protobuf.proto#L24
+message TestSubProtobuf {
+ optional int32 subproto_i32 = 1;
+ repeated int32 subproto_rep_i32 = 2 [packed = true];
+ optional TestProtobuf parent = 3;
+}
+
+message TestProtobuf {
+ enum Enum {
+ Label1 = 0;
+ Label2 = 1;
+ Label3 = 2;
+ Label4 = 3;
+ Label5 = 4;
+ }
+
+ optional bool b = 1;
+ optional int32 i32 = 2;
+ optional uint32 u32 = 3;
+ optional int64 i64 = 4;
+ optional uint64 u64 = 5;
+ optional float f = 6;
+ optional double d = 7;
+ optional string str = 8;
+ optional Enum e = 9;
+ optional TestSubProtobuf subproto = 10;
+
+ repeated bool rep_b = 11;
+ repeated int32 rep_i32 = 12;
+ repeated uint32 rep_u32 = 13;
+ repeated int64 rep_i64 = 14;
+ repeated uint64 rep_u64 = 15;
+ repeated float rep_f = 16;
+ repeated double rep_d = 17;
+ repeated string rep_str = 18;
+ repeated Enum rep_e = 19;
+ repeated TestSubProtobuf rep_subproto = 20;
+
+ oneof oneof_field {
+ int32 oneof_i32 = 21;
+ int64 oneof_i64 = 22;
+ uint32 oneof_u32 = 24;
+ }
+
+ map<int32, int32> map_field = 25;
+
+ // Special cases
+ enum EnumOneLabel {
+ OnlyLabel = 17;
+ }
+ optional EnumOneLabel enum_one_label = 100;
+ message EmptyMessage {}
+ optional EmptyMessage empty_message = 101;
+}
+
+message OriginalSubmessage2 {
+ required int32 numeric_field = 1;
+}
+
+message OriginalMessage2 {
+ required OriginalSubmessage2 message_field = 1;
+ required bool bool_field = 2;
+}
+
+message ExtendedSubmessage2 {
+ required int32 numeric_field = 1;
+ required OriginalSubmessage2 message_field = 2;
+}
+
+message ExtendedMessage2 {
+ required ExtendedSubmessage2 message_field = 1;
+ required bool bool_field = 2;
+ required float float_field = 3;
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto
new file mode 100644
index 00000000..7bd6ffeb
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto
@@ -0,0 +1,144 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+import "google/protobuf/any.proto";
+
+option java_package = "com.code_intelligence.jazzer.protobuf";
+
+message PrimitiveField3 {
+ bool some_field = 1;
+}
+
+message OptionalPrimitiveField3 {
+ optional bool some_field = 1;
+}
+
+message RepeatedPrimitiveField3 {
+ repeated bool some_field = 1;
+}
+
+message MessageField3 {
+ PrimitiveField3 message_field = 1;
+}
+
+message RepeatedMessageField3 {
+ repeated PrimitiveField3 message_field = 1;
+}
+
+message RecursiveMessageField3 {
+ bool some_field = 1;
+ RecursiveMessageField3 message_field = 2;
+}
+
+message RepeatedRecursiveMessageField3 {
+ bool some_field = 1;
+ repeated RepeatedRecursiveMessageField3 message_field = 2;
+}
+
+message OneOfField3 {
+ bool other_field = 4;
+ oneof oneof_field {
+ bool bool_field = 7;
+ PrimitiveField3 message_field = 2;
+ }
+ bool yet_another_field = 1;
+}
+
+message IntegralField3 {
+ uint32 some_field = 1;
+}
+
+message RepeatedIntegralField3 {
+ repeated uint32 some_field = 1;
+}
+
+message BytesField3 {
+ bytes some_field = 1;
+}
+
+message StringField3 {
+ string some_field = 1;
+}
+
+message EnumField3 {
+ enum TestEnum {
+ VAL1 = 0;
+ VAL2 = 1;
+ }
+ TestEnum some_field = 1;
+}
+
+enum TestEnumOutside3 {
+ VAL1 = 0;
+ VAL2 = 1;
+ VAL3 = 3;
+}
+
+message EnumFieldOutside3 {
+ TestEnumOutside3 some_field = 1;
+}
+
+message EnumFieldOne3 {
+ enum TestEnumOne {
+ ONE = 0;
+ }
+ TestEnumOne some_field = 1;
+}
+
+message EnumFieldRepeated3 {
+ enum TestEnumRepeated {
+ UNASSIGNED = 0;
+ VAL1 = 1;
+ VAL2 = 2;
+ }
+ repeated TestEnumRepeated some_field = 1;
+}
+
+message MapField3 {
+ map<int32, string> some_field = 1;
+}
+
+message MessageMapField3 {
+ map<string, MapField3> some_field = 1;
+}
+
+message FloatField3 {
+ float some_field = 1;
+}
+
+message RepeatedFloatField3 {
+ repeated float some_field = 1;
+}
+
+message DoubleField3 {
+ double some_field = 1;
+}
+
+message RepeatedDoubleField3 {
+ repeated double some_field = 1;
+}
+
+message EmptyMessage3 {}
+
+message AnyField3 {
+ google.protobuf.Any some_field = 1;
+}
+
+message SingleOptionOneOfField3 {
+ oneof oneof_field {
+ bool bool_field = 1;
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/support/BUILD.bazel
new file mode 100644
index 00000000..bcde8ba9
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/BUILD.bazel
@@ -0,0 +1,35 @@
+load("@contrib_rules_jvm//java:defs.bzl", "JUNIT5_DEPS", "java_test_suite")
+
+java_library(
+ name = "test_support",
+ testonly = True,
+ srcs = ["TestSupport.java"],
+ visibility = ["//src/test/java/com/code_intelligence/jazzer/mutation:__subpackages__"],
+ exports = JUNIT5_DEPS + [
+ # keep sorted
+ "@maven//:com_google_truth_extensions_truth_java8_extension",
+ "@maven//:com_google_truth_extensions_truth_proto_extension",
+ "@maven//:com_google_truth_truth",
+ "@maven//:org_junit_jupiter_junit_jupiter_api",
+ "@maven//:org_junit_jupiter_junit_jupiter_params",
+ ],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/api",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/engine",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "@com_google_errorprone_error_prone_annotations//jar",
+ "@maven//:com_google_truth_truth",
+ ],
+)
+
+java_test_suite(
+ name = "SupportTests",
+ size = "small",
+ srcs = glob(["*Test.java"]),
+ runner = "junit5",
+ deps = [
+ ":test_support",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ ],
+)
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/ExceptionSupportTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/ExceptionSupportTest.java
new file mode 100644
index 00000000..630b7cdf
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/ExceptionSupportTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.support;
+
+import static com.code_intelligence.jazzer.mutation.support.ExceptionSupport.asUnchecked;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+class ExceptionSupportTest {
+ @Test
+ void testAsUnchecked_withUncheckedException() {
+ assertThrows(IllegalStateException.class, () -> {
+ // noinspection TrivialFunctionalExpressionUsage
+ ((Runnable) () -> { throw asUnchecked(new IllegalStateException()); }).run();
+ });
+ }
+
+ @Test
+ void testAsUnchecked_withCheckedException() {
+ assertThrows(IOException.class, () -> {
+ // Verify that asUnchecked can be used to throw a checked exception in a function that doesn't
+ // declare it as being thrown.
+ // noinspection TrivialFunctionalExpressionUsage
+ ((Runnable) () -> { throw asUnchecked(new IOException()); }).run();
+ });
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/HolderTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/HolderTest.java
new file mode 100644
index 00000000..97450e57
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/HolderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.support;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class HolderTest {
+ @Test
+ void testTypeHolder_rawType() {
+ Type type = new TypeHolder<List<String>>() {}.type();
+ assertThat(type).isInstanceOf(ParameterizedType.class);
+
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ assertThat(parameterizedType.getRawType()).isEqualTo(List.class);
+ assertThat(parameterizedType.getActualTypeArguments()).asList().containsExactly(String.class);
+ }
+
+ @Test
+ void testTypeHolder_annotatedType() {
+ AnnotatedType type = new TypeHolder<@Foo List<@Bar String>>() {}.annotatedType();
+ assertThat(type).isInstanceOf(AnnotatedParameterizedType.class);
+
+ AnnotatedParameterizedType listType = (AnnotatedParameterizedType) type;
+ assertThat(listType.getType()).isInstanceOf(ParameterizedType.class);
+ assertThat(((ParameterizedType) listType.getType()).getRawType()).isEqualTo(List.class);
+ assertThat(listType.getAnnotations()).hasLength(1);
+ assertThat(listType.getAnnotations()[0]).isInstanceOf(Foo.class);
+ assertThat(listType.getAnnotatedActualTypeArguments()).hasLength(1);
+
+ AnnotatedType stringType = listType.getAnnotatedActualTypeArguments()[0];
+ assertThat(stringType.getType()).isEqualTo(String.class);
+ assertThat(stringType.getAnnotations()).hasLength(1);
+ assertThat(stringType.getAnnotations()[0]).isInstanceOf(Bar.class);
+ }
+
+ @Test
+ void testParameterHolder_rawType() {
+ Type type = new ParameterHolder() {
+ void foo(List<String> parameter) {}
+ }.type();
+ assertThat(type).isInstanceOf(ParameterizedType.class);
+
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ assertThat(parameterizedType.getRawType()).isEqualTo(List.class);
+ assertThat(parameterizedType.getActualTypeArguments()).asList().containsExactly(String.class);
+ }
+
+ @Test
+ void testParameterHolder_annotatedType() {
+ AnnotatedType type = new ParameterHolder() {
+ void foo(@ParameterAnnotation @Foo List<@Bar String> parameter) {}
+ }.annotatedType();
+ assertThat(type).isInstanceOf(AnnotatedParameterizedType.class);
+
+ AnnotatedParameterizedType listType = (AnnotatedParameterizedType) type;
+ assertThat(listType.getType()).isInstanceOf(ParameterizedType.class);
+ assertThat(((ParameterizedType) listType.getType()).getRawType()).isEqualTo(List.class);
+ assertThat(listType.getAnnotations()).hasLength(1);
+ assertThat(listType.getAnnotations()[0]).isInstanceOf(Foo.class);
+ assertThat(listType.getAnnotatedActualTypeArguments()).hasLength(1);
+
+ AnnotatedType stringType = listType.getAnnotatedActualTypeArguments()[0];
+ assertThat(stringType.getType()).isEqualTo(String.class);
+ assertThat(stringType.getAnnotations()).hasLength(1);
+ assertThat(stringType.getAnnotations()[0]).isInstanceOf(Bar.class);
+ }
+
+ @Test
+ void testParameterHolder_parameterAnnotations() {
+ Annotation[] annotations = new ParameterHolder() {
+ void foo(@ParameterAnnotation @Foo List<@Bar String> parameter) {}
+ }.parameterAnnotations();
+ assertThat(annotations).hasLength(1);
+ assertThat(annotations[0]).isInstanceOf(ParameterAnnotation.class);
+ }
+
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface Foo {}
+
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface Bar {}
+
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface ParameterAnnotation {}
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/InputStreamSupportTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/InputStreamSupportTest.java
new file mode 100644
index 00000000..29963f4f
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/InputStreamSupportTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.support;
+
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.cap;
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithReadExactly;
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithZeros;
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.infiniteZeros;
+import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.readAllBytes;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.code_intelligence.jazzer.mutation.support.InputStreamSupport.ReadExactlyInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class InputStreamSupportTest {
+ @Test
+ void testInfiniteZeros() throws IOException {
+ InputStream input = infiniteZeros();
+
+ assertThat(input.available()).isEqualTo(Integer.MAX_VALUE);
+ assertThat(input.read()).isEqualTo(0);
+
+ input.close();
+
+ assertThat(input.available()).isEqualTo(Integer.MAX_VALUE);
+ assertThat(input.read()).isEqualTo(0);
+ }
+
+ @Test
+ void testExtendWithNullInputStream_empty() throws IOException {
+ InputStream input = extendWithZeros(new ByteArrayInputStream(new byte[0]));
+ assertThat(input.skip(5)).isEqualTo(5);
+ assertThat(input.read()).isEqualTo(0);
+ byte[] bytes = new byte[] {9, 9, 9, 9, 9};
+ assertThat(input.read(bytes)).isEqualTo(5);
+ assertThat(bytes).asList().containsExactly((byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0);
+ }
+
+ @Test
+ void testExtendWithNullInputStream_emptyAfterRead() throws IOException {
+ InputStream input = extendWithZeros(new ByteArrayInputStream(new byte[] {1}));
+ assertThat(input.read()).isEqualTo(1);
+ assertThat(input.read()).isEqualTo(0);
+ assertThat(input.read()).isEqualTo(0);
+ byte[] bytes = new byte[] {9, 9, 9, 9, 9};
+ assertThat(input.read(bytes)).isEqualTo(5);
+ assertThat(bytes).asList().containsExactly((byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0);
+ }
+
+ @Test
+ void testExtendWithNullInputStream_emptyWithinRead() throws IOException {
+ InputStream input = extendWithZeros(new ByteArrayInputStream(new byte[] {1, 2, 3}));
+ byte[] bytes = new byte[] {9, 9, 9, 9, 9};
+ assertThat(input.read(bytes)).isEqualTo(5);
+ assertThat(bytes).asList().containsExactly((byte) 1, (byte) 2, (byte) 3, (byte) 0, (byte) 0);
+ }
+
+ @Test
+ void testExtendWithNullInputStream_emptyWithinSkip() throws IOException {
+ InputStream input = extendWithZeros(new ByteArrayInputStream(new byte[] {1, 2, 3}));
+ assertThat(input.skip(5)).isEqualTo(5);
+ byte[] bytes = new byte[] {9, 9, 9, 9, 9};
+ assertThat(input.read(bytes)).isEqualTo(5);
+ assertThat(bytes).asList().containsExactly((byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0);
+ }
+
+ @Test
+ void testCap_reachedAfterRead() throws IOException {
+ InputStream input = cap(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}), 3);
+ assertThat(input.available()).isEqualTo(3);
+ assertThat(input.read()).isEqualTo(1);
+ assertThat(input.available()).isEqualTo(2);
+ assertThat(input.read()).isEqualTo(2);
+ assertThat(input.available()).isEqualTo(1);
+ assertThat(input.read()).isEqualTo(3);
+ assertThat(input.available()).isEqualTo(0);
+ assertThat(input.read()).isEqualTo(-1);
+ assertThat(input.read(new byte[5], 0, 5)).isEqualTo(-1);
+ }
+
+ @Test
+ void testCap_reachedWithinRead() throws IOException {
+ InputStream input = cap(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}), 3);
+ byte[] bytes = new byte[5];
+ assertThat(input.available()).isEqualTo(3);
+ assertThat(input.read(bytes, 0, 5)).isEqualTo(3);
+ assertThat(bytes).asList().containsExactly((byte) 1, (byte) 2, (byte) 3, (byte) 0, (byte) 0);
+ }
+
+ @ParameterizedTest
+ // 8192 is the internal buffer size.
+ @ValueSource(ints = {0, 1, 3, 500, 8192, 8192 + 17, 8192 * 8192 + 17})
+ void testReadAllBytes(int length) throws IOException {
+ byte[] bytes = new byte[length];
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) i;
+ }
+ InputStream input = new ByteArrayInputStream(bytes);
+
+ assertThat(readAllBytes(input)).isEqualTo(bytes);
+ }
+
+ @Test
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ void testReadExactly() throws IOException {
+ ReadExactlyInputStream ce = extendWithReadExactly(new ByteArrayInputStream(new byte[] {0, 1}));
+ assertThat(ce.isConsumedExactly()).isFalse();
+ ce.read();
+ assertThat(ce.isConsumedExactly()).isFalse();
+ ce.read();
+ assertThat(ce.isConsumedExactly()).isTrue();
+ ce.read();
+ assertThat(ce.isConsumedExactly()).isFalse();
+ }
+
+ @Test
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ void testReadExactly_readBytes() throws IOException {
+ ReadExactlyInputStream ce =
+ extendWithReadExactly(new ByteArrayInputStream(new byte[] {0, 1, 2}));
+ assertThat(ce.isConsumedExactly()).isFalse();
+ ce.read(new byte[3]);
+ assertThat(ce.isConsumedExactly()).isTrue();
+ ce.read(new byte[1]);
+ assertThat(ce.isConsumedExactly()).isFalse();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
new file mode 100644
index 00000000..8035ef86
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.support;
+
+import static com.code_intelligence.jazzer.mutation.support.Preconditions.requireNonNullElements;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toCollection;
+
+import com.code_intelligence.jazzer.mutation.api.Debuggable;
+import com.code_intelligence.jazzer.mutation.api.InPlaceMutator;
+import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
+import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.engine.SeededPseudoRandom;
+import com.google.errorprone.annotations.CheckReturnValue;
+import com.google.errorprone.annotations.MustBeClosed;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Queue;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+
+public final class TestSupport {
+ private static final DataOutputStream nullDataOutputStream =
+ new DataOutputStream(new OutputStream() {
+ @Override
+ public void write(int i) {}
+ });
+
+ private TestSupport() {}
+
+ public static DataOutputStream nullDataOutputStream() {
+ return nullDataOutputStream;
+ }
+
+ /**
+ * Deterministically creates a new instance of {@link PseudoRandom} whose exact behavior is
+ * intentionally unspecified.
+ */
+ // TODO: Turn usages of this function into fuzz tests.
+ public static PseudoRandom anyPseudoRandom() {
+ // Change this seed from time to time to shake out tests relying on hardcoded behavior.
+ return new SeededPseudoRandom(8853461259049838337L);
+ }
+
+ /**
+ * Creates a {@link PseudoRandom} whose methods return the given values in order.
+ */
+ @MustBeClosed
+ public static MockPseudoRandom mockPseudoRandom(Object... returnValues) {
+ return new MockPseudoRandom(returnValues);
+ }
+
+ @CheckReturnValue
+ public static <T> SerializingMutator<T> mockMutator(T initialValue, UnaryOperator<T> mutate) {
+ return mockMutator(initialValue, mutate, value -> value);
+ }
+
+ @CheckReturnValue
+ public static <T> SerializingMutator<T> mockMutator(
+ T initialValue, UnaryOperator<T> mutate, UnaryOperator<T> detach) {
+ return new AbstractMockMutator<T>() {
+ @Override
+ protected T nextInitialValue() {
+ return initialValue;
+ }
+
+ @Override
+ public T mutate(T value, PseudoRandom prng) {
+ return mutate.apply(value);
+ }
+
+ @Override
+ public T detach(T value) {
+ return detach.apply(value);
+ }
+ };
+ }
+
+ @CheckReturnValue
+ public static <T> SerializingMutator<T> mockInitializer(
+ Supplier<T> getInitialValues, UnaryOperator<T> detach) {
+ return new AbstractMockMutator<T>() {
+ @Override
+ protected T nextInitialValue() {
+ return getInitialValues.get();
+ }
+
+ @Override
+ public T mutate(T value, PseudoRandom prng) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T detach(T value) {
+ return detach.apply(value);
+ }
+ };
+ }
+
+ @CheckReturnValue
+ public static <T> SerializingMutator<T> mockCrossOver(BiFunction<T, T, T> getCrossOverValue) {
+ return new AbstractMockMutator<T>() {
+ @Override
+ protected T nextInitialValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T mutate(T value, PseudoRandom prng) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T crossOver(T value, T otherValue, PseudoRandom prng) {
+ return getCrossOverValue.apply(value, otherValue);
+ }
+
+ @Override
+ public T detach(T value) {
+ return value;
+ }
+ };
+ }
+
+ @CheckReturnValue
+ public static <T> InPlaceMutator<T> mockCrossOverInPlace(BiConsumer<T, T> crossOverInPlace) {
+ return new AbstractMockInPlaceMutator<T>() {
+ @Override
+ public void crossOverInPlace(T reference, T otherReference, PseudoRandom prng) {
+ crossOverInPlace.accept(reference, otherReference);
+ }
+
+ @Override
+ public String toDebugString(Predicate<Debuggable> isInCycle) {
+ return "CrossOverInPlaceMockMutator";
+ }
+ };
+ }
+
+ @CheckReturnValue
+ public static <T> InPlaceMutator<T> mockInitInPlace(Consumer<T> setInitialValues) {
+ return new AbstractMockInPlaceMutator<T>() {
+ @Override
+ public void initInPlace(T reference, PseudoRandom prng) {
+ setInitialValues.accept(reference);
+ }
+
+ @Override
+ public String toDebugString(Predicate<Debuggable> isInCycle) {
+ return "InitInPlaceMockMutator";
+ }
+ };
+ }
+
+ private static abstract class AbstractMockInPlaceMutator<T> implements InPlaceMutator<T> {
+ @Override
+ public void initInPlace(T reference, PseudoRandom prng) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mutateInPlace(T reference, PseudoRandom prng) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void crossOverInPlace(T reference, T otherReference, PseudoRandom prng) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static abstract class AbstractMockMutator<T> extends SerializingMutator<T> {
+ abstract protected T nextInitialValue();
+
+ @Override
+ public T read(DataInputStream in) {
+ return nextInitialValue();
+ }
+
+ @Override
+ public void write(T value, DataOutputStream out) {
+ throw new UnsupportedOperationException("mockMutator does not support write");
+ }
+
+ @Override
+ public T init(PseudoRandom prng) {
+ return nextInitialValue();
+ }
+
+ @Override
+ public T crossOver(T value, T otherValue, PseudoRandom prng) {
+ return value;
+ }
+
+ @Override
+ public String toDebugString(Predicate<Debuggable> isInCycle) {
+ T initialValue = nextInitialValue();
+ if (initialValue == null) {
+ return "null";
+ }
+ return initialValue.getClass().getSimpleName();
+ }
+
+ @Override
+ public T detach(T value) {
+ return value;
+ }
+ }
+
+ public static final class MockPseudoRandom implements PseudoRandom, AutoCloseable {
+ private final Queue<Object> elements;
+
+ private MockPseudoRandom(Object... objects) {
+ requireNonNullElements(objects);
+ this.elements = stream(objects).collect(toCollection(ArrayDeque::new));
+ }
+
+ @Override
+ public boolean choice() {
+ assertThat(elements).isNotEmpty();
+ return (boolean) elements.poll();
+ }
+
+ @Override
+ public boolean trueInOneOutOf(int inverseFrequencyTrue) {
+ assertThat(inverseFrequencyTrue).isAtLeast(2);
+
+ assertThat(elements).isNotEmpty();
+ return (boolean) elements.poll();
+ }
+
+ @Override
+ public <T> T pickIn(T[] array) {
+ assertThat(array).isNotEmpty();
+
+ assertThat(elements).isNotEmpty();
+ return array[(int) elements.poll()];
+ }
+
+ @Override
+ public <T> T pickIn(List<T> list) {
+ assertThat(list).isNotEmpty();
+
+ assertThat(elements).isNotEmpty();
+ return list.get((int) elements.poll());
+ }
+
+ @Override
+ public <T> int indexIn(T[] array) {
+ assertThat(array).isNotEmpty();
+
+ assertThat(elements).isNotEmpty();
+ return (int) elements.poll();
+ }
+
+ @Override
+ public <T> int indexIn(List<T> list) {
+ assertThat(list).isNotEmpty();
+
+ assertThat(elements).isNotEmpty();
+ return (int) elements.poll();
+ }
+
+ @Override
+ public int indexIn(int range) {
+ assertThat(range).isAtLeast(1);
+
+ assertThat(elements).isNotEmpty();
+ return (int) elements.poll();
+ }
+
+ @Override
+ public <T> int otherIndexIn(T[] array, int currentIndex) {
+ return otherIndexIn(array.length, currentIndex);
+ }
+
+ @Override
+ public int otherIndexIn(int range, int currentValue) {
+ assertThat(range).isAtLeast(2);
+ assertThat(elements).isNotEmpty();
+ int result = (int) elements.poll();
+ assertThat(result).isAtLeast(0);
+ assertThat(result).isAtMost(range - 1);
+ assertThat(result).isNotEqualTo(currentValue);
+ return result;
+ }
+
+ @Override
+ public int closedRange(int lowerInclusive, int upperInclusive) {
+ assertThat(lowerInclusive).isAtMost(upperInclusive);
+
+ assertThat(elements).isNotEmpty();
+ int result = (int) elements.poll();
+ assertThat(result).isAtLeast(lowerInclusive);
+ assertThat(result).isAtMost(upperInclusive);
+ return result;
+ }
+
+ @Override
+ public long closedRange(long lowerInclusive, long upperInclusive) {
+ assertThat(lowerInclusive).isAtMost(upperInclusive);
+
+ assertThat(elements).isNotEmpty();
+ long result = (long) elements.poll();
+ assertThat(result).isAtLeast(lowerInclusive);
+ assertThat(result).isAtMost(upperInclusive);
+ return result;
+ }
+
+ @Override
+ public float closedRange(float lowerInclusive, float upperInclusive) {
+ assertThat(lowerInclusive).isLessThan(upperInclusive);
+ assertThat(elements).isNotEmpty();
+ float result = (float) elements.poll();
+ assertThat(result).isAtLeast(lowerInclusive);
+ assertThat(result).isAtMost(upperInclusive);
+ return result;
+ }
+
+ @Override
+ public double closedRange(double lowerInclusive, double upperInclusive) {
+ assertThat(lowerInclusive).isLessThan(upperInclusive);
+ assertThat(elements).isNotEmpty();
+ double result = (double) elements.poll();
+ assertThat(result).isAtLeast(lowerInclusive);
+ assertThat(result).isAtMost(upperInclusive);
+ return result;
+ }
+
+ @Override
+ public int closedRangeBiasedTowardsSmall(int upperInclusive) {
+ assertThat(upperInclusive).isAtLeast(0);
+
+ assertThat(elements).isNotEmpty();
+ int result = (int) elements.poll();
+ assertThat(result).isAtLeast(0);
+ assertThat(result).isAtMost(upperInclusive);
+ return result;
+ }
+
+ @Override
+ public int closedRangeBiasedTowardsSmall(int lowerInclusive, int upperInclusive) {
+ assertThat(lowerInclusive).isAtMost(upperInclusive);
+
+ assertThat(elements).isNotEmpty();
+ int result = (int) elements.poll();
+ assertThat(result).isAtLeast(lowerInclusive);
+ assertThat(result).isAtMost(upperInclusive);
+ return result;
+ }
+
+ @Override
+ public void bytes(byte[] bytes) {
+ assertThat(elements).isNotEmpty();
+ byte[] result = (byte[]) elements.poll();
+ assertThat(result).hasLength(bytes.length);
+ System.arraycopy(result, 0, bytes, 0, bytes.length);
+ }
+
+ @Override
+ public <T> T pickValue(
+ T value, T otherValue, Supplier<T> supplier, int inverseSupplierFrequency) {
+ assertThat(elements).isNotEmpty();
+ switch ((int) elements.poll()) {
+ case 0:
+ return value;
+ case 1:
+ return otherValue;
+ case 2:
+ return supplier.get();
+ default:
+ throw new AssertionError("Invalid pickValue element");
+ }
+ }
+
+ @Override
+ public long nextLong() {
+ assertThat(elements).isNotEmpty();
+ return (long) elements.poll();
+ }
+
+ @Override
+ public void close() {
+ assertThat(elements).isEmpty();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <K, V> LinkedHashMap<K, V> asMap(Object... objs) {
+ LinkedHashMap<K, V> map = new LinkedHashMap<>();
+ for (int i = 0; i < objs.length; i += 2) {
+ map.put((K) objs[i], (V) objs[i + 1]);
+ }
+ return map;
+ }
+
+ @SafeVarargs
+ public static <T> ArrayList<T> asMutableList(T... objs) {
+ return stream(objs).collect(toCollection(ArrayList::new));
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java
new file mode 100644
index 00000000..bbf4a7e6
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.support;
+
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asAnnotatedType;
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asSubclassOrEmpty;
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.containedInDirectedCycle;
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.visitAnnotatedType;
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withTypeArguments;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static java.util.Arrays.stream;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.code_intelligence.jazzer.mutation.annotation.NotNull;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.ParameterizedType;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
+
+class TypeSupportTest {
+ @Test
+ void testFillTypeVariablesRawType_oneVariable() {
+ AnnotatedParameterizedType actual =
+ withTypeArguments(new TypeHolder<@NotNull List>() {}.annotatedType(),
+ new TypeHolder<@NotNull String>() {}.annotatedType());
+ AnnotatedParameterizedType expected =
+ (AnnotatedParameterizedType) new TypeHolder<@NotNull List<@NotNull String>>() {
+ }.annotatedType();
+
+ // Test both equals implementations as we implement them ourselves.
+ assertThat(actual.getType()).isEqualTo(expected.getType());
+ assertThat(expected.getType()).isEqualTo(actual.getType());
+
+ assertThat(actual.getAnnotations()).isEqualTo(expected.getAnnotations());
+ assertThat(expected.getAnnotations()).isEqualTo(actual.getAnnotations());
+
+ assertThat(((ParameterizedType) actual.getType()).getActualTypeArguments())
+ .isEqualTo(((ParameterizedType) expected.getType()).getActualTypeArguments());
+ assertThat(((ParameterizedType) expected.getType()).getActualTypeArguments())
+ .isEqualTo(((ParameterizedType) actual.getType()).getActualTypeArguments());
+ }
+
+ @Test
+ // Java <= 11 does not implement AnnotatedType#equals.
+ // https://github.com/openjdk/jdk/commit/ab0128ca51de59aaaa674654ca8d4e16b3b79965
+ @EnabledForJreRange(min = JRE.JAVA_12)
+ void testFillTypeVariablesAnnotatedType_oneVariable() {
+ AnnotatedParameterizedType actual =
+ withTypeArguments(new TypeHolder<@NotNull List>() {}.annotatedType(),
+ new TypeHolder<@NotNull String>() {}.annotatedType());
+ AnnotatedParameterizedType expected =
+ (AnnotatedParameterizedType) new TypeHolder<@NotNull List<@NotNull String>>() {
+ }.annotatedType();
+
+ // Test both equals implementations as we implement them ourselves.
+ assertThat(actual).isEqualTo(expected);
+ assertThat(expected).isEqualTo(actual);
+
+ assertThat(actual.getType()).isEqualTo(expected.getType());
+ assertThat(expected.getType()).isEqualTo(actual.getType());
+
+ assertThat(actual.getAnnotations()).isEqualTo(expected.getAnnotations());
+ assertThat(expected.getAnnotations()).isEqualTo(actual.getAnnotations());
+
+ assertThat(actual.getAnnotatedActualTypeArguments())
+ .isEqualTo(expected.getAnnotatedActualTypeArguments());
+ assertThat(expected.getAnnotatedActualTypeArguments())
+ .isEqualTo(actual.getAnnotatedActualTypeArguments());
+ }
+
+ @Test
+ void testFillTypeVariablesRawType_oneVariable_differentType() {
+ AnnotatedParameterizedType actual =
+ withTypeArguments(new TypeHolder<@NotNull List>() {}.annotatedType(),
+ new TypeHolder<@NotNull String>() {}.annotatedType());
+ AnnotatedParameterizedType differentParameterAnnotation =
+ (AnnotatedParameterizedType) new TypeHolder<@NotNull List<@NotNull Boolean>>() {
+ }.annotatedType();
+
+ // Test both equals implementations as we implement them ourselves.
+ assertThat(actual.getType()).isNotEqualTo(differentParameterAnnotation.getType());
+ assertThat(differentParameterAnnotation.getType()).isNotEqualTo(actual.getType());
+
+ assertThat(actual.getAnnotations()).isEqualTo(differentParameterAnnotation.getAnnotations());
+ assertThat(differentParameterAnnotation.getAnnotations()).isEqualTo(actual.getAnnotations());
+
+ assertThat(((ParameterizedType) actual.getType()).getActualTypeArguments())
+ .isNotEqualTo(
+ ((ParameterizedType) differentParameterAnnotation.getType()).getActualTypeArguments());
+ assertThat(
+ ((ParameterizedType) differentParameterAnnotation.getType()).getActualTypeArguments())
+ .isNotEqualTo(((ParameterizedType) actual.getType()).getActualTypeArguments());
+ }
+
+ @Test
+ // Java <= 11 does not implement AnnotatedType#equals.
+ // https://github.com/openjdk/jdk/commit/ab0128ca51de59aaaa674654ca8d4e16b3b79965
+ @EnabledForJreRange(min = JRE.JAVA_12)
+ void testFillTypeVariablesAnnotatedType_oneVariable_differentAnnotations() {
+ AnnotatedParameterizedType actual =
+ withTypeArguments(new TypeHolder<@NotNull List>() {}.annotatedType(),
+ new TypeHolder<@NotNull String>() {}.annotatedType());
+ AnnotatedParameterizedType differentParameterAnnotation =
+ (AnnotatedParameterizedType) new TypeHolder<@NotNull List<String>>() {}.annotatedType();
+
+ // Test both equals implementations as we implement them ourselves.
+ assertThat(actual).isNotEqualTo(differentParameterAnnotation);
+ assertThat(differentParameterAnnotation).isNotEqualTo(actual);
+
+ assertThat(actual.getType()).isEqualTo(differentParameterAnnotation.getType());
+ assertThat(differentParameterAnnotation.getType()).isEqualTo(actual.getType());
+
+ assertThat(actual.getAnnotations()).isEqualTo(differentParameterAnnotation.getAnnotations());
+ assertThat(differentParameterAnnotation.getAnnotations()).isEqualTo(actual.getAnnotations());
+
+ assertThat(actual.getAnnotatedActualTypeArguments())
+ .isNotEqualTo(differentParameterAnnotation.getAnnotatedActualTypeArguments());
+ assertThat(differentParameterAnnotation.getAnnotatedActualTypeArguments())
+ .isNotEqualTo(actual.getAnnotatedActualTypeArguments());
+ }
+
+ @Test
+ void testFillTypeVariablesRawType_twoVariables() {
+ AnnotatedParameterizedType actual =
+ withTypeArguments(new TypeHolder<@NotNull Map>() {}.annotatedType(),
+ new TypeHolder<@NotNull String>() {}.annotatedType(),
+ new TypeHolder<byte[]>() {}.annotatedType());
+ AnnotatedParameterizedType expected =
+ (AnnotatedParameterizedType) new TypeHolder<@NotNull Map<@NotNull String, byte[]>>() {
+ }.annotatedType();
+
+ // Test both equals implementations as we implement them ourselves.
+ assertThat(actual.getType()).isEqualTo(expected.getType());
+ assertThat(expected.getType()).isEqualTo(actual.getType());
+
+ assertThat(actual.getAnnotations()).isEqualTo(expected.getAnnotations());
+ assertThat(expected.getAnnotations()).isEqualTo(actual.getAnnotations());
+
+ assertThat(((ParameterizedType) actual.getType()).getActualTypeArguments())
+ .isEqualTo(((ParameterizedType) expected.getType()).getActualTypeArguments());
+ assertThat(((ParameterizedType) expected.getType()).getActualTypeArguments())
+ .isEqualTo(((ParameterizedType) actual.getType()).getActualTypeArguments());
+ }
+
+ @Test
+ // Java <= 11 does not implement AnnotatedType#equals.
+ // https://github.com/openjdk/jdk/commit/ab0128ca51de59aaaa674654ca8d4e16b3b79965
+ @EnabledForJreRange(min = JRE.JAVA_12)
+ void testFillTypeVariablesAnnotatedType_twoVariables() {
+ AnnotatedParameterizedType actual =
+ withTypeArguments(new TypeHolder<@NotNull Map>() {}.annotatedType(),
+ new TypeHolder<@NotNull String>() {}.annotatedType(),
+ new TypeHolder<byte[]>() {}.annotatedType());
+ AnnotatedParameterizedType expected =
+ (AnnotatedParameterizedType) new TypeHolder<@NotNull Map<@NotNull String, byte[]>>() {
+ }.annotatedType();
+
+ // Test both equals implementations as we implement them ourselves.
+ assertThat(actual).isEqualTo(expected);
+ assertThat(expected).isEqualTo(actual);
+
+ assertThat(actual.getType()).isEqualTo(expected.getType());
+ assertThat(expected.getType()).isEqualTo(actual.getType());
+
+ assertThat(actual.getAnnotations()).isEqualTo(expected.getAnnotations());
+ assertThat(expected.getAnnotations()).isEqualTo(actual.getAnnotations());
+
+ assertThat(actual.getAnnotatedActualTypeArguments())
+ .isEqualTo(expected.getAnnotatedActualTypeArguments());
+ assertThat(expected.getAnnotatedActualTypeArguments())
+ .isEqualTo(actual.getAnnotatedActualTypeArguments());
+ }
+
+ @Test
+ void testFillTypeVariables_failures() {
+ assertThrows(IllegalArgumentException.class,
+ () -> withTypeArguments(new TypeHolder<List>() {}.annotatedType()));
+ assertThrows(IllegalArgumentException.class, () -> withTypeArguments(new TypeHolder<List<?>>() {
+ }.annotatedType(), asAnnotatedType(String.class)));
+ }
+
+ @Test
+ void testAsSubclassOrEmpty() {
+ assertThat(asSubclassOrEmpty(asAnnotatedType(String.class), String.class))
+ .hasValue(String.class);
+ assertThat(asSubclassOrEmpty(asAnnotatedType(String.class), CharSequence.class))
+ .hasValue(String.class);
+ assertThat(asSubclassOrEmpty(asAnnotatedType(CharSequence.class), String.class)).isEmpty();
+ assertThat(asSubclassOrEmpty(new TypeHolder<List<String>>() {
+ }.annotatedType(), List.class)).isEmpty();
+ }
+
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface A {
+ int value();
+ }
+
+ @Test
+ void testVisitAnnotatedType() {
+ Map<Integer, Class<?>> visited = new LinkedHashMap<>();
+ AnnotatedType type = new TypeHolder<@A(
+ 1) List<@A(2) Map<@A(3) byte @A(4)[] @A(5)[], @A(6) Byte> @A(7)[] @A(8)[]>>(){}
+ .annotatedType();
+
+ visitAnnotatedType(type,
+ (clazz, annotations)
+ -> stream(annotations)
+ .map(annotation -> ((A) annotation).value())
+ .forEach(value -> visited.put(value, clazz)));
+
+ assertThat(visited)
+ .containsExactly(1, List.class, 7, Map[][].class, 8, Map[].class, 2, Map.class, 4,
+ byte[][].class, 5, byte[].class, 3, byte.class, 6, Byte.class)
+ .inOrder();
+ }
+
+ @Test
+ void testContainedInDirectedCycle() {
+ Function<Integer, Stream<Integer>> successors = integer -> {
+ switch (integer) {
+ case 1:
+ return Stream.of(2);
+ case 2:
+ return Stream.of(3);
+ case 3:
+ return Stream.of(4, 5);
+ case 4:
+ return Stream.of(2);
+ case 5:
+ return Stream.empty();
+ default:
+ throw new IllegalStateException();
+ }
+ };
+
+ assertThat(containedInDirectedCycle(1, successors)).isFalse();
+ assertThat(containedInDirectedCycle(2, successors)).isTrue();
+ assertThat(containedInDirectedCycle(3, successors)).isTrue();
+ assertThat(containedInDirectedCycle(4, successors)).isTrue();
+ assertThat(containedInDirectedCycle(5, successors)).isFalse();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/WeakIdentityHashMapTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/WeakIdentityHashMapTest.java
new file mode 100644
index 00000000..5406ef88
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/WeakIdentityHashMapTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.mutation.support;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class WeakIdentityHashMapTest {
+ private static void reachabilityFence(Object o) {
+ // Polyfill for JDK 9+ Reference.reachabilityFence:
+ // https://mail.openjdk.org/pipermail/core-libs-dev/2018-February/051312.html
+ }
+
+ @Test
+ void testWeakIdentityHashMap_hasIdentitySemantics() {
+ WeakIdentityHashMap<List<Integer>, String> map = new WeakIdentityHashMap<>();
+
+ List<Integer> list = Arrays.asList(1, 2);
+ map.put(list, "value");
+ assertThat(map.containsKey(list)).isTrue();
+
+ List<Integer> equalList = Arrays.asList(1, 2);
+ assertThat(map.containsKey(equalList)).isFalse();
+
+ reachabilityFence(list);
+ }
+
+ @Test
+ void testWeakIdentityHashMap_hasWeakSemantics() {
+ WeakIdentityHashMap<List<Integer>, String> map = new WeakIdentityHashMap<>();
+
+ List<Integer> list = Arrays.asList(1, 2);
+ map.put(list, "value");
+ assertThat(map.containsKey(list)).isTrue();
+ assertThat(map.size()).isEqualTo(1);
+ assertThat(map.isEmpty()).isFalse();
+
+ reachabilityFence(list);
+ map.collectKeysForTesting();
+
+ assertThat(map.size()).isEqualTo(0);
+ assertThat(map.isEmpty()).isTrue();
+ }
+}