aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang')
-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
8 files changed, 1579 insertions, 0 deletions
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);
+ }
+}