aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/com
diff options
context:
space:
mode:
authorPeter Samarin <peter.samarin@code-intelligence.com>2023-04-11 09:05:12 +0200
committerGitHub <noreply@github.com>2023-04-11 09:05:12 +0200
commit8c31bd8f969a45d1c51afaeba8e665a41ccdd21d (patch)
treee03d9fa7789588ed412e6990cac840c522946a91 /src/test/java/com
parent2825c5e8c8976551378fc912bdd9245fd5501e71 (diff)
downloadjazzer-api-8c31bd8f969a45d1c51afaeba8e665a41ccdd21d.tar.gz
mutation: add mutators for floats and doubles (#669)
Co-authored-by: Fabian Meumertzheim <meumertzheim@code-intelligence.com>
Diffstat (limited to 'src/test/java/com')
-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.java110
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java137
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java655
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java16
5 files changed, 918 insertions, 13 deletions
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..4ff95e1b
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandomTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+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 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();
+ }
+ }
+ }
+}
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
index fc6dab74..05fe573e 100644
--- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java
@@ -31,6 +31,8 @@ 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;
@@ -60,6 +62,10 @@ import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedIntegralField3;
import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedRecursiveMessageField3;
import com.code_intelligence.jazzer.protobuf.Proto3.StringField3;
import com.google.protobuf.Any;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
+import com.google.protobuf.Message;
+import com.google.protobuf.Message.Builder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -71,7 +77,9 @@ import java.util.HashMap;
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;
@@ -165,7 +173,44 @@ public class StressTest {
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)));
+ 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() {
@@ -237,15 +282,15 @@ public class StressTest {
"{Builder.Map<String,{Builder.Map<Integer,String>} -> Message>} -> Message",
distinctElementsRatio(0.45), distinctElementsRatio(0.45)),
arguments(new TypeHolder<@NotNull DoubleField3>() {}.annotatedType(),
- "{Builder.Double} -> Message", manyDistinctElements(), distinctElementsRatio(0.99)),
+ "{Builder.Double} -> Message", distinctElementsRatio(0.45), distinctElementsRatio(0.7)),
arguments(new TypeHolder<@NotNull RepeatedDoubleField3>() {}.annotatedType(),
- "{Builder via List<Double>} -> Message", manyDistinctElements(),
- distinctElementsRatio(0.99)),
+ "{Builder via List<Double>} -> Message", distinctElementsRatio(0.2),
+ distinctElementsRatio(0.9)),
arguments(new TypeHolder<@NotNull FloatField3>() {}.annotatedType(),
- "{Builder.Float} -> Message", manyDistinctElements(), distinctElementsRatio(0.99)),
+ "{Builder.Float} -> Message", distinctElementsRatio(0.45), distinctElementsRatio(0.7)),
arguments(new TypeHolder<@NotNull RepeatedFloatField3>() {}.annotatedType(),
- "{Builder via List<Float>} -> Message", manyDistinctElements(),
- distinctElementsRatio(0.99), emptyList()),
+ "{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>>, Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>} -> 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>>, 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()),
@@ -368,6 +413,10 @@ public class StressTest {
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 -> {
@@ -400,8 +449,11 @@ public class StressTest {
for (int i = 0; i < NUM_INITS; i++) {
Object value = mutator.init(rng);
- testReadWriteRoundtrip(mutator, value);
- testReadWriteExclusiveRoundtrip(mutator, 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.
+ Object fixedValue = fixFloatingPointsForProtos(value);
+ testReadWriteRoundtrip(mutator, fixedValue);
+ testReadWriteExclusiveRoundtrip(mutator, fixedValue);
initValues.add(mutator.detach(value));
@@ -409,13 +461,27 @@ public class StressTest {
Object detachedOldValue = mutator.detach(value);
value = mutator.mutate(value, rng);
if (!mayPerformNoopMutations) {
- assertThat(value).isNotEqualTo(detachedOldValue);
+ 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);
+ }
}
- testReadWriteRoundtrip(mutator, value);
- testReadWriteExclusiveRoundtrip(mutator, 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);
}
}
@@ -447,4 +513,49 @@ public class StressTest {
}
return map;
}
+
+ // 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) {
+ 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/lang/FloatingPointMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java
new file mode 100644
index 00000000..13346056
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java
@@ -0,0 +1,655 @@
+/*
+ * 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.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);
+ }
+ });
+ }
+ }
+
+ 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);
+ }
+ });
+ }
+ }
+}
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
index 37ace8e6..1f863d24 100644
--- a/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
@@ -128,6 +128,22 @@ public final class TestSupport {
}
@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();