diff options
author | Peter Samarin <peter.samarin@code-intelligence.com> | 2023-04-11 09:05:12 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-11 09:05:12 +0200 |
commit | 8c31bd8f969a45d1c51afaeba8e665a41ccdd21d (patch) | |
tree | e03d9fa7789588ed412e6990cac840c522946a91 /src/test/java/com | |
parent | 2825c5e8c8976551378fc912bdd9245fd5501e71 (diff) | |
download | jazzer-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')
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(); |