diff options
Diffstat (limited to 'src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang')
8 files changed, 1579 insertions, 0 deletions
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel new file mode 100644 index 00000000..05e1d720 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel @@ -0,0 +1,18 @@ +load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite") + +java_test_suite( + name = "PrimitiveTests", + size = "small", + srcs = glob(["*.java"]), + env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"}, + runner = "junit5", + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer", + "//src/main/java/com/code_intelligence/jazzer/mutation/support", + "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support", + "@com_google_protobuf//java/core", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java new file mode 100644 index 00000000..3bf55bcf --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +class BooleanMutatorTest { + @Test + void testPrimitive() { + SerializingMutator<Boolean> mutator = LangMutators.newFactory().createOrThrow(boolean.class); + assertThat(mutator.toString()).isEqualTo("Boolean"); + + boolean bool; + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + bool = mutator.init(prng); + } + assertThat(bool).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isFalse(); + } + + @Test + void testBoxed() { + SerializingMutator<Boolean> mutator = + (SerializingMutator<Boolean>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Boolean>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("Boolean"); + + Boolean bool; + try (MockPseudoRandom prng = mockPseudoRandom(false)) { + bool = mutator.init(prng); + } + assertThat(bool).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isTrue(); + } + + @Test + void testCrossOver() { + SerializingMutator<Boolean> mutator = LangMutators.newFactory().createOrThrow(boolean.class); + try (MockPseudoRandom prng = mockPseudoRandom(true, false)) { + assertThat(mutator.crossOver(true, false, prng)).isTrue(); + assertThat(mutator.crossOver(true, false, prng)).isFalse(); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java new file mode 100644 index 00000000..1592b17d --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithLength; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) +public class ByteArrayMutatorTest { + /** + * Some tests may set {@link LibFuzzerMutator#MOCK_SIZE_KEY} which can interfere with other tests + * unless cleared. + */ + @AfterEach + void cleanMockSize() { + System.clearProperty(LibFuzzerMutator.MOCK_SIZE_KEY); + } + + @Test + void testBasicFunction() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("Nullable<byte[]>"); + + byte[] arr; + try (MockPseudoRandom prng = mockPseudoRandom(false, 5, new byte[] {1, 2, 3, 4, 5})) { + arr = mutator.init(prng); + } + assertThat(arr).isEqualTo(new byte[] {1, 2, 3, 4, 5}); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "10"); + try (MockPseudoRandom prng = mockPseudoRandom(false)) { + arr = mutator.mutate(arr, prng); + } + assertThat(arr).isEqualTo(new byte[] {2, 4, 6, 8, 10, 6, 7, 8, 9, 10}); + } + + @Test + void testMaxLength() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(max = 10)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + byte[] arr; + try (MockPseudoRandom prng = mockPseudoRandom(8, new byte[] {1, 2, 3, 4, 5, 6, 7, 8})) { + arr = mutator.init(prng); + } + assertThat(arr).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "11"); + try (MockPseudoRandom prng = mockPseudoRandom()) { + // the ByteArrayMutator will limit the maximum size of the data requested from libfuzzer to + // WithLength::max so setting the mock mutator to make it bigger will cause an exception + assertThrows(ArrayIndexOutOfBoundsException.class, () -> { mutator.mutate(arr, prng); }); + } + } + + @Test + void testMaxLengthInitClamp() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(max = 5)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = mockPseudoRandom(10)) { + // init will call closedRange(min, max) and the mock prng will assert that the given value + // above is between those values which we want to fail here to show that we're properly + // clamping the range + assertThrows(AssertionError.class, () -> { mutator.init(prng); }); + } + } + + @Test + void testMinLengthInitClamp() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(min = 5)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = mockPseudoRandom(3)) { + // init will call closedrange(min, max) and the mock prng will assert that the given value + // above is between those values which we want to fail here to show that we're properly + // clamping the range + assertThrows(AssertionError.class, () -> { mutator.init(prng); }); + } + } + + @Test + void testMinLength() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(min = 5)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + byte[] arr; + try (MockPseudoRandom prng = mockPseudoRandom(10, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) { + arr = mutator.init(prng); + } + assertThat(arr).hasLength(10); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "3"); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + arr = mutator.mutate(arr, prng); + } + assertThat(arr).hasLength(5); + assertThat(arr).isEqualTo(new byte[] {2, 4, 6, 0, 0}); + } + + @Test + void testCrossOver() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + byte[] value = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + byte[] otherValue = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + + byte[] crossedOver; + try (MockPseudoRandom prng = mockPseudoRandom( + // intersect arrays + 0, + // out length + 8, + // copy 3 from first + 3, + // copy 1 from second + 1, + // copy 1 from first, + 1, + // copy 3 from second + 3)) { + crossedOver = mutator.crossOver(value, otherValue, prng); + assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 2, 10, 3, 11, 12, 13}); + } + + try (MockPseudoRandom prng = mockPseudoRandom( + // insert into action + 1, + // copy size + 3, + // from position + 5, + // to position + 2)) { + crossedOver = mutator.crossOver(value, otherValue, prng); + assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 15, 16, 17, 2, 3, 4, 5, 6, 7, 8, 9}); + } + + try (MockPseudoRandom prng = mockPseudoRandom( + // overwrite action + 2, + // to position + 3, + // copy size + 3, + // from position + 4)) { + crossedOver = mutator.crossOver(value, otherValue, prng); + assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 2, 14, 15, 16, 6, 7, 8, 9}); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java new file mode 100644 index 00000000..d2c61397 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class EnumMutatorTest { + enum TestEnumOne { A } + + enum TestEnum { A, B, C } + + @Test + void testBoxed() { + SerializingMutator<TestEnum> mutator = + (SerializingMutator<TestEnum>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull TestEnum>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("Enum<TestEnum>"); + TestEnum cl; + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + cl = mutator.init(prng); + } + assertThat(cl).isEqualTo(TestEnum.A); + + try (MockPseudoRandom prng = mockPseudoRandom(1)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.B); + + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.A); + + try (MockPseudoRandom prng = mockPseudoRandom(2)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.C); + + try (MockPseudoRandom prng = mockPseudoRandom(1)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.B); + } + + @Test + void testEnumWithOneElementShouldThrow() { + assertThrows(IllegalArgumentException.class, () -> { + LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull TestEnumOne>() {}.annotatedType()); + }, "When trying to build mutators for Enum with one value, an Exception should be thrown."); + } + + @Test + void testEnumBasedOnInvalidInput() throws IOException { + SerializingMutator<TestEnum> mutator = + (SerializingMutator<TestEnum>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull TestEnum>() {}.annotatedType()); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + DataOutputStream os = new DataOutputStream(bo); + // Valid values + os.writeInt(0); + os.writeInt(1); + os.writeInt(2); + // Too high indices wrap around + os.writeInt(3); + // Abs. value is used to calculate the index + os.writeInt(-3); + + DataInputStream is = new DataInputStream(new ByteArrayInputStream(bo.toByteArray())); + assertThat(mutator.read(is)).isEqualTo(TestEnum.A); + assertThat(mutator.read(is)).isEqualTo(TestEnum.B); + assertThat(mutator.read(is)).isEqualTo(TestEnum.C); + assertThat(mutator.read(is)).isEqualTo(TestEnum.A); + assertThat(mutator.read(is)).isEqualTo(TestEnum.A); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java new file mode 100644 index 00000000..9c03b467 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java @@ -0,0 +1,785 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.mutator.lang.FloatingPointMutatorFactory.DoubleMutator; +import static com.code_intelligence.jazzer.mutation.mutator.lang.FloatingPointMutatorFactory.FloatMutator; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange; +import com.code_intelligence.jazzer.mutation.annotation.FloatInRange; +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class FloatingPointMutatorTest { + static final Float UNUSED_FLOAT = 0.0f; + static final Double UNUSED_DOUBLE = 0.0; + + static Stream<Arguments> floatForceInRangeCases() { + float NaN1 = Float.intBitsToFloat(0x7f800001); + float NaN2 = Float.intBitsToFloat(0x7f800002); + float NaN3 = Float.intBitsToFloat(0x7f800003); + assertThat(Float.isNaN(NaN1) && Float.isNaN(NaN2) && Float.isNaN(NaN3)).isTrue(); + + return Stream.of( + // value is already in range: it should stay in range + arguments(0.0f, 0.0f, 1.0f, true), arguments(0.0f, 1.0f, 1.0f, true), + arguments(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 1.0f, true), + arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, true), + arguments(Float.NaN, 0.0f, 1.0f, true), + arguments(1e30f, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(-1e30f, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(0.0f, Float.NEGATIVE_INFINITY, Float.MAX_VALUE, true), + arguments(0.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, true), + arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(Float.MAX_VALUE, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(-Float.MAX_VALUE, Float.MAX_VALUE - 3.4e30f, Float.MAX_VALUE, false), + arguments(Float.MAX_VALUE, -100.0f, Float.MAX_VALUE, true), + arguments(0.0f, -Float.MIN_VALUE, Float.MIN_VALUE, true), + // Special values and diff/ranges outside the range + arguments(Float.NEGATIVE_INFINITY, -1.0f, 1.0f, true), + arguments(Float.POSITIVE_INFINITY, -1.0f, 1.0f, true), + arguments(Float.POSITIVE_INFINITY, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.MAX_VALUE, true), + arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, true), + arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, Float.POSITIVE_INFINITY, true), + arguments(Float.NEGATIVE_INFINITY, Float.MAX_VALUE, Float.POSITIVE_INFINITY, true), + // Values outside the range + arguments(-2e30f, -100000.0f, 100000.0f, true), + arguments(2e30f, Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, true), + arguments(-1.0f, 0.0f, 1.0f, false), arguments(5.0f, 0.0f, 1.0f, false), + arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, 100.0f, true), + // NaN not allowed + arguments(Float.NaN, 0.0f, 1.0f, false), + arguments(Float.NaN, -Float.MAX_VALUE, 1.0f, false), + arguments(Float.NaN, Float.NEGATIVE_INFINITY, 1.0f, false), + arguments(Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, false), + arguments(Float.NaN, 0f, Float.POSITIVE_INFINITY, false), + arguments(Float.NaN, 0f, Float.MAX_VALUE, false), + arguments(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE, false), + arguments(Float.NaN, -Float.MIN_VALUE, 0.0f, false), + arguments(Float.NaN, -Float.MIN_VALUE, Float.MIN_VALUE, false), + arguments(Float.NaN, 0.0f, Float.MIN_VALUE, false), + // There are many possible NaN values, test a few of them that are different from Float.NaN + // (0x7fc00000) + arguments(NaN1, 0.0f, 1.0f, false), arguments(NaN2, 0.0f, 1.0f, false), + arguments(NaN3, 0.0f, 1.0f, false)); + } + + static Stream<Arguments> doubleForceInRangeCases() { + double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L); + double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L); + double NaN3 = Double.longBitsToDouble(0x7ff0000000000003L); + double NaNdeadbeef = Double.longBitsToDouble(0x7ff00000deadbeefL); + assertThat( + Double.isNaN(NaN1) && Double.isNaN(NaN2) && Double.isNaN(NaN3) && Double.isNaN(NaNdeadbeef)) + .isTrue(); + + return Stream.of( + // value is already in range: it should stay in range + arguments(0.0, 0.0, 1.0, true), arguments(0.0, 1.0, 1.0, true), + arguments(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0, true), + arguments( + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, true), + arguments(Double.NaN, 0.0, 1.0, true), + arguments(1e30, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(-1e30, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(0.0, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, true), + arguments(0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, true), + arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(-Double.MAX_VALUE, Double.MAX_VALUE - 3.4e30, Double.MAX_VALUE, false), + arguments(Double.MAX_VALUE, -100.0, Double.MAX_VALUE, true), + arguments(0.0, -Double.MIN_VALUE, Double.MIN_VALUE, true), + // Special values and diff/ranges outside the range + arguments(Double.NEGATIVE_INFINITY, -1.0, 1.0, true), + arguments(Double.POSITIVE_INFINITY, -1.0, 1.0, true), + arguments(Double.POSITIVE_INFINITY, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, true), + arguments(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, true), + arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, Double.POSITIVE_INFINITY, true), + arguments(Double.NEGATIVE_INFINITY, Double.MAX_VALUE, Double.POSITIVE_INFINITY, true), + // Values outside the range + arguments(-2e30, -100000.0, 100000.0, true), + arguments(2e30, Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, true), + arguments(-1.0, 0.0, 1.0, false), arguments(5.0, 0.0, 1.0, false), + arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, 100.0, true), + arguments( + Math.nextDown(Double.MAX_VALUE), -Double.MAX_VALUE * 0.5, Double.MAX_VALUE * 0.5, true), + arguments(Math.nextDown(Double.MAX_VALUE), -Double.MAX_VALUE * 0.5, + Math.nextUp(Double.MAX_VALUE * 0.5), true), + // NaN not allowed + arguments(Double.NaN, 0.0, 1.0, false), + arguments(Double.NaN, -Double.MAX_VALUE, 1.0, false), + arguments(Double.NaN, Double.NEGATIVE_INFINITY, 1.0, false), + arguments(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false), + arguments(Double.NaN, 0, Double.POSITIVE_INFINITY, false), + arguments(Double.NaN, 0, Double.MAX_VALUE, false), + arguments(Double.NaN, -Double.MAX_VALUE, Double.MAX_VALUE, false), + arguments(Double.NaN, -Double.MIN_VALUE, 0.0, false), + arguments(Double.NaN, -Double.MIN_VALUE, Double.MIN_VALUE, false), + arguments(Double.NaN, 0.0, Double.MIN_VALUE, false), + // There are many possible NaN values, test a few of them that are different from Double.NaN + // (0x7ff8000000000000L) + arguments(NaN1, 0.0, 1.0, false), arguments(NaN2, 0.0, 1.0, false), + arguments(NaN3, 0.0, 1.0, false), + arguments(NaNdeadbeef, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false)); + } + + @ParameterizedTest + @MethodSource("floatForceInRangeCases") + void testFloatForceInRange(float value, float minValue, float maxValue, boolean allowNaN) { + float inRange = FloatMutator.forceInRange(value, minValue, maxValue, allowNaN); + + // inRange can become NaN only if allowNaN is true and value was NaN already + if (Float.isNaN(inRange)) { + if (allowNaN) { + assertThat(Float.isNaN(value)).isTrue(); + return; // NaN is not in range of anything + } else { + throw new AssertionError("NaN is not allowed but was returned"); + } + } + + assertThat(inRange).isAtLeast(minValue); + assertThat(inRange).isAtMost(maxValue); + if (value >= minValue && value <= maxValue) { + assertThat(inRange).isEqualTo(value); + } + } + + @ParameterizedTest + @MethodSource("doubleForceInRangeCases") + void testDoubleForceInRange(double value, double minValue, double maxValue, boolean allowNaN) { + double inRange = DoubleMutator.forceInRange(value, minValue, maxValue, allowNaN); + + // inRange can become NaN only if allowNaN is true and value was NaN already + if (Double.isNaN(inRange)) { + if (allowNaN) { + assertThat(Double.isNaN(value)).isTrue(); + return; // NaN is not in range of anything + } else { + throw new AssertionError("NaN is not allowed but was returned"); + } + } + + assertThat(inRange).isAtLeast(minValue); + assertThat(inRange).isAtMost(maxValue); + if (value >= minValue && value <= maxValue) { + assertThat(inRange).isEqualTo(value); + } + } + + // Tests of mutators' special values after initialization use mocked PRNG to test one special + // value after another. This counter enables adding new special values and testcases for them + // without modifying all the other test cases. + static Supplier<Integer> makeCounter() { + return new Supplier<Integer>() { + private int counter = 0; + + @Override + public Integer get() { + return counter++; + } + }; + } + + static Stream<Arguments> floatInitCasesFullRange() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMinusOneToOne() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = -1.0f, max = 1.0f) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -1.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), 1.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMinusMinToMin() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange( + min = -Float.MIN_VALUE, max = Float.MIN_VALUE) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMaxToInf() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange( + min = Float.MAX_VALUE, max = Float.POSITIVE_INFINITY) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMinusInfToMinusMax() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange( + min = Float.NEGATIVE_INFINITY, max = -Float.MAX_VALUE) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesFullRangeWithoutNaN() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = Float.NEGATIVE_INFINITY, + max = Float.POSITIVE_INFINITY, allowNaN = true) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + @ParameterizedTest + @MethodSource({"floatInitCasesMinusOneToOne", "floatInitCasesFullRange", + "floatInitCasesMinusMinToMin", "floatInitCasesMaxToInf", "floatInitCasesMinusInfToMinusMax", + "floatInitCasesFullRangeWithoutNaN"}) + void + testFloatInitCases(SerializingMutator<Float> mutator, Stream<Object> prngValues, float expected, + boolean specialValueIndexExists) { + assertThat(mutator.toString()).isEqualTo("Float"); + if (specialValueIndexExists) { + Float n = null; + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + n = mutator.init(prng); + } + assertThat(n).isEqualTo(expected); + } else { // should throw + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + mutator.init(prng); + } + }); + } + } + + static Stream<Arguments> floatMutateSanityChecksFullRangeCases() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = Float.NEGATIVE_INFINITY, + max = Float.POSITIVE_INFINITY, allowNaN = true) Float>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flips + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 0), 1.4e-45f, true), + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 30), 2.0f, true), + arguments(mutator, Stream.of(false, 2f), Stream.of(false, 0, 31), -2.0f, true), + arguments(mutator, Stream.of(false, -2f), Stream.of(false, 0, 22), -3.0f, true), + // mutateExponent + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111100), 0.125f, true), + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111110), 0.5f, true), + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111111), 1.0f, true), + // mutateMantissa + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 2, 0, 100), 1.4e-43f, true), + arguments(mutator, Stream.of(false, Float.intBitsToFloat(1)), Stream.of(false, 2, 0, -1), 0, + true), + // mutateWithMathematicalFn + arguments( + mutator, Stream.of(false, 10.1f), Stream.of(false, 3, 4), 11f, true), // Math::ceil + arguments( + mutator, Stream.of(false, 1000f), Stream.of(false, 3, 11), 3f, true), // Math::log10 + // skip libfuzzer + // random in range + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 5, 10f), 10f, true), + // unknown mutation case exception + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 6), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatMutateLimitedRangeCases() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = -1f, max = 1f, allowNaN = false) Float>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flip; forceInRange(); result equals previous value; adjust value + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 30, true), + 0f - Float.MIN_VALUE, true), + arguments(mutator, Stream.of(false, 1f), Stream.of(false, 0, 30), Math.nextDown(1f), true), + arguments(mutator, Stream.of(false, -1f), Stream.of(false, 0, 30), Math.nextUp(-1f), true), + // NaN after mutateWithMathematicalFn with NaN not allowed; forceInRange will return + // (min+max)/2 + arguments(mutator, Stream.of(false, -1f), Stream.of(false, 3, 16), 0.0f, true)); + } + + static Stream<Arguments> floatMutateLimitedRangeCasesWithNaN() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = -1f, max = 1f, allowNaN = true) Float>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // NaN after mutation and forceInRange(); all good! + arguments(mutator, Stream.of(false, -1f), Stream.of(false, 3, 16), Float.NaN, true), + // NaN (with a set bit #8) after init, mutation, and forceInRange(); need to change NaN to + // something else + arguments(mutator, Stream.of(true, 6), Stream.of(false, 0, 8, 0.3f), 0.3f, true)); + } + + @ParameterizedTest + @MethodSource({"floatMutateSanityChecksFullRangeCases", "floatMutateLimitedRangeCases", + "floatMutateLimitedRangeCasesWithNaN"}) + void + testFloatMutateCases(SerializingMutator<Float> mutator, Stream<Object> initValues, + Stream<Object> mutationValues, float expected, boolean knownMutatorSwitchCase) { + assertThat(mutator.toString()).isEqualTo("Float"); + Float n; + + // Init + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(initValues.toArray())) { + n = mutator.init(prng); + } + + // Mutate + if (knownMutatorSwitchCase) { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + n = mutator.mutate(n, prng); + } + assertThat(n).isEqualTo(expected); + + if (!((FloatMutator) mutator).allowNaN) { + assertThat(n).isNotEqualTo(Float.NaN); + } + + if (!Float.isNaN(n)) { + assertThat(n).isAtLeast(((FloatMutator) mutator).minValue); + assertThat(n).isAtMost(((FloatMutator) mutator).maxValue); + } + } else { // Invalid mutation because a case is not handled + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + mutator.mutate(UNUSED_FLOAT, prng); + } + }); + } + } + + @Test + void testFloatCrossOverMean() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = + mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { + assertThat(mutator.crossOver(0f, 0f, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(-0f, 0f, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(0f, 2f, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(1f, 2f, prng)).isWithin(1e-10f).of(1.5f); + assertThat(mutator.crossOver(1f, 3f, prng)).isWithin(1e-10f).of(2f); + assertThat(mutator.crossOver(Float.MAX_VALUE, Float.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(Float.MAX_VALUE); + + assertThat(mutator.crossOver(0f, -2f, prng)).isWithin(1e-10f).of(-1.0f); + assertThat(mutator.crossOver(-1f, -2f, prng)).isWithin(1e-10f).of(-1.5f); + assertThat(mutator.crossOver(-1f, -3f, prng)).isWithin(1e-10f).of(-2f); + assertThat(mutator.crossOver(-Float.MAX_VALUE, -Float.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(-Float.MAX_VALUE); + + assertThat(mutator.crossOver(-100f, 200f, prng)).isWithin(1e-10f).of(50.0f); + assertThat(mutator.crossOver(100f, -200f, prng)).isWithin(1e-10f).of(-50f); + assertThat(mutator.crossOver(-Float.MAX_VALUE, Float.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(0f); + + assertThat(mutator.crossOver(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, prng)).isNaN(); + assertThat(mutator.crossOver(Float.POSITIVE_INFINITY, 0f, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(0f, Float.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(Float.NEGATIVE_INFINITY, 0f, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(0f, Float.NEGATIVE_INFINITY, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(Float.NaN, 0f, prng)).isNaN(); + assertThat(mutator.crossOver(0f, Float.NaN, prng)).isNaN(); + } + } + + @Test + void testFloatCrossOverExponent() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(1, 1, 1)) { + assertThat(mutator.crossOver(2.0f, -1.5f, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(2.0f, Float.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(-1.5f, Float.NEGATIVE_INFINITY, prng)).isNaN(); + } + } + + @Test + void testFloatCrossOverMantissa() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(2, 2, 2)) { + assertThat(mutator.crossOver(4.0f, 3.5f, prng)).isWithin(1e-10f).of(7.0f); + assertThat(mutator.crossOver(Float.POSITIVE_INFINITY, 3.0f, prng)).isNaN(); + assertThat(mutator.crossOver(Float.MAX_VALUE, 0.0f, prng)).isWithin(1e-10f).of(1.7014118e38f); + } + } + + static Stream<Arguments> doubleInitCasesFullRange() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMinusOneToOne() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = -1.0, max = 1.0) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -1.0, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), 1.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMinusMinToMin() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange( + min = -Double.MIN_VALUE, max = Double.MIN_VALUE) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMaxToInf() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange( + min = Double.MAX_VALUE, max = Double.POSITIVE_INFINITY) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMinusInfToMinusMax() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange( + min = Double.NEGATIVE_INFINITY, max = -Double.MAX_VALUE) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesFullRangeWithoutNaN() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = Double.NEGATIVE_INFINITY, + max = Double.POSITIVE_INFINITY, allowNaN = true) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + @ParameterizedTest + @MethodSource({"doubleInitCasesMinusOneToOne", "doubleInitCasesFullRange", + "doubleInitCasesMinusMinToMin", "doubleInitCasesMaxToInf", + "doubleInitCasesMinusInfToMinusMax", "doubleInitCasesFullRangeWithoutNaN"}) + void + testDoubleInitCases(SerializingMutator<Double> mutator, Stream<Object> prngValues, + double expected, boolean knownSwitchCase) { + assertThat(mutator.toString()).isEqualTo("Double"); + if (knownSwitchCase) { + Double n = null; + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + n = mutator.init(prng); + } + assertThat(n).isEqualTo(expected); + } else { + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + mutator.init(prng); + } + }); + } + } + + static Stream<Arguments> doubleMutateSanityChecksFullRangeCases() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = Double.NEGATIVE_INFINITY, + max = Double.POSITIVE_INFINITY, allowNaN = true) Double>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flips + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 0), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 62), 2.0, true), + arguments(mutator, Stream.of(false, 2.0), Stream.of(false, 0, 63), -2.0, true), + arguments(mutator, Stream.of(false, -2.0), Stream.of(false, 0, 51), -3.0, true), + // mutateExponent + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111100), 0.125, true), + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111110), 0.5, true), + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111111), 1.0, true), + // mutateMantissa + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 2, 0, 100L), 4.94e-322, true), + arguments(mutator, Stream.of(false, Double.longBitsToDouble(1)), + Stream.of(false, 2, 0, -1L), 0, true), + // mutateWithMathematicalFn + arguments(mutator, Stream.of(false, 10.1), Stream.of(false, 3, 4), 11, true), // Math::ceil + arguments( + mutator, Stream.of(false, 1000.0), Stream.of(false, 3, 11), 3, true), // Math::log10 + // skip libfuzzer + // random in range + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 5, 10.0), 10, true), + // unknown mutation case exception + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 6), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleMutateLimitedRangeCases() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = -1, max = 1, allowNaN = false) Double>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flip; forceInRange(); result equals previous value; adjust value + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 62, true), + 0.0 - Double.MIN_VALUE, true), + arguments( + mutator, Stream.of(false, 1.0), Stream.of(false, 0, 62), Math.nextDown(1.0), true), + arguments( + mutator, Stream.of(false, -1.0), Stream.of(false, 0, 62), Math.nextUp(-1.0), true), + // NaN after mutateWithMathematicalFn: sqrt(-1.0); NaN not allowed; forceInRange will return + // (min+max)/2 + arguments(mutator, Stream.of(false, -1.0), Stream.of(false, 3, 16), 0.0, true)); + } + + static Stream<Arguments> doubleMutateLimitedRangeCasesWithNaN() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = -1, max = 1, allowNaN = true) Double>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // NaN after mutation and forceInRange(); all good! + arguments(mutator, Stream.of(false, -1.0), Stream.of(false, 3, 16), Double.NaN, true), + // NaN (with a set bit #8) after init, mutation, and forceInRange(); need to change NaN to + // something else + arguments(mutator, Stream.of(true, 6), Stream.of(false, 0, 8, 0.3), 0.3, true)); + } + + @ParameterizedTest + @MethodSource({"doubleMutateSanityChecksFullRangeCases", "doubleMutateLimitedRangeCases", + "doubleMutateLimitedRangeCasesWithNaN"}) + void + testDoubleMutateCases(SerializingMutator<Double> mutator, Stream<Object> initValues, + Stream<Object> mutationValues, double expected, boolean knownSwitchCase) { + assertThat(mutator.toString()).isEqualTo("Double"); + Double n; + + // Init + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(initValues.toArray())) { + n = mutator.init(prng); + } + + // Mutate + if (knownSwitchCase) { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + n = mutator.mutate(n, prng); + } + assertThat(n).isEqualTo(expected); + + if (!((DoubleMutator) mutator).allowNaN) { + assertThat(n).isNotEqualTo(Double.NaN); + } + + if (!Double.isNaN(n)) { + assertThat(n).isAtLeast(((DoubleMutator) mutator).minValue); + assertThat(n).isAtMost(((DoubleMutator) mutator).maxValue); + } + } else { // Invalid mutation because a case is not handled + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + mutator.mutate(UNUSED_DOUBLE, prng); + } + }); + } + } + + @Test + void testDoubleCrossOverMean() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = + mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { + assertThat(mutator.crossOver(0.0, 0.0, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(-0.0, 0.0, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(0.0, 2.0, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(1.0, 2.0, prng)).isWithin(1e-10f).of(1.5f); + assertThat(mutator.crossOver(1.0, 3.0, prng)).isWithin(1e-10f).of(2f); + assertThat(mutator.crossOver(Double.MAX_VALUE, Double.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(Double.MAX_VALUE); + + assertThat(mutator.crossOver(0.0, -2.0, prng)).isWithin(1e-10f).of(-1.0f); + assertThat(mutator.crossOver(-1.0, -2.0, prng)).isWithin(1e-10f).of(-1.5f); + assertThat(mutator.crossOver(-1.0, -3.0, prng)).isWithin(1e-10f).of(-2f); + assertThat(mutator.crossOver(-Double.MAX_VALUE, -Double.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(-Double.MAX_VALUE); + + assertThat(mutator.crossOver(-100.0, 200.0, prng)).isWithin(1e-10f).of(50.0f); + assertThat(mutator.crossOver(100.0, -200.0, prng)).isWithin(1e-10f).of(-50f); + assertThat(mutator.crossOver(-Double.MAX_VALUE, Double.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(0f); + + assertThat(mutator.crossOver(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, prng)) + .isNaN(); + assertThat(mutator.crossOver(Double.POSITIVE_INFINITY, 0.0, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(0.0, Double.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(Double.NEGATIVE_INFINITY, 0.0, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(0.0, Double.NEGATIVE_INFINITY, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(Double.NaN, 0.0, prng)).isNaN(); + assertThat(mutator.crossOver(0.0, Double.NaN, prng)).isNaN(); + } + } + + @Test + void testDoubleCrossOverExponent() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(1, 1, 1)) { + assertThat(mutator.crossOver(2.0, -1.5, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(2.0, Double.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(-1.5, Double.NEGATIVE_INFINITY, prng)).isNaN(); + } + } + + @Test + void testDoubleCrossOverMantissa() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(2, 2, 2)) { + assertThat(mutator.crossOver(4.0, 3.5, prng)).isWithin(1e-10f).of(7.0f); + assertThat(mutator.crossOver(Double.POSITIVE_INFINITY, 3.0, prng)).isNaN(); + assertThat(mutator.crossOver(Double.MAX_VALUE, 0.0, prng)) + .isWithin(1e-10f) + .of(8.98846567431158e307); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java new file mode 100644 index 00000000..dda8cfed --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.mutator.lang.IntegralMutatorFactory.AbstractIntegralMutator.forceInRange; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@SuppressWarnings("unchecked") +class IntegralMutatorTest { + static Stream<Arguments> forceInRangeCases() { + return Stream.of(arguments(0, 0, 1), arguments(5, 0, 1), arguments(-5, -10, -1), + arguments(-200, -10, -1), arguments(10, 0, 3), arguments(-5, 0, 3), arguments(10, -7, 7), + arguments(Long.MIN_VALUE, Long.MIN_VALUE, Long.MAX_VALUE), + arguments(Long.MIN_VALUE, Long.MIN_VALUE, 100), + arguments(Long.MIN_VALUE + 100, Long.MIN_VALUE, 100), + arguments(Long.MAX_VALUE, -100, Long.MAX_VALUE), + arguments(Long.MAX_VALUE - 100, -100, Long.MAX_VALUE), + arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE), + arguments(Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MAX_VALUE), + arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE - 1), + arguments(Long.MIN_VALUE, Long.MAX_VALUE - 5, Long.MAX_VALUE), + arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MIN_VALUE + 5)); + } + + @ParameterizedTest + @MethodSource("forceInRangeCases") + void testForceInRange(long value, long minValue, long maxValue) { + long inRange = forceInRange(value, minValue, maxValue); + assertThat(inRange).isAtLeast(minValue); + assertThat(inRange).isAtMost(maxValue); + if (value >= minValue && value <= maxValue) { + assertThat(inRange).isEqualTo(value); + } + } + + @Test + void testCrossOver() { + SerializingMutator<Long> mutator = + (SerializingMutator<Long>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Long>() {}.annotatedType()); + // cross over mean values + try (MockPseudoRandom prng = mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { + assertThat(mutator.crossOver(0L, 0L, prng)).isEqualTo(0); + assertThat(mutator.crossOver(0L, 2L, prng)).isEqualTo(1); + assertThat(mutator.crossOver(1L, 2L, prng)).isEqualTo(1); + assertThat(mutator.crossOver(1L, 3L, prng)).isEqualTo(2); + assertThat(mutator.crossOver(Long.MAX_VALUE, Long.MAX_VALUE, prng)).isEqualTo(Long.MAX_VALUE); + + assertThat(mutator.crossOver(0L, -2L, prng)).isEqualTo(-1); + assertThat(mutator.crossOver(-1L, -2L, prng)).isEqualTo(-1); + assertThat(mutator.crossOver(-1L, -3L, prng)).isEqualTo(-2); + assertThat(mutator.crossOver(Long.MIN_VALUE, Long.MIN_VALUE, prng)).isEqualTo(Long.MIN_VALUE); + + assertThat(mutator.crossOver(-100L, 200L, prng)).isEqualTo(50); + assertThat(mutator.crossOver(100L, -200L, prng)).isEqualTo(-50); + assertThat(mutator.crossOver(Long.MIN_VALUE, Long.MAX_VALUE, prng)).isEqualTo(0); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java new file mode 100644 index 00000000..bc9a65b2 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.lang.reflect.AnnotatedType; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +class NullableMutatorTest { + @Test + void testNullable() { + SerializingMutator<Boolean> mutator = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()) + .createOrThrow(Boolean.class); + assertThat(mutator.toString()).isEqualTo("Nullable<Boolean>"); + + Boolean bool; + try (MockPseudoRandom prng = mockPseudoRandom(/* init to null */ true)) { + bool = mutator.init(prng); + } + assertThat(bool).isNull(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* init for non-null Boolean */ false)) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* mutate to non-null Boolean */ false)) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* mutate to null */ true)) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isNull(); + } + + @Test + void testNotNull() { + ChainedMutatorFactory factory = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()); + AnnotatedType notNullBoolean = new TypeHolder<@NotNull Boolean>() {}.annotatedType(); + SerializingMutator<Boolean> mutator = + (SerializingMutator<Boolean>) factory.createOrThrow(notNullBoolean); + assertThat(mutator.toString()).isEqualTo("Boolean"); + } + + @Test + void testPrimitive() { + ChainedMutatorFactory factory = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()); + SerializingMutator<Boolean> mutator = factory.createOrThrow(boolean.class); + assertThat(mutator.toString()).isEqualTo("Boolean"); + } + + @Test + void testCrossOver() { + SerializingMutator<Boolean> mutator = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()) + .createOrThrow(Boolean.class); + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + Boolean valueCrossedOver = mutator.crossOver(Boolean.TRUE, Boolean.TRUE, prng); + assertThat(valueCrossedOver).isNotNull(); + } + try (MockPseudoRandom prng = mockPseudoRandom()) { + Boolean bothNull = mutator.crossOver(null, null, prng); + assertThat(bothNull).isNull(); + } + try (MockPseudoRandom prng = mockPseudoRandom(false)) { + Boolean oneNotNull = mutator.crossOver(null, Boolean.TRUE, prng); + assertThat(oneNotNull).isNotNull(); + } + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + Boolean nullFrequency = mutator.crossOver(null, Boolean.TRUE, prng); + assertThat(nullFrequency).isNull(); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java new file mode 100644 index 00000000..23060359 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.mutator.lang.StringMutatorFactory.fixUpAscii; +import static com.code_intelligence.jazzer.mutation.mutator.lang.StringMutatorFactory.fixUpUtf8; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator; +import com.code_intelligence.jazzer.mutation.support.RandomSupport; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import com.google.protobuf.ByteString; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.SplittableRandom; +import org.junit.jupiter.api.*; + +class StringMutatorTest { + /** + * Some tests may set {@link LibFuzzerMutator#MOCK_SIZE_KEY} which can interfere with other tests + * unless cleared. + */ + @AfterEach + void cleanMockSize() { + System.clearProperty(LibFuzzerMutator.MOCK_SIZE_KEY); + } + + @RepeatedTest(10) + void testFixAscii_randomInputFixed(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixAscii_randomInputFixed".hashCode() * info.getCurrentRepetition()); + + for (int length = 0; length < 1000; length++) { + byte[] randomBytes = generateRandomBytes(random, length); + byte[] copy = Arrays.copyOf(randomBytes, randomBytes.length); + fixUpAscii(copy); + if (isValidAscii(randomBytes)) { + assertThat(copy).isEqualTo(randomBytes); + } else { + assertThat(isValidAscii(copy)).isTrue(); + } + } + } + + @RepeatedTest(10) + void testFixAscii_validInputNotChanged(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixAscii_validInputNotChanged".hashCode() * info.getCurrentRepetition()); + + for (int codePoints = 0; codePoints < 1000; codePoints++) { + byte[] validAscii = generateValidAsciiBytes(random, codePoints); + byte[] copy = Arrays.copyOf(validAscii, validAscii.length); + fixUpAscii(copy); + assertThat(copy).isEqualTo(validAscii); + } + } + + @RepeatedTest(20) + void testFixUtf8_randomInputFixed(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixUtf8_randomInputFixed".hashCode() * info.getCurrentRepetition()); + + for (int length = 0; length < 1000; length++) { + byte[] randomBytes = generateRandomBytes(random, length); + byte[] copy = Arrays.copyOf(randomBytes, randomBytes.length); + fixUpUtf8(copy); + if (isValidUtf8(randomBytes)) { + assertThat(copy).isEqualTo(randomBytes); + } else { + assertThat(isValidUtf8(copy)).isTrue(); + } + } + } + + @RepeatedTest(20) + void testFixUtf8_validInputNotChanged(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixUtf8_validInputNotChanged".hashCode() * info.getCurrentRepetition()); + + for (int codePoints = 0; codePoints < 1000; codePoints++) { + byte[] validUtf8 = generateValidUtf8Bytes(random, codePoints); + byte[] copy = Arrays.copyOf(validUtf8, validUtf8.length); + fixUpUtf8(copy); + assertThat(copy).isEqualTo(validUtf8); + } + } + + @Test + void testMinLengthInit() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + try (MockPseudoRandom prng = mockPseudoRandom(5)) { + // mock prng should throw an assert error when given a lower value than min + Assertions.assertThrows(AssertionError.class, () -> { String s = mutator.init(prng); }); + } + } + + @Test + void testMaxLengthInit() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(max = 50) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + try (MockPseudoRandom prng = mockPseudoRandom(60)) { + // mock prng should throw an assert error when given a value higher than max + Assertions.assertThrows(AssertionError.class, () -> { String s = mutator.init(prng); }); + } + } + + @Test + void testMinLengthMutate() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + String s; + try (MockPseudoRandom prng = mockPseudoRandom(10, "foobarbazf".getBytes())) { + s = mutator.init(prng); + } + assertThat(s).isEqualTo("foobarbazf"); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "5"); + try (MockPseudoRandom prng = mockPseudoRandom()) { + s = mutator.mutate(s, prng); + } + assertThat(s).isEqualTo("gqrff\0\0\0\0\0"); + } + + @Test + void testMaxLengthMutate() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(max = 15) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + String s; + try (MockPseudoRandom prng = mockPseudoRandom(10, "foobarbazf".getBytes())) { + s = mutator.init(prng); + } + assertThat(s).isEqualTo("foobarbazf"); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "20"); + try (MockPseudoRandom prng = mockPseudoRandom()) { + Assertions.assertThrows( + ArrayIndexOutOfBoundsException.class, () -> { String s2 = mutator.mutate(s, prng); }); + } + } + + @Test + void testMultibyteCharacters() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + String s; + try ( + MockPseudoRandom prng = mockPseudoRandom(10, "foobarÖÖ".getBytes(StandardCharsets.UTF_8))) { + s = mutator.init(prng); + } + assertThat(s).hasLength(8); + assertThat(s).isEqualTo("foobarÖÖ"); + } + + private static boolean isValidUtf8(byte[] data) { + return ByteString.copyFrom(data).isValidUtf8(); + } + + private static boolean isValidAscii(byte[] data) { + for (byte b : data) { + if ((b & 0xFF) > 0x7F) { + return false; + } + } + return true; + } + + private static byte[] generateRandomBytes(SplittableRandom random, int length) { + byte[] bytes = new byte[length]; + RandomSupport.nextBytes(random, bytes); + return bytes; + } + + private static byte[] generateValidAsciiBytes(SplittableRandom random, int length) { + return random.ints(0, 0x7F) + .limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString() + .getBytes(StandardCharsets.UTF_8); + } + + private static byte[] generateValidUtf8Bytes(SplittableRandom random, long codePoints) { + return random.ints(0, Character.MAX_CODE_POINT + 1) + .filter(code -> code < Character.MIN_SURROGATE || code > Character.MAX_SURROGATE) + .limit(codePoints) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString() + .getBytes(StandardCharsets.UTF_8); + } +} |