diff options
Diffstat (limited to 'src/test/java/com/code_intelligence/jazzer/mutation/mutator')
21 files changed, 4678 insertions, 0 deletions
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel new file mode 100644 index 00000000..26943353 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel @@ -0,0 +1,28 @@ +load("@contrib_rules_jvm//java:defs.bzl", "java_junit5_test") + +TEST_PARALLELISM = 4 + +java_junit5_test( + name = "StressTest", + size = "large", + srcs = ["StressTest.java"], + env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"}, + jvm_flags = [ + "-Djunit.jupiter.execution.parallel.enabled=true", + "-Djunit.jupiter.execution.parallel.mode.default=concurrent", + "-Djunit.jupiter.execution.parallel.config.strategy=fixed", + "-Djunit.jupiter.execution.parallel.config.fixed.parallelism=" + str(TEST_PARALLELISM), + ], + tags = ["cpu:" + str(TEST_PARALLELISM)], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto", + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator", + "//src/main/java/com/code_intelligence/jazzer/mutation/support", + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto:proto2_java_proto", + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto:proto3_java_proto", + "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support", + "@com_google_protobuf_protobuf_java//jar", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java new file mode 100644 index 00000000..3bf880a4 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java @@ -0,0 +1,588 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator; + +import static com.code_intelligence.jazzer.mutation.mutator.Mutators.validateAnnotationUsage; +import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithZeros; +import static com.code_intelligence.jazzer.mutation.support.Preconditions.require; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.anyPseudoRandom; +import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asAnnotatedType; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.lang.Math.floor; +import static java.lang.Math.pow; +import static java.lang.Math.sqrt; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.IntStream.rangeClosed; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange; +import com.code_intelligence.jazzer.mutation.annotation.FloatInRange; +import com.code_intelligence.jazzer.mutation.annotation.InRange; +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithSize; +import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource; +import com.code_intelligence.jazzer.mutation.annotation.proto.WithDefaultInstance; +import com.code_intelligence.jazzer.mutation.api.PseudoRandom; +import com.code_intelligence.jazzer.mutation.api.Serializer; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import com.code_intelligence.jazzer.protobuf.Proto2.TestProtobuf; +import com.code_intelligence.jazzer.protobuf.Proto3.AnyField3; +import com.code_intelligence.jazzer.protobuf.Proto3.BytesField3; +import com.code_intelligence.jazzer.protobuf.Proto3.DoubleField3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3.TestEnum; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3.TestEnumRepeated; +import com.code_intelligence.jazzer.protobuf.Proto3.FloatField3; +import com.code_intelligence.jazzer.protobuf.Proto3.IntegralField3; +import com.code_intelligence.jazzer.protobuf.Proto3.MapField3; +import com.code_intelligence.jazzer.protobuf.Proto3.MessageField3; +import com.code_intelligence.jazzer.protobuf.Proto3.MessageMapField3; +import com.code_intelligence.jazzer.protobuf.Proto3.OptionalPrimitiveField3; +import com.code_intelligence.jazzer.protobuf.Proto3.PrimitiveField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedDoubleField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedFloatField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedIntegralField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedRecursiveMessageField3; +import com.code_intelligence.jazzer.protobuf.Proto3.SingleOptionOneOfField3; +import com.code_intelligence.jazzer.protobuf.Proto3.StringField3; +import com.google.protobuf.Any; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.Message; +import com.google.protobuf.Message.Builder; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.AnnotatedType; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class StressTest { + private static final int NUM_INITS = 500; + private static final int NUM_MUTATE_PER_INIT = 100; + private static final double MANY_DISTINCT_ELEMENTS_RATIO = 0.5; + + private enum TestEnumTwo { A, B } + + private enum TestEnumThree { A, B, C } + + @SuppressWarnings("unused") + static Message getTestProtobufDefaultInstance() { + return TestProtobuf.getDefaultInstance(); + } + + public static Stream<Arguments> stressTestCases() { + return Stream.of(arguments(asAnnotatedType(boolean.class), "Boolean", exactly(false, true), + exactly(false, true)), + arguments(new TypeHolder<@NotNull Boolean>() {}.annotatedType(), "Boolean", + exactly(false, true), exactly(false, true)), + arguments(new TypeHolder<Boolean>() {}.annotatedType(), "Nullable<Boolean>", + exactly(null, false, true), exactly(null, false, true)), + arguments(new TypeHolder<@NotNull List<@NotNull Boolean>>() {}.annotatedType(), + "List<Boolean>", exactly(emptyList(), singletonList(false), singletonList(true)), + manyDistinctElements()), + arguments(new TypeHolder<@NotNull List<Boolean>>() {}.annotatedType(), + "List<Nullable<Boolean>>", + exactly(emptyList(), singletonList(null), singletonList(false), singletonList(true)), + manyDistinctElements()), + arguments(new TypeHolder<List<@NotNull Boolean>>() {}.annotatedType(), + "Nullable<List<Boolean>>", + exactly(null, emptyList(), singletonList(false), singletonList(true)), + distinctElementsRatio(0.30)), + arguments(new TypeHolder<List<Boolean>>() {}.annotatedType(), + "Nullable<List<Nullable<Boolean>>>", + exactly( + null, emptyList(), singletonList(null), singletonList(false), singletonList(true)), + distinctElementsRatio(0.30)), + arguments( + new TypeHolder<@NotNull Map<@NotNull String, @NotNull String>>() {}.annotatedType(), + "Map<String,String>", distinctElementsRatio(0.45), distinctElementsRatio(0.45)), + arguments(new TypeHolder<Map<@NotNull String, @NotNull String>>() {}.annotatedType(), + "Nullable<Map<String,String>>", distinctElementsRatio(0.46), + distinctElementsRatio(0.48)), + arguments( + new TypeHolder<@WithSize(max = 3) @NotNull Map<@NotNull Integer, @NotNull Integer>>() { + }.annotatedType(), + "Map<Integer,Integer>", + // Half of all maps are empty, the other half is heavily biased towards special values. + all(mapSizeInClosedRange(0, 3), distinctElementsRatio(0.2)), + all(mapSizeInClosedRange(0, 3), manyDistinctElements())), + arguments( + new TypeHolder<@NotNull Map<@NotNull Boolean, @NotNull Boolean>>() {}.annotatedType(), + "Map<Boolean,Boolean>", + // 1 0-element map, 4 1-element maps + distinctElements(1 + 4), + // 1 0-element map, 4 1-element maps, 4 2-element maps + distinctElements(1 + 4 + 4)), + arguments(asAnnotatedType(byte.class), "Byte", + // init is heavily biased towards special values and only returns a uniformly random + // value in 1 out of 5 calls. + all(expectedNumberOfDistinctElements(1 << Byte.SIZE, boundHits(NUM_INITS, 0.2)), + contains((byte) 0, (byte) 1, Byte.MIN_VALUE, Byte.MAX_VALUE)), + // With mutations, we expect to reach all possible bytes. + exactly(rangeClosed(Byte.MIN_VALUE, Byte.MAX_VALUE).mapToObj(i -> (byte) i).toArray())), + arguments(asAnnotatedType(short.class), "Short", + // init is heavily biased towards special values and only returns a uniformly random + // value in 1 out of 5 calls. + all(expectedNumberOfDistinctElements(1 << Short.SIZE, boundHits(NUM_INITS, 0.2)), + contains((short) 0, (short) 1, Short.MIN_VALUE, Short.MAX_VALUE)), + // The integral type mutator does not always return uniformly random values and the + // random walk it uses is more likely to produce non-distinct elements, hence the test + // only passes with ~90% of the optimal parameters. + expectedNumberOfDistinctElements( + 1 << Short.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)), + arguments(asAnnotatedType(int.class), "Integer", + // init is heavily biased towards special values and only returns a uniformly random + // value in 1 out of 5 calls. + all(expectedNumberOfDistinctElements(1L << Integer.SIZE, boundHits(NUM_INITS, 0.2)), + contains(0, 1, Integer.MIN_VALUE, Integer.MAX_VALUE)), + // See "Short" case. + expectedNumberOfDistinctElements( + 1L << Integer.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)), + arguments(new TypeHolder<@NotNull @InRange(min = 0) Long>() {}.annotatedType(), "Long", + // init is heavily biased towards special values and only returns a uniformly random + // value in 1 out of 5 calls. + all(expectedNumberOfDistinctElements(1L << Long.SIZE - 1, boundHits(NUM_INITS, 0.2)), + contains(0L, 1L, Long.MAX_VALUE)), + // See "Short" case. + expectedNumberOfDistinctElements( + 1L << Integer.SIZE - 1, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)), + arguments( + new TypeHolder<@NotNull @InRange(max = Integer.MIN_VALUE + 5) Integer>() { + }.annotatedType(), + "Integer", + exactly(rangeClosed(Integer.MIN_VALUE, Integer.MIN_VALUE + 5).boxed().toArray()), + exactly(rangeClosed(Integer.MIN_VALUE, Integer.MIN_VALUE + 5).boxed().toArray())), + arguments(asAnnotatedType(TestEnumTwo.class), "Nullable<Enum<TestEnumTwo>>", + exactly(null, TestEnumTwo.A, TestEnumTwo.B), + exactly(null, TestEnumTwo.A, TestEnumTwo.B)), + arguments(asAnnotatedType(TestEnumThree.class), "Nullable<Enum<TestEnumThree>>", + exactly(null, TestEnumThree.A, TestEnumThree.B, TestEnumThree.C), + exactly(null, TestEnumThree.A, TestEnumThree.B, TestEnumThree.C)), + arguments(new TypeHolder<@NotNull @FloatInRange(min = 0f) Float>() {}.annotatedType(), + "Float", + all(distinctElementsRatio(0.45), + doesNotContain(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, -Float.MIN_VALUE), + contains(Float.NaN, Float.POSITIVE_INFINITY, Float.MAX_VALUE, Float.MIN_VALUE, 0.0f, + -0.0f)), + all(distinctElementsRatio(0.75), + doesNotContain(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, -Float.MIN_VALUE))), + arguments(new TypeHolder<@NotNull Float>() {}.annotatedType(), "Float", + all(distinctElementsRatio(0.45), + contains(Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, + -Float.MAX_VALUE, Float.MAX_VALUE, -Float.MIN_VALUE, Float.MIN_VALUE, 0.0f, + -0.0f)), + distinctElementsRatio(0.76)), + arguments( + new TypeHolder<@NotNull @FloatInRange( + min = -1.0f, max = 1.0f, allowNaN = false) Float>() { + }.annotatedType(), + "Float", + all(distinctElementsRatio(0.45), + doesNotContain(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY), + contains(-Float.MIN_VALUE, Float.MIN_VALUE, 0.0f, -0.0f)), + all(distinctElementsRatio(0.525), + doesNotContain(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY), + contains(-Float.MIN_VALUE, Float.MIN_VALUE, 0.0f, -0.0f))), + arguments(new TypeHolder<@NotNull Double>() {}.annotatedType(), "Double", + all(distinctElementsRatio(0.45), + contains(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)), + distinctElementsRatio(0.75)), + arguments( + new TypeHolder<@NotNull @DoubleInRange( + min = -1.0, max = 1.0, allowNaN = false) Double>() { + }.annotatedType(), + "Double", all(distinctElementsRatio(0.45), doesNotContain(Double.NaN)), + all(distinctElementsRatio(0.55), doesNotContain(Double.NaN)))); + } + + public static Stream<Arguments> protoStressTestCases() { + return Stream.of( + arguments(new TypeHolder<@NotNull OptionalPrimitiveField3>() {}.annotatedType(), + "{Builder.Nullable<Boolean>} -> Message", + exactly(OptionalPrimitiveField3.newBuilder().build(), + OptionalPrimitiveField3.newBuilder().setSomeField(false).build(), + OptionalPrimitiveField3.newBuilder().setSomeField(true).build()), + exactly(OptionalPrimitiveField3.newBuilder().build(), + OptionalPrimitiveField3.newBuilder().setSomeField(false).build(), + OptionalPrimitiveField3.newBuilder().setSomeField(true).build())), + arguments(new TypeHolder<@NotNull RepeatedRecursiveMessageField3>() {}.annotatedType(), + "{Builder.Boolean, WithoutInit(Builder via List<(cycle) -> Message>)} -> Message", + // The message field is recursive and thus not initialized. + exactly(RepeatedRecursiveMessageField3.getDefaultInstance(), + RepeatedRecursiveMessageField3.newBuilder().setSomeField(true).build()), + manyDistinctElements()), + arguments(new TypeHolder<@NotNull IntegralField3>() {}.annotatedType(), + "{Builder.Integer} -> Message", + // init is heavily biased towards special values and only returns a uniformly random + // value in 1 out of 5 calls. + all(expectedNumberOfDistinctElements(1L << Integer.SIZE, boundHits(NUM_INITS, 0.2)), + contains(IntegralField3.newBuilder().build(), + IntegralField3.newBuilder().setSomeField(1).build(), + IntegralField3.newBuilder().setSomeField(Integer.MIN_VALUE).build(), + IntegralField3.newBuilder().setSomeField(Integer.MAX_VALUE).build())), + // Our mutations return uniformly random elements in ~3/8 of all cases. + expectedNumberOfDistinctElements( + 1L << Integer.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 3 / 8)), + arguments(new TypeHolder<@NotNull RepeatedIntegralField3>() {}.annotatedType(), + "{Builder via List<Integer>} -> Message", + contains(RepeatedIntegralField3.getDefaultInstance(), + RepeatedIntegralField3.newBuilder().addSomeField(0).build(), + RepeatedIntegralField3.newBuilder().addSomeField(1).build(), + RepeatedIntegralField3.newBuilder().addSomeField(Integer.MAX_VALUE).build(), + RepeatedIntegralField3.newBuilder().addSomeField(Integer.MIN_VALUE).build()), + // TODO: This ratio is on the lower end, most likely because of the strong bias towards + // special values combined with the small initial size of the list. When we improve the + // list mutator, this may be increased. + distinctElementsRatio(0.25)), + arguments(new TypeHolder<@NotNull BytesField3>() {}.annotatedType(), + "{Builder.byte[] -> ByteString} -> Message", manyDistinctElements(), + manyDistinctElements()), + arguments(new TypeHolder<@NotNull StringField3>() {}.annotatedType(), + "{Builder.String} -> Message", manyDistinctElements(), manyDistinctElements()), + arguments(new TypeHolder<@NotNull EnumField3>() {}.annotatedType(), + "{Builder.Enum<TestEnum>} -> Message", + exactly(EnumField3.getDefaultInstance(), + EnumField3.newBuilder().setSomeField(TestEnum.VAL2).build()), + exactly(EnumField3.getDefaultInstance(), + EnumField3.newBuilder().setSomeField(TestEnum.VAL2).build())), + arguments(new TypeHolder<@NotNull EnumFieldRepeated3>() {}.annotatedType(), + "{Builder via List<Enum<TestEnumRepeated>>} -> Message", + exactly(EnumFieldRepeated3.getDefaultInstance(), + EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.UNASSIGNED).build(), + EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.VAL1).build(), + EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.VAL2).build()), + manyDistinctElements()), + arguments(new TypeHolder<@NotNull MapField3>() {}.annotatedType(), + "{Builder.Map<Integer,String>} -> Message", distinctElementsRatio(0.47), + manyDistinctElements()), + arguments(new TypeHolder<@NotNull MessageMapField3>() {}.annotatedType(), + "{Builder.Map<String,{Builder.Map<Integer,String>} -> Message>} -> Message", + distinctElementsRatio(0.45), distinctElementsRatio(0.45)), + arguments(new TypeHolder<@NotNull DoubleField3>() {}.annotatedType(), + "{Builder.Double} -> Message", distinctElementsRatio(0.45), distinctElementsRatio(0.7)), + arguments(new TypeHolder<@NotNull RepeatedDoubleField3>() {}.annotatedType(), + "{Builder via List<Double>} -> Message", distinctElementsRatio(0.2), + distinctElementsRatio(0.9)), + arguments(new TypeHolder<@NotNull FloatField3>() {}.annotatedType(), + "{Builder.Float} -> Message", distinctElementsRatio(0.45), distinctElementsRatio(0.7)), + arguments(new TypeHolder<@NotNull RepeatedFloatField3>() {}.annotatedType(), + "{Builder via List<Float>} -> Message", distinctElementsRatio(0.20), + distinctElementsRatio(0.9), emptyList()), + arguments(new TypeHolder<@NotNull TestProtobuf>() {}.annotatedType(), + "{Builder.Nullable<Boolean>, Builder.Nullable<Integer>, Builder.Nullable<Integer>, Builder.Nullable<Long>, Builder.Nullable<Long>, Builder.Nullable<Float>, Builder.Nullable<Double>, Builder.Nullable<String>, Builder.Nullable<Enum<Enum>>, WithoutInit(Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>, WithoutInit(Builder.Nullable<(cycle) -> Message>)} -> Message>), Builder via List<Boolean>, Builder via List<Integer>, Builder via List<Integer>, Builder via List<Long>, Builder via List<Long>, Builder via List<Float>, Builder via List<Double>, Builder via List<String>, Builder via List<Enum<Enum>>, WithoutInit(Builder via List<(cycle) -> Message>), Builder.Map<Integer,Integer>, Builder.Nullable<FixedValue(OnlyLabel)>, Builder.Nullable<{<empty>} -> Message>, Builder.Nullable<Integer> | Builder.Nullable<Long> | Builder.Nullable<Integer>} -> Message", + manyDistinctElements(), manyDistinctElements()), + arguments( + new TypeHolder<@NotNull @WithDefaultInstance( + "com.code_intelligence.jazzer.mutation.mutator.StressTest#getTestProtobufDefaultInstance") + Message>() { + }.annotatedType(), + "{Builder.Nullable<Boolean>, Builder.Nullable<Integer>, Builder.Nullable<Integer>, Builder.Nullable<Long>, Builder.Nullable<Long>, Builder.Nullable<Float>, Builder.Nullable<Double>, Builder.Nullable<String>, Builder.Nullable<Enum<Enum>>, WithoutInit(Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>, WithoutInit(Builder.Nullable<(cycle) -> Message>)} -> Message>), Builder via List<Boolean>, Builder via List<Integer>, Builder via List<Integer>, Builder via List<Long>, Builder via List<Long>, Builder via List<Float>, Builder via List<Double>, Builder via List<String>, Builder via List<Enum<Enum>>, WithoutInit(Builder via List<(cycle) -> Message>), Builder.Map<Integer,Integer>, Builder.Nullable<FixedValue(OnlyLabel)>, Builder.Nullable<{<empty>} -> Message>, Builder.Nullable<Integer> | Builder.Nullable<Long> | Builder.Nullable<Integer>} -> Message", + manyDistinctElements(), manyDistinctElements()), + arguments( + new TypeHolder<@NotNull @AnySource( + {PrimitiveField3.class, MessageField3.class}) AnyField3>() { + }.annotatedType(), + "{Builder.Nullable<Builder.{Builder.Boolean} -> Message | Builder.{Builder.Nullable<(cycle) -> Message>} -> Message -> Message>} -> Message", + exactly(AnyField3.getDefaultInstance(), + AnyField3.newBuilder() + .setSomeField(Any.pack(PrimitiveField3.getDefaultInstance())) + .build(), + AnyField3.newBuilder() + .setSomeField(Any.pack(PrimitiveField3.newBuilder().setSomeField(true).build())) + .build(), + AnyField3.newBuilder() + .setSomeField(Any.pack(MessageField3.getDefaultInstance())) + .build(), + AnyField3.newBuilder() + .setSomeField( + Any.pack(MessageField3.newBuilder() + .setMessageField(PrimitiveField3.getDefaultInstance()) + .build())) + .build(), + AnyField3.newBuilder() + .setSomeField(Any.pack( + MessageField3.newBuilder() + .setMessageField(PrimitiveField3.newBuilder().setSomeField(true)) + .build())) + .build()), + exactly(AnyField3.getDefaultInstance(), + AnyField3.newBuilder() + .setSomeField(Any.pack(PrimitiveField3.getDefaultInstance())) + .build(), + AnyField3.newBuilder() + .setSomeField(Any.pack(PrimitiveField3.newBuilder().setSomeField(true).build())) + .build(), + AnyField3.newBuilder() + .setSomeField(Any.pack(MessageField3.getDefaultInstance())) + .build(), + AnyField3.newBuilder() + .setSomeField( + Any.pack(MessageField3.newBuilder() + .setMessageField(PrimitiveField3.getDefaultInstance()) + .build())) + .build(), + AnyField3.newBuilder() + .setSomeField(Any.pack( + MessageField3.newBuilder() + .setMessageField(PrimitiveField3.newBuilder().setSomeField(true)) + .build())) + .build())), + arguments(new TypeHolder<@NotNull SingleOptionOneOfField3>() {}.annotatedType(), + "{Builder.Nullable<Boolean>} -> Message", + exactly(SingleOptionOneOfField3.getDefaultInstance(), + SingleOptionOneOfField3.newBuilder().setBoolField(false).build(), + SingleOptionOneOfField3.newBuilder().setBoolField(true).build()), + exactly(SingleOptionOneOfField3.getDefaultInstance(), + SingleOptionOneOfField3.newBuilder().setBoolField(false).build(), + SingleOptionOneOfField3.newBuilder().setBoolField(true).build()))); + } + + @SafeVarargs + private static Consumer<List<Object>> all(Consumer<List<Object>>... checks) { + return list -> { + for (Consumer<List<Object>> check : checks) { + check.accept(list); + } + }; + } + + private static Consumer<List<Object>> distinctElements(int num) { + return list -> assertThat(new HashSet<>(list).size()).isAtLeast(num); + } + + private static Consumer<List<Object>> manyDistinctElements() { + return distinctElementsRatio(MANY_DISTINCT_ELEMENTS_RATIO); + } + + /** + * Returns a lower bound on the expected number of hits when sampling from a domain of a given + * size with the given probability. + */ + private static int boundHits(long domainSize, double probability) { + // Binomial distribution. + double expectedValue = domainSize * probability; + double variance = domainSize * probability * (1 - probability); + double standardDeviation = sqrt(variance); + // Allow missing the expected value by two standard deviations. For a normal distribution, + // this would correspond to 95% of all cases. + int almostCertainLowerBound = (int) floor(expectedValue - 2 * standardDeviation); + return almostCertainLowerBound; + } + + /** + * Asserts that a given list contains at least as many distinct elements as can be expected when + * picking {@code picks} out of {@code domainSize} elements uniformly at random. + */ + private static Consumer<List<Object>> expectedNumberOfDistinctElements( + long domainSize, int picks) { + // https://www.randomservices.org/random/urn/Birthday.html#mom2 + double expectedValue = domainSize * (1 - pow(1 - 1.0 / domainSize, picks)); + double variance = domainSize * (domainSize - 1) * pow(1 - 2.0 / domainSize, picks) + + domainSize * pow(1 - 1.0 / domainSize, picks) + - domainSize * domainSize * pow(1 - 1.0 / domainSize, 2 * picks); + double standardDeviation = sqrt(variance); + // Allow missing the expected value by two standard deviations. For a normal distribution, + // this would correspond to 95% of all cases. + int almostCertainLowerBound = (int) floor(expectedValue - 2 * standardDeviation); + return list + -> assertWithMessage("V=distinct elements among %s picked out of %s\nE[V]=%s\nσ[V]=%s", + picks, domainSize, expectedValue, standardDeviation) + .that(new HashSet<>(list).size()) + .isAtLeast(almostCertainLowerBound); + } + + private static Consumer<List<Object>> distinctElementsRatio(double ratio) { + require(ratio > 0); + require(ratio <= 1); + return list -> assertThat(new HashSet<>(list).size() / (double) list.size()).isAtLeast(ratio); + } + + private static Consumer<List<Object>> exactly(Object... expected) { + return list -> assertThat(new HashSet<>(list)).containsExactly(expected); + } + + private static Consumer<List<Object>> contains(Object... expected) { + return list -> assertThat(new HashSet<>(list)).containsAtLeastElementsIn(expected); + } + + private static Consumer<List<Object>> doesNotContain(Object... expected) { + return list -> assertThat(new HashSet<>(list)).containsNoneIn(expected); + } + + private static Consumer<List<Object>> mapSizeInClosedRange(int min, int max) { + return list -> { + list.forEach(map -> { + if (map instanceof Map) { + assertThat(((Map) map).size()).isAtLeast(min); + assertThat(((Map) map).size()).isAtMost(max); + } else { + throw new IllegalArgumentException( + "Expected a list of maps, got list of" + map.getClass().getName()); + } + }); + }; + } + + @ParameterizedTest(name = "{index} {0}, {1}") + @MethodSource({"stressTestCases", "protoStressTestCases"}) + void genericMutatorStressTest(AnnotatedType type, String mutatorTree, + Consumer<List<Object>> expectedInitValues, Consumer<List<Object>> expectedMutatedValues) + throws IOException { + validateAnnotationUsage(type); + SerializingMutator mutator = Mutators.newFactory().createOrThrow(type); + assertThat(mutator.toString()).isEqualTo(mutatorTree); + + // Even with a fallback to mutating map values when no new key can be constructed, the map + // {false: true, true: false} will not change its equality class when the fallback picks both + // values to mutate. + boolean mayPerformNoopMutations = + mutatorTree.contains("FixedValue(") || mutatorTree.contains("Map<Boolean,Boolean>"); + + PseudoRandom rng = anyPseudoRandom(); + + List<Object> initValues = new ArrayList<>(); + List<Object> mutatedValues = new ArrayList<>(); + for (int i = 0; i < NUM_INITS; i++) { + Object value = mutator.init(rng); + + // For proto messages, each float field with value -0.0f, and double field with value -0.0 + // will be converted to 0.0f and 0.0, respectively. + Object fixedValue = fixFloatingPointsForProtos(value); + testReadWriteRoundtrip(mutator, fixedValue); + testReadWriteExclusiveRoundtrip(mutator, fixedValue); + + initValues.add(mutator.detach(value)); + value = fixFloatingPointsForProtos(value); + + for (int mutation = 0; mutation < NUM_MUTATE_PER_INIT; mutation++) { + Object detachedOldValue = mutator.detach(value); + value = mutator.mutate(value, rng); + if (!mayPerformNoopMutations) { + if (value instanceof Double) { + assertThat(Double.compare((Double) value, (Double) detachedOldValue)).isNotEqualTo(0); + } else if (value instanceof Float) { + assertThat(Float.compare((Float) value, (Float) detachedOldValue)).isNotEqualTo(0); + } else { + assertThat(detachedOldValue).isNotEqualTo(value); + } + } + + mutatedValues.add(mutator.detach(value)); + + // For proto messages, each float field with value -0.0f, and double field with value -0.0 + // will be converted to 0.0f and 0.0, respectively. This is because the values -0f and 0f + // and their double counterparts are serialized as default values (0f, and 0.0), which is + // relevant for mutation and the round trip tests. This means that the protos with float or + // double fields that equal to negative zero, will start mutation from positive zeros, and + // cause the assertion above to fail from time to time. To avoid this, we convert all + // negative zeros to positive zeros for float and double proto fields. + value = fixFloatingPointsForProtos(value); + testReadWriteRoundtrip(mutator, fixedValue); + testReadWriteExclusiveRoundtrip(mutator, fixedValue); + } + } + + expectedInitValues.accept(initValues); + expectedMutatedValues.accept(mutatedValues); + } + + private static <T> void testReadWriteExclusiveRoundtrip(Serializer<T> serializer, T value) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializer.writeExclusive(value, out); + T newValue = serializer.readExclusive(new ByteArrayInputStream(out.toByteArray())); + assertThat(newValue).isEqualTo(value); + } + + private static <T> void testReadWriteRoundtrip(Serializer<T> serializer, T value) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializer.write(value, new DataOutputStream(out)); + T newValue = serializer.read( + new DataInputStream(extendWithZeros(new ByteArrayInputStream(out.toByteArray())))); + assertThat(newValue).isEqualTo(value); + } + + // Filter out floating point values -0.0f and -0.0 and replace them + // by 0.0f and 0.0 respectively. + // This is a workaround for a bug in the protobuf library that causes + // our "...RoundTrip" tests to fail for negative zero in floats and doubles. + private static <T> T fixFloatingPointsForProtos(T value) { + if (!(value instanceof Message)) { + return value; + } + Message.Builder builder = ((Message) value).toBuilder(); + walkFields(builder, oldValue -> { + if (Objects.equals(oldValue, -0.0)) { + return 0.0; + } else if (Objects.equals(oldValue, -0.0f)) { + return 0.0f; + } else { + return oldValue; + } + }); + return (T) builder.build(); + } + + private static void walkFields(Builder builder, Function<Object, Object> transform) { + for (FieldDescriptor field : builder.getDescriptorForType().getFields()) { + if (field.isRepeated()) { + int bound = builder.getRepeatedFieldCount(field); + for (int i = 0; i < bound; i++) { + if (field.getJavaType() == JavaType.MESSAGE) { + Builder repeatedFieldBuilder = + ((Message) builder.getRepeatedField(field, i)).toBuilder(); + walkFields(repeatedFieldBuilder, transform); + builder.setRepeatedField(field, i, repeatedFieldBuilder.build()); + } else { + builder.setRepeatedField(field, i, transform.apply(builder.getRepeatedField(field, i))); + } + } + } else if (field.getJavaType() == JavaType.MESSAGE) { + // Break up unbounded recursion. + if (!builder.hasField(field)) { + continue; + } + Builder fieldBuilder = ((Message) builder.getField(field)).toBuilder(); + walkFields(fieldBuilder, transform); + builder.setField(field, fieldBuilder.build()); + } else { + builder.setField(field, transform.apply(builder.getField(field))); + } + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel new file mode 100644 index 00000000..2e60b9d5 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/BUILD.bazel @@ -0,0 +1,17 @@ +load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite") + +java_test_suite( + name = "CollectionTests", + size = "small", + srcs = glob(["*.java"]), + env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"}, + runner = "junit5", + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang", + "//src/main/java/com/code_intelligence/jazzer/mutation/support", + "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java new file mode 100644 index 00000000..2fa0c1cf --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutationsTest.java @@ -0,0 +1,237 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.collection; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMap; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMutableList; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockInitializer; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockMutator; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +class ChunkMutationsTest { + @Test + void testDeleteRandomChunk() { + List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6).collect(toList()); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + ChunkMutations.deleteRandomChunk(list, 2, prng); + } + assertThat(list).containsExactly(1, 2, 3, 6).inOrder(); + } + + @Test + void testInsertRandomChunk() { + List<String> list = Stream.of("1", "2", "3", "4", "5", "6").collect(toList()); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + ChunkMutations.insertRandomChunk(list, 10, mockInitializer(() -> "7", String::new), prng); + } + assertThat(list).containsExactly("1", "2", "3", "7", "7", "4", "5", "6").inOrder(); + String firstNewValue = list.get(3); + String secondNewValue = list.get(4); + assertThat(firstNewValue).isEqualTo(secondNewValue); + // Verify that the individual new elements were detached. + assertThat(firstNewValue).isNotSameInstanceAs(secondNewValue); + } + + @Test + void testInsertRandomChunkSet() { + Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6).collect(toCollection(LinkedHashSet::new)); + + Queue<Integer> initReturnValues = + Stream.of(7, 7, 7, 8, 9, 9).collect(toCollection(ArrayDeque::new)); + boolean result; + try (MockPseudoRandom prng = mockPseudoRandom(3)) { + result = ChunkMutations.insertRandomChunk( + set, set::add, 10, mockInitializer(initReturnValues::remove, v -> v), prng); + } + assertThat(result).isTrue(); + assertThat(set).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9).inOrder(); + } + + @Test + void testInsertRandomChunkSet_largeChunk() { + Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6).collect(toCollection(LinkedHashSet::new)); + + Queue<Integer> initReturnValues = + IntStream.rangeClosed(1, 10000).boxed().collect(toCollection(ArrayDeque::new)); + boolean result; + try (MockPseudoRandom prng = mockPseudoRandom(9994)) { + result = ChunkMutations.insertRandomChunk( + set, set::add, 10000, mockInitializer(initReturnValues::remove, v -> v), prng); + } + assertThat(result).isTrue(); + assertThat(set) + .containsExactlyElementsIn(IntStream.rangeClosed(1, 10000).boxed().toArray()) + .inOrder(); + } + + @Test + void testInsertRandomChunkSet_failsToConstructDistinctValues() { + Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6).collect(toCollection(LinkedHashSet::new)); + + Queue<Integer> initReturnValues = + Stream.concat(Stream.of(7, 7, 7, 8), Stream.generate(() -> 7).limit(1000)) + .collect(toCollection(ArrayDeque::new)); + boolean result; + try (MockPseudoRandom prng = mockPseudoRandom(3)) { + result = ChunkMutations.insertRandomChunk( + set, set::add, 10, mockInitializer(initReturnValues::remove, v -> v), prng); + } + assertThat(result).isFalse(); + assertThat(set).containsExactly(1, 2, 3, 4, 5, 6, 7, 8).inOrder(); + } + + @Test + void testMutateChunk() { + List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6).collect(toList()); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + ChunkMutations.mutateRandomChunk(list, mockMutator(1, i -> 2 * i), prng); + } + assertThat(list).containsExactly(1, 2, 3, 8, 10, 6).inOrder(); + } + + @Test + void testMutateRandomValuesChunk() { + Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + ChunkMutations.mutateRandomValuesChunk(map, mockMutator(1, i -> 2 * i), prng); + } + assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 4, 80, 5, 100, 6, 60).inOrder(); + } + + @Test + void testMutateRandomKeysChunk() { + Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20, + asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60); + SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> { + List<Integer> newList = list.stream().map(i -> i + 1).collect(toList()); + list.clear(); + return newList; + }, ArrayList::new); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng); + assertThat(result).isTrue(); + } + assertThat(map) + .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30, + asMutableList(6), 60, asMutableList(7), 40, asMutableList(8), 50) + .inOrder(); + } + + @Test + void testMutateRandomKeysChunk_failsToConstructSomeDistinctKeys() { + Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20, + asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60); + SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> { + list.clear(); + List<Integer> newList = new ArrayList<>(); + newList.add(7); + return newList; + }, ArrayList::new); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng); + assertThat(result).isTrue(); + } + assertThat(map) + .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30, + asMutableList(5), 50, asMutableList(6), 60, asMutableList(7), 40) + .inOrder(); + } + + @Test + void testMutateRandomKeysChunk_failsToConstructAnyDistinctKeys() { + Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20, + asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60); + SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> { + list.clear(); + List<Integer> newList = new ArrayList<>(); + newList.add(1); + return newList; + }, ArrayList::new); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng); + assertThat(result).isFalse(); + } + assertThat(map) + .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30, + asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60) + .inOrder(); + } + + @Test + void testMutateRandomKeysChunk_nullKeyAndValue() { + Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20, + asMutableList(3), 30, asMutableList(4), null, null, 50, asMutableList(6), 60); + SerializingMutator<List<Integer>> keyMutator = mockMutator(null, list -> { + if (list != null) { + List<Integer> newList = list.stream().map(i -> i + 1).collect(toList()); + list.clear(); + return newList; + } else { + return asMutableList(10); + } + }, list -> list != null ? new ArrayList<>(list) : null); + + try (MockPseudoRandom prng = mockPseudoRandom(2, 3)) { + boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng); + assertThat(result).isTrue(); + } + assertThat(map) + .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30, + asMutableList(6), 60, asMutableList(5), null, asMutableList(10), 50) + .inOrder(); + } + + @Test + void testMutateRandomKeysChunk_mutateKeyToNull() { + Map<List<Integer>, Integer> map = asMap(asMutableList(1), 10, asMutableList(2), 20, + asMutableList(3), 30, asMutableList(4), 40, asMutableList(5), 50, asMutableList(6), 60); + SerializingMutator<List<Integer>> keyMutator = + mockMutator(null, list -> null, list -> list != null ? new ArrayList<>(list) : null); + + try (MockPseudoRandom prng = mockPseudoRandom(1, 3)) { + boolean result = ChunkMutations.mutateRandomKeysChunk(map, keyMutator, prng); + assertThat(result).isTrue(); + } + assertThat(map) + .containsExactly(asMutableList(1), 10, asMutableList(2), 20, asMutableList(3), 30, + asMutableList(5), 50, asMutableList(6), 60, null, 40) + .inOrder(); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java new file mode 100644 index 00000000..24299f48 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorTest.java @@ -0,0 +1,280 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.collection; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptyList; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithSize; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.MutatorFactory; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.lang.reflect.AnnotatedType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +public class ListMutatorTest { + public static final MutatorFactory FACTORY = + new ChainedMutatorFactory(LangMutators.newFactory(), CollectionMutators.newFactory()); + + private static SerializingMutator<@NotNull List<@NotNull Integer>> defaultListMutator() { + AnnotatedType type = new TypeHolder<@NotNull List<@NotNull Integer>>() {}.annotatedType(); + return (SerializingMutator<@NotNull List<@NotNull Integer>>) FACTORY.createOrThrow(type); + } + + @Test + void testInit() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + assertThat(mutator.toString()).isEqualTo("List<Integer>"); + + List<Integer> list; + try (MockPseudoRandom prng = mockPseudoRandom( + // targetSize + 1, + // elementMutator.init + 1)) { + list = mutator.init(prng); + } + assertThat(list).containsExactly(0); + } + + @Test + void testInitMaxSize() { + AnnotatedType type = + new TypeHolder<@NotNull @WithSize(min = 2, max = 3) List<@NotNull Integer>>(){} + .annotatedType(); + + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = + (SerializingMutator<@NotNull List<@NotNull Integer>>) FACTORY.createOrThrow(type); + + assertThat(mutator.toString()).isEqualTo("List<Integer>"); + List<Integer> list; + try (MockPseudoRandom prng = mockPseudoRandom(2, 4, 42L, 4, 43L)) { + list = mutator.init(prng); + } + + assertThat(list).containsExactly(42, 43).inOrder(); + } + + @Test + void testRemoveSingleElement() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + try (MockPseudoRandom prng = mockPseudoRandom( + // action + 0, + // number of elements to remove + 1, + // index to remove + 2)) { + list = mutator.mutate(list, prng); + } + assertThat(list).containsExactly(1, 2, 4, 5, 6, 7, 8, 9).inOrder(); + } + + @Test + void testRemoveChunk() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + try (MockPseudoRandom prng = mockPseudoRandom( + // action + 0, + // chunk size + 2, + // chunk offset + 3)) { + list = mutator.mutate(list, prng); + } + assertThat(list).containsExactly(1, 2, 3, 6, 7, 8, 9).inOrder(); + } + + @Test + void testAddSingleElement() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + try (MockPseudoRandom prng = mockPseudoRandom( + // action + 1, + // add single element, + 1, + // offset, + 9, + // Integral initImpl sentinel value + 4, + // value + 42L)) { + list = mutator.mutate(list, prng); + } + assertThat(list).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 42).inOrder(); + } + + @Test + void testAddChunk() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + try (MockPseudoRandom prng = mockPseudoRandom( + // action + 1, + // chunkSize + 2, + // chunkOffset + 3, + // Integral initImpl + 4, + // val + 42L)) { + list = mutator.mutate(list, prng); + } + assertThat(list).containsExactly(1, 2, 3, 42, 42, 4, 5, 6, 7, 8, 9).inOrder(); + } + + @Test + void testChangeSingleElement() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + try (MockPseudoRandom prng = mockPseudoRandom( + // action + 2, + // number of elements to mutate + 1, + // first index to mutate at + 2, + // mutation choice based on `IntegralMutatorFactory` + // 2 == closedRange + 2, + // value + 55L)) { + list = mutator.mutate(list, prng); + } + assertThat(list).containsExactly(1, 2, 55, 4, 5, 6, 7, 8, 9).inOrder(); + } + + @Test + void testChangeChunk() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); + try (MockPseudoRandom prng = mockPseudoRandom( + // action + 2, + // number of elements to mutate + 2, + // first index to mutate at + 5, + // mutation: 0 == bitflip + 0, + // shift constant + 13, + // and again + 0, 12)) { + list = mutator.mutate(list, prng); + } + assertThat(list).containsExactly(1, 2, 3, 4, 5, 8198, 4103, 8, 9, 10, 11).inOrder(); + } + + @Test + void testCrossOverEmptyLists() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + List<Integer> list = mutator.crossOver(emptyList(), emptyList(), prng); + assertThat(list).isEmpty(); + } + } + + @Test + void testCrossOverInsertChunk() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + List<Integer> otherList = + new ArrayList<>(Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)); + try (MockPseudoRandom prng = mockPseudoRandom( + // insert action + 0, + // chunk size + 3, + // fromPos + 2, + // toPos + 5)) { + list = mutator.crossOver(list, otherList, prng); + } + assertThat(list).containsExactly(0, 1, 2, 3, 4, 12, 13, 14, 5, 6, 7, 8, 9).inOrder(); + } + + @Test + void testCrossOverOverwriteChunk() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + List<Integer> otherList = + new ArrayList<>(Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)); + try (MockPseudoRandom prng = mockPseudoRandom( + // overwrite action + 1, + // chunk size + 3, + // fromPos + 2, + // toPos + 5)) { + list = mutator.crossOver(list, otherList, prng); + } + assertThat(list).containsExactly(0, 1, 2, 3, 4, 12, 13, 14, 8, 9).inOrder(); + } + + @Test + void testCrossOverCrossOverChunk() { + SerializingMutator<@NotNull List<@NotNull Integer>> mutator = defaultListMutator(); + + List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + List<Integer> otherList = + new ArrayList<>(Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)); + try (MockPseudoRandom prng = mockPseudoRandom( + // overwrite action + 2, + // chunk size + 3, + // fromPos + 2, + // toPos + 2, + // mean value in sub cross over + 0, + // mean value in sub cross over + 0, + // mean value in sub cross over + 0)) { + list = mutator.crossOver(list, otherList, prng); + } + assertThat(list).containsExactly(0, 1, 7, 8, 9, 5, 6, 7, 8, 9).inOrder(); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java new file mode 100644 index 00000000..4c2c14f9 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorTest.java @@ -0,0 +1,355 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.collection; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMap; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMutableList; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptyMap; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithSize; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.MutatorFactory; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.lang.reflect.AnnotatedType; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +class MapMutatorTest { + public static final MutatorFactory FACTORY = + new ChainedMutatorFactory(LangMutators.newFactory(), CollectionMutators.newFactory()); + + private static SerializingMutator<Map<Integer, Integer>> defaultTestMapMutator() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType(); + return (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type); + } + + @Test + void mapInitInsert() { + AnnotatedType type = + new TypeHolder<@NotNull @WithSize(max = 3) Map<@NotNull String, @NotNull String>>(){} + .annotatedType(); + SerializingMutator<Map<String, String>> mutator = + (SerializingMutator<Map<String, String>>) FACTORY.createOrThrow(type); + assertThat(mutator.toString()).isEqualTo("Map<String,String>"); + + // Initialize new map + Map<String, String> map; + try (MockPseudoRandom prng = mockPseudoRandom( + // Initial map size + 1, + // Key 1 size + 4, + // Key 1 value + "Key1".getBytes(), + // Value size + 6, + // Value value + "Value1".getBytes())) { + map = mutator.init(prng); + } + assertThat(map).containsExactly("Key1", "Value1"); + + // Add 2 new entries + try (MockPseudoRandom prng = mockPseudoRandom( + // grow chunk + 1, + // ChunkSize + 2, + // Key 2 size + 4, + // Key 2 value + "Key2".getBytes(), + // Value size + 6, + // Value value + "Value2".getBytes(), + // Key 3 size + 4, + // Key 3 value + "Key3".getBytes(), + // Value size + 6, + // Value value + "Value3".getBytes())) { + map = mutator.mutate(map, prng); + } + assertThat(map).containsExactly("Key1", "Value1", "Key2", "Value2", "Key3", "Value3").inOrder(); + } + + @Test + void mapDelete() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType(); + SerializingMutator<Map<Integer, Integer>> mutator = + (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type); + assertThat(mutator.toString()).isEqualTo("Map<Integer,Integer>"); + + Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60); + + try (MockPseudoRandom prng = mockPseudoRandom( + // delete chunk + 0, + // chunk size + 2, + // chunk position + 3)) { + map = mutator.mutate(map, prng); + } + assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 6, 60).inOrder(); + } + + @Test + void mapMutateValues() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType(); + SerializingMutator<Map<Integer, Integer>> mutator = + (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type); + assertThat(mutator.toString()).isEqualTo("Map<Integer,Integer>"); + + Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60); + + try (MockPseudoRandom prng = mockPseudoRandom( + // change chunk + 2, + // mutate values, + true, + // chunk size + 2, + // chunk position + 3, + // uniform pick + 2, + // random integer + 41L, + // uniform pick + 2, + // random integer + 51L)) { + map = mutator.mutate(map, prng); + } + assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 4, 41, 5, 51, 6, 60).inOrder(); + } + + @Test + void mapMutateKeys() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull Integer>>() {}.annotatedType(); + SerializingMutator<Map<Integer, Integer>> mutator = + (SerializingMutator<Map<Integer, Integer>>) FACTORY.createOrThrow(type); + assertThat(mutator.toString()).isEqualTo("Map<Integer,Integer>"); + + Map<Integer, Integer> map = asMap(1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60); + + try (MockPseudoRandom prng = mockPseudoRandom( + // change chunk + 2, + // mutate keys, + false, + // chunk size + 2, + // chunk position + 3, + // uniform pick + 2, + // integer + 7L, + // uniform pick + 2, + // random integer + 8L)) { + map = mutator.mutate(map, prng); + } + assertThat(map).containsExactly(1, 10, 2, 20, 3, 30, 6, 60, 7, 40, 8, 50).inOrder(); + } + + @Test + void mapMutateKeysFallbackToValues() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull Boolean, @NotNull Boolean>>() {}.annotatedType(); + SerializingMutator<Map<Boolean, Boolean>> mutator = + (SerializingMutator<Map<Boolean, Boolean>>) FACTORY.createOrThrow(type); + assertThat(mutator.toString()).isEqualTo("Map<Boolean,Boolean>"); + + // No new keys can be generated for this map. + Map<Boolean, Boolean> map = asMap(false, false, true, false); + + try (MockPseudoRandom prng = mockPseudoRandom( + // change chunk + 2, + // mutate keys, + false, + // chunk size + 1, + // chunk position + 0, + // chunk size for fallback to mutate values + 2, + // chunk position for fallback + 0)) { + map = mutator.mutate(map, prng); + } + assertThat(map).containsExactly(false, true, true, true).inOrder(); + } + + @Test + void testCrossOverEmptyMaps() { + SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull Integer>> mutator = + defaultTestMapMutator(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + Map<Integer, Integer> map = mutator.crossOver(emptyMap(), emptyMap(), prng); + assertThat(map).isEmpty(); + } + } + + @Test + void testCrossOverInsertChunk() { + SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull Integer>> mutator = + defaultTestMapMutator(); + + Map<Integer, Integer> map = asMap(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6); + Map<Integer, Integer> otherMap = asMap(1, 1, 2, 2, 3, 3, 40, 40, 50, 50, 60, 60); + + try (MockPseudoRandom prng = mockPseudoRandom( + // insert action + 0, + // chunk size + 3, + // from chunk offset, will skip first element of chunk as it is already present in map + 3, + // to chunk offset, unused + 0)) { + map = mutator.crossOver(map, otherMap, prng); + assertThat(map) + .containsExactly(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 40, 40, 50, 50, 60, 60) + .inOrder(); + } + } + + @Test + void testCrossOverOverwriteChunk() { + SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull Integer>> mutator = + defaultTestMapMutator(); + + Map<Integer, Integer> map = asMap(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6); + Map<Integer, Integer> otherMap = asMap(1, 1, 2, 2, 3, 3, 40, 40, 50, 50, 60, 60); + + try (MockPseudoRandom prng = mockPseudoRandom( + // overwrite action + 1, + // chunk size + 3, + // from chunk offset + 2, + // to chunk offset, will not change first element as values are equal + 2)) { + map = mutator.crossOver(map, otherMap, prng); + assertThat(map).containsExactly(1, 1, 2, 2, 3, 3, 4, 40, 5, 50, 6, 6).inOrder(); + } + } + + @Test + void testCrossOverCrossOverChunkKeys() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull List<@NotNull Integer>, @NotNull Integer>>() { + }.annotatedType(); + SerializingMutator<@NotNull Map<@NotNull List<@NotNull Integer>, @NotNull Integer>> mutator = + (SerializingMutator<@NotNull Map<@NotNull List<@NotNull Integer>, @NotNull Integer>>) + FACTORY.createOrThrow(type); + + Map<List<Integer>, Integer> map = asMap(asMutableList(1), 1, asMutableList(2), 2, + asMutableList(3), 3, asMutableList(4), 4, asMutableList(5), 5, asMutableList(6), 6); + Map<List<Integer>, Integer> otherMap = asMap(asMutableList(1), 1, asMutableList(2), 2, + asMutableList(3), 3, asMutableList(40), 4, asMutableList(50), 5, asMutableList(60), 6); + + try (MockPseudoRandom prng = mockPseudoRandom( + // cross over action + 2, + // keys + true, + // chunk size + 3, + // from chunk offset + 2, + // to chunk offset, + // first keys ("3") are equal and will be overwritten + 2, + // first key, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0 + 1, 1, 0, 0, + // second key, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0 + 1, 1, 0, 0, + // third key, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0 + 1, 1, 0, 0)) { + map = mutator.crossOver(map, otherMap, prng); + assertThat(map) + .containsExactly(asMutableList(1), 1, asMutableList(2), 2, asMutableList(6), 6, + // Overwritten keys after here + asMutableList(3), 3, asMutableList(40), 4, asMutableList(50), 5) + .inOrder(); + } + } + + @Test + void testCrossOverCrossOverChunkValues() { + AnnotatedType type = + new TypeHolder<@NotNull Map<@NotNull Integer, @NotNull List<@NotNull Integer>>>() { + }.annotatedType(); + SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull List<@NotNull Integer>>> mutator = + (SerializingMutator<@NotNull Map<@NotNull Integer, @NotNull List<@NotNull Integer>>>) + FACTORY.createOrThrow(type); + + Map<Integer, List<Integer>> map = asMap(1, asMutableList(1), 2, asMutableList(2), 3, + asMutableList(3), 4, asMutableList(4), 5, asMutableList(5), 6, asMutableList(6)); + Map<Integer, List<Integer>> otherMap = asMap(1, asMutableList(1), 2, asMutableList(2), 3, + asMutableList(30), 40, asMutableList(40), 50, asMutableList(50), 60, asMutableList(60)); + + try ( + MockPseudoRandom prng = mockPseudoRandom( + // cross over action + 2, + // values + false, + // chunk size + 3, + // from chunk offset + 2, + // to chunk offset, + 2, + // first value, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0 + 1, 1, 0, 0, + // second value, delegate to list cross over, overwrite 1 entry at offset 0 from offset + // 0 + 1, 1, 0, 0, + // third value, delegate to list cross over, overwrite 1 entry at offset 0 from offset 0 + 1, 1, 0, 0)) { + map = mutator.crossOver(map, otherMap, prng); + assertThat(map) + .containsExactly(1, asMutableList(1), 2, asMutableList(2), 3, asMutableList(30), 4, + asMutableList(40), 5, asMutableList(50), 6, asMutableList(6)) + .inOrder(); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel new file mode 100644 index 00000000..05e1d720 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel @@ -0,0 +1,18 @@ +load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite") + +java_test_suite( + name = "PrimitiveTests", + size = "small", + srcs = glob(["*.java"]), + env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"}, + runner = "junit5", + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer", + "//src/main/java/com/code_intelligence/jazzer/mutation/support", + "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support", + "@com_google_protobuf//java/core", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java new file mode 100644 index 00000000..3bf55bcf --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +class BooleanMutatorTest { + @Test + void testPrimitive() { + SerializingMutator<Boolean> mutator = LangMutators.newFactory().createOrThrow(boolean.class); + assertThat(mutator.toString()).isEqualTo("Boolean"); + + boolean bool; + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + bool = mutator.init(prng); + } + assertThat(bool).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isFalse(); + } + + @Test + void testBoxed() { + SerializingMutator<Boolean> mutator = + (SerializingMutator<Boolean>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Boolean>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("Boolean"); + + Boolean bool; + try (MockPseudoRandom prng = mockPseudoRandom(false)) { + bool = mutator.init(prng); + } + assertThat(bool).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isTrue(); + } + + @Test + void testCrossOver() { + SerializingMutator<Boolean> mutator = LangMutators.newFactory().createOrThrow(boolean.class); + try (MockPseudoRandom prng = mockPseudoRandom(true, false)) { + assertThat(mutator.crossOver(true, false, prng)).isTrue(); + assertThat(mutator.crossOver(true, false, prng)).isFalse(); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java new file mode 100644 index 00000000..1592b17d --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithLength; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) +public class ByteArrayMutatorTest { + /** + * Some tests may set {@link LibFuzzerMutator#MOCK_SIZE_KEY} which can interfere with other tests + * unless cleared. + */ + @AfterEach + void cleanMockSize() { + System.clearProperty(LibFuzzerMutator.MOCK_SIZE_KEY); + } + + @Test + void testBasicFunction() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("Nullable<byte[]>"); + + byte[] arr; + try (MockPseudoRandom prng = mockPseudoRandom(false, 5, new byte[] {1, 2, 3, 4, 5})) { + arr = mutator.init(prng); + } + assertThat(arr).isEqualTo(new byte[] {1, 2, 3, 4, 5}); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "10"); + try (MockPseudoRandom prng = mockPseudoRandom(false)) { + arr = mutator.mutate(arr, prng); + } + assertThat(arr).isEqualTo(new byte[] {2, 4, 6, 8, 10, 6, 7, 8, 9, 10}); + } + + @Test + void testMaxLength() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(max = 10)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + byte[] arr; + try (MockPseudoRandom prng = mockPseudoRandom(8, new byte[] {1, 2, 3, 4, 5, 6, 7, 8})) { + arr = mutator.init(prng); + } + assertThat(arr).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "11"); + try (MockPseudoRandom prng = mockPseudoRandom()) { + // the ByteArrayMutator will limit the maximum size of the data requested from libfuzzer to + // WithLength::max so setting the mock mutator to make it bigger will cause an exception + assertThrows(ArrayIndexOutOfBoundsException.class, () -> { mutator.mutate(arr, prng); }); + } + } + + @Test + void testMaxLengthInitClamp() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(max = 5)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = mockPseudoRandom(10)) { + // init will call closedRange(min, max) and the mock prng will assert that the given value + // above is between those values which we want to fail here to show that we're properly + // clamping the range + assertThrows(AssertionError.class, () -> { mutator.init(prng); }); + } + } + + @Test + void testMinLengthInitClamp() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(min = 5)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = mockPseudoRandom(3)) { + // init will call closedrange(min, max) and the mock prng will assert that the given value + // above is between those values which we want to fail here to show that we're properly + // clamping the range + assertThrows(AssertionError.class, () -> { mutator.init(prng); }); + } + } + + @Test + void testMinLength() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull @WithLength(min = 5)[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + byte[] arr; + try (MockPseudoRandom prng = mockPseudoRandom(10, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) { + arr = mutator.init(prng); + } + assertThat(arr).hasLength(10); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "3"); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + arr = mutator.mutate(arr, prng); + } + assertThat(arr).hasLength(5); + assertThat(arr).isEqualTo(new byte[] {2, 4, 6, 0, 0}); + } + + @Test + void testCrossOver() { + SerializingMutator<byte[]> mutator = + (SerializingMutator<byte[]>) LangMutators.newFactory().createOrThrow( + new TypeHolder<byte @NotNull[]>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + byte[] value = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + byte[] otherValue = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + + byte[] crossedOver; + try (MockPseudoRandom prng = mockPseudoRandom( + // intersect arrays + 0, + // out length + 8, + // copy 3 from first + 3, + // copy 1 from second + 1, + // copy 1 from first, + 1, + // copy 3 from second + 3)) { + crossedOver = mutator.crossOver(value, otherValue, prng); + assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 2, 10, 3, 11, 12, 13}); + } + + try (MockPseudoRandom prng = mockPseudoRandom( + // insert into action + 1, + // copy size + 3, + // from position + 5, + // to position + 2)) { + crossedOver = mutator.crossOver(value, otherValue, prng); + assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 15, 16, 17, 2, 3, 4, 5, 6, 7, 8, 9}); + } + + try (MockPseudoRandom prng = mockPseudoRandom( + // overwrite action + 2, + // to position + 3, + // copy size + 3, + // from position + 4)) { + crossedOver = mutator.crossOver(value, otherValue, prng); + assertThat(crossedOver).isEqualTo(new byte[] {0, 1, 2, 14, 15, 16, 6, 7, 8, 9}); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java new file mode 100644 index 00000000..d2c61397 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/EnumMutatorTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class EnumMutatorTest { + enum TestEnumOne { A } + + enum TestEnum { A, B, C } + + @Test + void testBoxed() { + SerializingMutator<TestEnum> mutator = + (SerializingMutator<TestEnum>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull TestEnum>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("Enum<TestEnum>"); + TestEnum cl; + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + cl = mutator.init(prng); + } + assertThat(cl).isEqualTo(TestEnum.A); + + try (MockPseudoRandom prng = mockPseudoRandom(1)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.B); + + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.A); + + try (MockPseudoRandom prng = mockPseudoRandom(2)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.C); + + try (MockPseudoRandom prng = mockPseudoRandom(1)) { + cl = mutator.mutate(cl, prng); + } + assertThat(cl).isEqualTo(TestEnum.B); + } + + @Test + void testEnumWithOneElementShouldThrow() { + assertThrows(IllegalArgumentException.class, () -> { + LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull TestEnumOne>() {}.annotatedType()); + }, "When trying to build mutators for Enum with one value, an Exception should be thrown."); + } + + @Test + void testEnumBasedOnInvalidInput() throws IOException { + SerializingMutator<TestEnum> mutator = + (SerializingMutator<TestEnum>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull TestEnum>() {}.annotatedType()); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + DataOutputStream os = new DataOutputStream(bo); + // Valid values + os.writeInt(0); + os.writeInt(1); + os.writeInt(2); + // Too high indices wrap around + os.writeInt(3); + // Abs. value is used to calculate the index + os.writeInt(-3); + + DataInputStream is = new DataInputStream(new ByteArrayInputStream(bo.toByteArray())); + assertThat(mutator.read(is)).isEqualTo(TestEnum.A); + assertThat(mutator.read(is)).isEqualTo(TestEnum.B); + assertThat(mutator.read(is)).isEqualTo(TestEnum.C); + assertThat(mutator.read(is)).isEqualTo(TestEnum.A); + assertThat(mutator.read(is)).isEqualTo(TestEnum.A); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java new file mode 100644 index 00000000..9c03b467 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorTest.java @@ -0,0 +1,785 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.mutator.lang.FloatingPointMutatorFactory.DoubleMutator; +import static com.code_intelligence.jazzer.mutation.mutator.lang.FloatingPointMutatorFactory.FloatMutator; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange; +import com.code_intelligence.jazzer.mutation.annotation.FloatInRange; +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class FloatingPointMutatorTest { + static final Float UNUSED_FLOAT = 0.0f; + static final Double UNUSED_DOUBLE = 0.0; + + static Stream<Arguments> floatForceInRangeCases() { + float NaN1 = Float.intBitsToFloat(0x7f800001); + float NaN2 = Float.intBitsToFloat(0x7f800002); + float NaN3 = Float.intBitsToFloat(0x7f800003); + assertThat(Float.isNaN(NaN1) && Float.isNaN(NaN2) && Float.isNaN(NaN3)).isTrue(); + + return Stream.of( + // value is already in range: it should stay in range + arguments(0.0f, 0.0f, 1.0f, true), arguments(0.0f, 1.0f, 1.0f, true), + arguments(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 1.0f, true), + arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, true), + arguments(Float.NaN, 0.0f, 1.0f, true), + arguments(1e30f, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(-1e30f, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(0.0f, Float.NEGATIVE_INFINITY, Float.MAX_VALUE, true), + arguments(0.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, true), + arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(Float.MAX_VALUE, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(-Float.MAX_VALUE, Float.MAX_VALUE - 3.4e30f, Float.MAX_VALUE, false), + arguments(Float.MAX_VALUE, -100.0f, Float.MAX_VALUE, true), + arguments(0.0f, -Float.MIN_VALUE, Float.MIN_VALUE, true), + // Special values and diff/ranges outside the range + arguments(Float.NEGATIVE_INFINITY, -1.0f, 1.0f, true), + arguments(Float.POSITIVE_INFINITY, -1.0f, 1.0f, true), + arguments(Float.POSITIVE_INFINITY, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.MAX_VALUE, true), + arguments(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, true), + arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, Float.MAX_VALUE, true), + arguments(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, Float.POSITIVE_INFINITY, true), + arguments(Float.NEGATIVE_INFINITY, Float.MAX_VALUE, Float.POSITIVE_INFINITY, true), + // Values outside the range + arguments(-2e30f, -100000.0f, 100000.0f, true), + arguments(2e30f, Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, true), + arguments(-1.0f, 0.0f, 1.0f, false), arguments(5.0f, 0.0f, 1.0f, false), + arguments(-Float.MAX_VALUE, -Float.MAX_VALUE, 100.0f, true), + // NaN not allowed + arguments(Float.NaN, 0.0f, 1.0f, false), + arguments(Float.NaN, -Float.MAX_VALUE, 1.0f, false), + arguments(Float.NaN, Float.NEGATIVE_INFINITY, 1.0f, false), + arguments(Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, false), + arguments(Float.NaN, 0f, Float.POSITIVE_INFINITY, false), + arguments(Float.NaN, 0f, Float.MAX_VALUE, false), + arguments(Float.NaN, -Float.MAX_VALUE, Float.MAX_VALUE, false), + arguments(Float.NaN, -Float.MIN_VALUE, 0.0f, false), + arguments(Float.NaN, -Float.MIN_VALUE, Float.MIN_VALUE, false), + arguments(Float.NaN, 0.0f, Float.MIN_VALUE, false), + // There are many possible NaN values, test a few of them that are different from Float.NaN + // (0x7fc00000) + arguments(NaN1, 0.0f, 1.0f, false), arguments(NaN2, 0.0f, 1.0f, false), + arguments(NaN3, 0.0f, 1.0f, false)); + } + + static Stream<Arguments> doubleForceInRangeCases() { + double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L); + double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L); + double NaN3 = Double.longBitsToDouble(0x7ff0000000000003L); + double NaNdeadbeef = Double.longBitsToDouble(0x7ff00000deadbeefL); + assertThat( + Double.isNaN(NaN1) && Double.isNaN(NaN2) && Double.isNaN(NaN3) && Double.isNaN(NaNdeadbeef)) + .isTrue(); + + return Stream.of( + // value is already in range: it should stay in range + arguments(0.0, 0.0, 1.0, true), arguments(0.0, 1.0, 1.0, true), + arguments(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0, true), + arguments( + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, true), + arguments(Double.NaN, 0.0, 1.0, true), + arguments(1e30, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(-1e30, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(0.0, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, true), + arguments(0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, true), + arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(-Double.MAX_VALUE, Double.MAX_VALUE - 3.4e30, Double.MAX_VALUE, false), + arguments(Double.MAX_VALUE, -100.0, Double.MAX_VALUE, true), + arguments(0.0, -Double.MIN_VALUE, Double.MIN_VALUE, true), + // Special values and diff/ranges outside the range + arguments(Double.NEGATIVE_INFINITY, -1.0, 1.0, true), + arguments(Double.POSITIVE_INFINITY, -1.0, 1.0, true), + arguments(Double.POSITIVE_INFINITY, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, true), + arguments(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, true), + arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, Double.MAX_VALUE, true), + arguments(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, Double.POSITIVE_INFINITY, true), + arguments(Double.NEGATIVE_INFINITY, Double.MAX_VALUE, Double.POSITIVE_INFINITY, true), + // Values outside the range + arguments(-2e30, -100000.0, 100000.0, true), + arguments(2e30, Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, true), + arguments(-1.0, 0.0, 1.0, false), arguments(5.0, 0.0, 1.0, false), + arguments(-Double.MAX_VALUE, -Double.MAX_VALUE, 100.0, true), + arguments( + Math.nextDown(Double.MAX_VALUE), -Double.MAX_VALUE * 0.5, Double.MAX_VALUE * 0.5, true), + arguments(Math.nextDown(Double.MAX_VALUE), -Double.MAX_VALUE * 0.5, + Math.nextUp(Double.MAX_VALUE * 0.5), true), + // NaN not allowed + arguments(Double.NaN, 0.0, 1.0, false), + arguments(Double.NaN, -Double.MAX_VALUE, 1.0, false), + arguments(Double.NaN, Double.NEGATIVE_INFINITY, 1.0, false), + arguments(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false), + arguments(Double.NaN, 0, Double.POSITIVE_INFINITY, false), + arguments(Double.NaN, 0, Double.MAX_VALUE, false), + arguments(Double.NaN, -Double.MAX_VALUE, Double.MAX_VALUE, false), + arguments(Double.NaN, -Double.MIN_VALUE, 0.0, false), + arguments(Double.NaN, -Double.MIN_VALUE, Double.MIN_VALUE, false), + arguments(Double.NaN, 0.0, Double.MIN_VALUE, false), + // There are many possible NaN values, test a few of them that are different from Double.NaN + // (0x7ff8000000000000L) + arguments(NaN1, 0.0, 1.0, false), arguments(NaN2, 0.0, 1.0, false), + arguments(NaN3, 0.0, 1.0, false), + arguments(NaNdeadbeef, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false)); + } + + @ParameterizedTest + @MethodSource("floatForceInRangeCases") + void testFloatForceInRange(float value, float minValue, float maxValue, boolean allowNaN) { + float inRange = FloatMutator.forceInRange(value, minValue, maxValue, allowNaN); + + // inRange can become NaN only if allowNaN is true and value was NaN already + if (Float.isNaN(inRange)) { + if (allowNaN) { + assertThat(Float.isNaN(value)).isTrue(); + return; // NaN is not in range of anything + } else { + throw new AssertionError("NaN is not allowed but was returned"); + } + } + + assertThat(inRange).isAtLeast(minValue); + assertThat(inRange).isAtMost(maxValue); + if (value >= minValue && value <= maxValue) { + assertThat(inRange).isEqualTo(value); + } + } + + @ParameterizedTest + @MethodSource("doubleForceInRangeCases") + void testDoubleForceInRange(double value, double minValue, double maxValue, boolean allowNaN) { + double inRange = DoubleMutator.forceInRange(value, minValue, maxValue, allowNaN); + + // inRange can become NaN only if allowNaN is true and value was NaN already + if (Double.isNaN(inRange)) { + if (allowNaN) { + assertThat(Double.isNaN(value)).isTrue(); + return; // NaN is not in range of anything + } else { + throw new AssertionError("NaN is not allowed but was returned"); + } + } + + assertThat(inRange).isAtLeast(minValue); + assertThat(inRange).isAtMost(maxValue); + if (value >= minValue && value <= maxValue) { + assertThat(inRange).isEqualTo(value); + } + } + + // Tests of mutators' special values after initialization use mocked PRNG to test one special + // value after another. This counter enables adding new special values and testcases for them + // without modifying all the other test cases. + static Supplier<Integer> makeCounter() { + return new Supplier<Integer>() { + private int counter = 0; + + @Override + public Integer get() { + return counter++; + } + }; + } + + static Stream<Arguments> floatInitCasesFullRange() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMinusOneToOne() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = -1.0f, max = 1.0f) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -1.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), 1.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMinusMinToMin() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange( + min = -Float.MIN_VALUE, max = Float.MIN_VALUE) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMaxToInf() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange( + min = Float.MAX_VALUE, max = Float.POSITIVE_INFINITY) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesMinusInfToMinusMax() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange( + min = Float.NEGATIVE_INFINITY, max = -Float.MAX_VALUE) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatInitCasesFullRangeWithoutNaN() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = Float.NEGATIVE_INFINITY, + max = Float.POSITIVE_INFINITY, allowNaN = true) Float>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Float.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0f, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Float.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_FLOAT, false)); + } + + @ParameterizedTest + @MethodSource({"floatInitCasesMinusOneToOne", "floatInitCasesFullRange", + "floatInitCasesMinusMinToMin", "floatInitCasesMaxToInf", "floatInitCasesMinusInfToMinusMax", + "floatInitCasesFullRangeWithoutNaN"}) + void + testFloatInitCases(SerializingMutator<Float> mutator, Stream<Object> prngValues, float expected, + boolean specialValueIndexExists) { + assertThat(mutator.toString()).isEqualTo("Float"); + if (specialValueIndexExists) { + Float n = null; + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + n = mutator.init(prng); + } + assertThat(n).isEqualTo(expected); + } else { // should throw + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + mutator.init(prng); + } + }); + } + } + + static Stream<Arguments> floatMutateSanityChecksFullRangeCases() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = Float.NEGATIVE_INFINITY, + max = Float.POSITIVE_INFINITY, allowNaN = true) Float>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flips + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 0), 1.4e-45f, true), + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 30), 2.0f, true), + arguments(mutator, Stream.of(false, 2f), Stream.of(false, 0, 31), -2.0f, true), + arguments(mutator, Stream.of(false, -2f), Stream.of(false, 0, 22), -3.0f, true), + // mutateExponent + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111100), 0.125f, true), + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111110), 0.5f, true), + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 1, 0B01111111), 1.0f, true), + // mutateMantissa + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 2, 0, 100), 1.4e-43f, true), + arguments(mutator, Stream.of(false, Float.intBitsToFloat(1)), Stream.of(false, 2, 0, -1), 0, + true), + // mutateWithMathematicalFn + arguments( + mutator, Stream.of(false, 10.1f), Stream.of(false, 3, 4), 11f, true), // Math::ceil + arguments( + mutator, Stream.of(false, 1000f), Stream.of(false, 3, 11), 3f, true), // Math::log10 + // skip libfuzzer + // random in range + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 5, 10f), 10f, true), + // unknown mutation case exception + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 6), UNUSED_FLOAT, false)); + } + + static Stream<Arguments> floatMutateLimitedRangeCases() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = -1f, max = 1f, allowNaN = false) Float>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flip; forceInRange(); result equals previous value; adjust value + arguments(mutator, Stream.of(false, 0f), Stream.of(false, 0, 30, true), + 0f - Float.MIN_VALUE, true), + arguments(mutator, Stream.of(false, 1f), Stream.of(false, 0, 30), Math.nextDown(1f), true), + arguments(mutator, Stream.of(false, -1f), Stream.of(false, 0, 30), Math.nextUp(-1f), true), + // NaN after mutateWithMathematicalFn with NaN not allowed; forceInRange will return + // (min+max)/2 + arguments(mutator, Stream.of(false, -1f), Stream.of(false, 3, 16), 0.0f, true)); + } + + static Stream<Arguments> floatMutateLimitedRangeCasesWithNaN() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @FloatInRange(min = -1f, max = 1f, allowNaN = true) Float>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // NaN after mutation and forceInRange(); all good! + arguments(mutator, Stream.of(false, -1f), Stream.of(false, 3, 16), Float.NaN, true), + // NaN (with a set bit #8) after init, mutation, and forceInRange(); need to change NaN to + // something else + arguments(mutator, Stream.of(true, 6), Stream.of(false, 0, 8, 0.3f), 0.3f, true)); + } + + @ParameterizedTest + @MethodSource({"floatMutateSanityChecksFullRangeCases", "floatMutateLimitedRangeCases", + "floatMutateLimitedRangeCasesWithNaN"}) + void + testFloatMutateCases(SerializingMutator<Float> mutator, Stream<Object> initValues, + Stream<Object> mutationValues, float expected, boolean knownMutatorSwitchCase) { + assertThat(mutator.toString()).isEqualTo("Float"); + Float n; + + // Init + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(initValues.toArray())) { + n = mutator.init(prng); + } + + // Mutate + if (knownMutatorSwitchCase) { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + n = mutator.mutate(n, prng); + } + assertThat(n).isEqualTo(expected); + + if (!((FloatMutator) mutator).allowNaN) { + assertThat(n).isNotEqualTo(Float.NaN); + } + + if (!Float.isNaN(n)) { + assertThat(n).isAtLeast(((FloatMutator) mutator).minValue); + assertThat(n).isAtMost(((FloatMutator) mutator).maxValue); + } + } else { // Invalid mutation because a case is not handled + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + mutator.mutate(UNUSED_FLOAT, prng); + } + }); + } + } + + @Test + void testFloatCrossOverMean() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = + mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { + assertThat(mutator.crossOver(0f, 0f, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(-0f, 0f, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(0f, 2f, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(1f, 2f, prng)).isWithin(1e-10f).of(1.5f); + assertThat(mutator.crossOver(1f, 3f, prng)).isWithin(1e-10f).of(2f); + assertThat(mutator.crossOver(Float.MAX_VALUE, Float.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(Float.MAX_VALUE); + + assertThat(mutator.crossOver(0f, -2f, prng)).isWithin(1e-10f).of(-1.0f); + assertThat(mutator.crossOver(-1f, -2f, prng)).isWithin(1e-10f).of(-1.5f); + assertThat(mutator.crossOver(-1f, -3f, prng)).isWithin(1e-10f).of(-2f); + assertThat(mutator.crossOver(-Float.MAX_VALUE, -Float.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(-Float.MAX_VALUE); + + assertThat(mutator.crossOver(-100f, 200f, prng)).isWithin(1e-10f).of(50.0f); + assertThat(mutator.crossOver(100f, -200f, prng)).isWithin(1e-10f).of(-50f); + assertThat(mutator.crossOver(-Float.MAX_VALUE, Float.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(0f); + + assertThat(mutator.crossOver(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, prng)).isNaN(); + assertThat(mutator.crossOver(Float.POSITIVE_INFINITY, 0f, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(0f, Float.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(Float.NEGATIVE_INFINITY, 0f, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(0f, Float.NEGATIVE_INFINITY, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(Float.NaN, 0f, prng)).isNaN(); + assertThat(mutator.crossOver(0f, Float.NaN, prng)).isNaN(); + } + } + + @Test + void testFloatCrossOverExponent() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(1, 1, 1)) { + assertThat(mutator.crossOver(2.0f, -1.5f, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(2.0f, Float.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(-1.5f, Float.NEGATIVE_INFINITY, prng)).isNaN(); + } + } + + @Test + void testFloatCrossOverMantissa() { + SerializingMutator<Float> mutator = + (SerializingMutator<Float>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Float>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(2, 2, 2)) { + assertThat(mutator.crossOver(4.0f, 3.5f, prng)).isWithin(1e-10f).of(7.0f); + assertThat(mutator.crossOver(Float.POSITIVE_INFINITY, 3.0f, prng)).isNaN(); + assertThat(mutator.crossOver(Float.MAX_VALUE, 0.0f, prng)).isWithin(1e-10f).of(1.7014118e38f); + } + } + + static Stream<Arguments> doubleInitCasesFullRange() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMinusOneToOne() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = -1.0, max = 1.0) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -1.0, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), 1.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMinusMinToMin() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange( + min = -Double.MIN_VALUE, max = Double.MIN_VALUE) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMaxToInf() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange( + min = Double.MAX_VALUE, max = Double.POSITIVE_INFINITY) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesMinusInfToMinusMax() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange( + min = Double.NEGATIVE_INFINITY, max = -Double.MAX_VALUE) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleInitCasesFullRangeWithoutNaN() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = Double.NEGATIVE_INFINITY, + max = Double.POSITIVE_INFINITY, allowNaN = true) Double>() { + }.annotatedType()); + Supplier<Integer> ctr = makeCounter(); + return Stream.of(arguments(mutator, Stream.of(true, ctr.get()), Double.NEGATIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), -0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), 0.0, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.MAX_VALUE, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.POSITIVE_INFINITY, true), + arguments(mutator, Stream.of(true, ctr.get()), Double.NaN, true), + arguments(mutator, Stream.of(true, ctr.get()), UNUSED_DOUBLE, false)); + } + + @ParameterizedTest + @MethodSource({"doubleInitCasesMinusOneToOne", "doubleInitCasesFullRange", + "doubleInitCasesMinusMinToMin", "doubleInitCasesMaxToInf", + "doubleInitCasesMinusInfToMinusMax", "doubleInitCasesFullRangeWithoutNaN"}) + void + testDoubleInitCases(SerializingMutator<Double> mutator, Stream<Object> prngValues, + double expected, boolean knownSwitchCase) { + assertThat(mutator.toString()).isEqualTo("Double"); + if (knownSwitchCase) { + Double n = null; + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + n = mutator.init(prng); + } + assertThat(n).isEqualTo(expected); + } else { + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(prngValues.toArray())) { + mutator.init(prng); + } + }); + } + } + + static Stream<Arguments> doubleMutateSanityChecksFullRangeCases() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = Double.NEGATIVE_INFINITY, + max = Double.POSITIVE_INFINITY, allowNaN = true) Double>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flips + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 0), Double.MIN_VALUE, true), + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 62), 2.0, true), + arguments(mutator, Stream.of(false, 2.0), Stream.of(false, 0, 63), -2.0, true), + arguments(mutator, Stream.of(false, -2.0), Stream.of(false, 0, 51), -3.0, true), + // mutateExponent + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111100), 0.125, true), + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111110), 0.5, true), + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 1, 0B1111111111), 1.0, true), + // mutateMantissa + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 2, 0, 100L), 4.94e-322, true), + arguments(mutator, Stream.of(false, Double.longBitsToDouble(1)), + Stream.of(false, 2, 0, -1L), 0, true), + // mutateWithMathematicalFn + arguments(mutator, Stream.of(false, 10.1), Stream.of(false, 3, 4), 11, true), // Math::ceil + arguments( + mutator, Stream.of(false, 1000.0), Stream.of(false, 3, 11), 3, true), // Math::log10 + // skip libfuzzer + // random in range + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 5, 10.0), 10, true), + // unknown mutation case exception + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 6), UNUSED_DOUBLE, false)); + } + + static Stream<Arguments> doubleMutateLimitedRangeCases() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = -1, max = 1, allowNaN = false) Double>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // Bit flip; forceInRange(); result equals previous value; adjust value + arguments(mutator, Stream.of(false, 0.0), Stream.of(false, 0, 62, true), + 0.0 - Double.MIN_VALUE, true), + arguments( + mutator, Stream.of(false, 1.0), Stream.of(false, 0, 62), Math.nextDown(1.0), true), + arguments( + mutator, Stream.of(false, -1.0), Stream.of(false, 0, 62), Math.nextUp(-1.0), true), + // NaN after mutateWithMathematicalFn: sqrt(-1.0); NaN not allowed; forceInRange will return + // (min+max)/2 + arguments(mutator, Stream.of(false, -1.0), Stream.of(false, 3, 16), 0.0, true)); + } + + static Stream<Arguments> doubleMutateLimitedRangeCasesWithNaN() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @DoubleInRange(min = -1, max = 1, allowNaN = true) Double>() { + }.annotatedType()); + // Init value can be set to desired one by giving this to the init method: (false, <desired + // value>) + return Stream.of( + // NaN after mutation and forceInRange(); all good! + arguments(mutator, Stream.of(false, -1.0), Stream.of(false, 3, 16), Double.NaN, true), + // NaN (with a set bit #8) after init, mutation, and forceInRange(); need to change NaN to + // something else + arguments(mutator, Stream.of(true, 6), Stream.of(false, 0, 8, 0.3), 0.3, true)); + } + + @ParameterizedTest + @MethodSource({"doubleMutateSanityChecksFullRangeCases", "doubleMutateLimitedRangeCases", + "doubleMutateLimitedRangeCasesWithNaN"}) + void + testDoubleMutateCases(SerializingMutator<Double> mutator, Stream<Object> initValues, + Stream<Object> mutationValues, double expected, boolean knownSwitchCase) { + assertThat(mutator.toString()).isEqualTo("Double"); + Double n; + + // Init + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(initValues.toArray())) { + n = mutator.init(prng); + } + + // Mutate + if (knownSwitchCase) { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + n = mutator.mutate(n, prng); + } + assertThat(n).isEqualTo(expected); + + if (!((DoubleMutator) mutator).allowNaN) { + assertThat(n).isNotEqualTo(Double.NaN); + } + + if (!Double.isNaN(n)) { + assertThat(n).isAtLeast(((DoubleMutator) mutator).minValue); + assertThat(n).isAtMost(((DoubleMutator) mutator).maxValue); + } + } else { // Invalid mutation because a case is not handled + assertThrows(AssertionError.class, () -> { + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(mutationValues.toArray())) { + mutator.mutate(UNUSED_DOUBLE, prng); + } + }); + } + } + + @Test + void testDoubleCrossOverMean() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = + mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { + assertThat(mutator.crossOver(0.0, 0.0, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(-0.0, 0.0, prng)).isWithin(0).of(0f); + assertThat(mutator.crossOver(0.0, 2.0, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(1.0, 2.0, prng)).isWithin(1e-10f).of(1.5f); + assertThat(mutator.crossOver(1.0, 3.0, prng)).isWithin(1e-10f).of(2f); + assertThat(mutator.crossOver(Double.MAX_VALUE, Double.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(Double.MAX_VALUE); + + assertThat(mutator.crossOver(0.0, -2.0, prng)).isWithin(1e-10f).of(-1.0f); + assertThat(mutator.crossOver(-1.0, -2.0, prng)).isWithin(1e-10f).of(-1.5f); + assertThat(mutator.crossOver(-1.0, -3.0, prng)).isWithin(1e-10f).of(-2f); + assertThat(mutator.crossOver(-Double.MAX_VALUE, -Double.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(-Double.MAX_VALUE); + + assertThat(mutator.crossOver(-100.0, 200.0, prng)).isWithin(1e-10f).of(50.0f); + assertThat(mutator.crossOver(100.0, -200.0, prng)).isWithin(1e-10f).of(-50f); + assertThat(mutator.crossOver(-Double.MAX_VALUE, Double.MAX_VALUE, prng)) + .isWithin(1e-10f) + .of(0f); + + assertThat(mutator.crossOver(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, prng)) + .isNaN(); + assertThat(mutator.crossOver(Double.POSITIVE_INFINITY, 0.0, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(0.0, Double.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(Double.NEGATIVE_INFINITY, 0.0, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(0.0, Double.NEGATIVE_INFINITY, prng)).isNegativeInfinity(); + assertThat(mutator.crossOver(Double.NaN, 0.0, prng)).isNaN(); + assertThat(mutator.crossOver(0.0, Double.NaN, prng)).isNaN(); + } + } + + @Test + void testDoubleCrossOverExponent() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(1, 1, 1)) { + assertThat(mutator.crossOver(2.0, -1.5, prng)).isWithin(1e-10f).of(1.0f); + assertThat(mutator.crossOver(2.0, Double.POSITIVE_INFINITY, prng)).isPositiveInfinity(); + assertThat(mutator.crossOver(-1.5, Double.NEGATIVE_INFINITY, prng)).isNaN(); + } + } + + @Test + void testDoubleCrossOverMantissa() { + SerializingMutator<Double> mutator = + (SerializingMutator<Double>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Double>() {}.annotatedType()); + try (TestSupport.MockPseudoRandom prng = mockPseudoRandom(2, 2, 2)) { + assertThat(mutator.crossOver(4.0, 3.5, prng)).isWithin(1e-10f).of(7.0f); + assertThat(mutator.crossOver(Double.POSITIVE_INFINITY, 3.0, prng)).isNaN(); + assertThat(mutator.crossOver(Double.MAX_VALUE, 0.0, prng)) + .isWithin(1e-10f) + .of(8.98846567431158e307); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java new file mode 100644 index 00000000..dda8cfed --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.mutator.lang.IntegralMutatorFactory.AbstractIntegralMutator.forceInRange; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@SuppressWarnings("unchecked") +class IntegralMutatorTest { + static Stream<Arguments> forceInRangeCases() { + return Stream.of(arguments(0, 0, 1), arguments(5, 0, 1), arguments(-5, -10, -1), + arguments(-200, -10, -1), arguments(10, 0, 3), arguments(-5, 0, 3), arguments(10, -7, 7), + arguments(Long.MIN_VALUE, Long.MIN_VALUE, Long.MAX_VALUE), + arguments(Long.MIN_VALUE, Long.MIN_VALUE, 100), + arguments(Long.MIN_VALUE + 100, Long.MIN_VALUE, 100), + arguments(Long.MAX_VALUE, -100, Long.MAX_VALUE), + arguments(Long.MAX_VALUE - 100, -100, Long.MAX_VALUE), + arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE), + arguments(Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MAX_VALUE), + arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE - 1), + arguments(Long.MIN_VALUE, Long.MAX_VALUE - 5, Long.MAX_VALUE), + arguments(Long.MAX_VALUE, Long.MIN_VALUE, Long.MIN_VALUE + 5)); + } + + @ParameterizedTest + @MethodSource("forceInRangeCases") + void testForceInRange(long value, long minValue, long maxValue) { + long inRange = forceInRange(value, minValue, maxValue); + assertThat(inRange).isAtLeast(minValue); + assertThat(inRange).isAtMost(maxValue); + if (value >= minValue && value <= maxValue) { + assertThat(inRange).isEqualTo(value); + } + } + + @Test + void testCrossOver() { + SerializingMutator<Long> mutator = + (SerializingMutator<Long>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull Long>() {}.annotatedType()); + // cross over mean values + try (MockPseudoRandom prng = mockPseudoRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { + assertThat(mutator.crossOver(0L, 0L, prng)).isEqualTo(0); + assertThat(mutator.crossOver(0L, 2L, prng)).isEqualTo(1); + assertThat(mutator.crossOver(1L, 2L, prng)).isEqualTo(1); + assertThat(mutator.crossOver(1L, 3L, prng)).isEqualTo(2); + assertThat(mutator.crossOver(Long.MAX_VALUE, Long.MAX_VALUE, prng)).isEqualTo(Long.MAX_VALUE); + + assertThat(mutator.crossOver(0L, -2L, prng)).isEqualTo(-1); + assertThat(mutator.crossOver(-1L, -2L, prng)).isEqualTo(-1); + assertThat(mutator.crossOver(-1L, -3L, prng)).isEqualTo(-2); + assertThat(mutator.crossOver(Long.MIN_VALUE, Long.MIN_VALUE, prng)).isEqualTo(Long.MIN_VALUE); + + assertThat(mutator.crossOver(-100L, 200L, prng)).isEqualTo(50); + assertThat(mutator.crossOver(100L, -200L, prng)).isEqualTo(-50); + assertThat(mutator.crossOver(Long.MIN_VALUE, Long.MAX_VALUE, prng)).isEqualTo(0); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java new file mode 100644 index 00000000..bc9a65b2 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.lang.reflect.AnnotatedType; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +class NullableMutatorTest { + @Test + void testNullable() { + SerializingMutator<Boolean> mutator = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()) + .createOrThrow(Boolean.class); + assertThat(mutator.toString()).isEqualTo("Nullable<Boolean>"); + + Boolean bool; + try (MockPseudoRandom prng = mockPseudoRandom(/* init to null */ true)) { + bool = mutator.init(prng); + } + assertThat(bool).isNull(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* init for non-null Boolean */ false)) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* mutate to non-null Boolean */ false)) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* mutate to null */ true)) { + bool = mutator.mutate(bool, prng); + } + assertThat(bool).isNull(); + } + + @Test + void testNotNull() { + ChainedMutatorFactory factory = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()); + AnnotatedType notNullBoolean = new TypeHolder<@NotNull Boolean>() {}.annotatedType(); + SerializingMutator<Boolean> mutator = + (SerializingMutator<Boolean>) factory.createOrThrow(notNullBoolean); + assertThat(mutator.toString()).isEqualTo("Boolean"); + } + + @Test + void testPrimitive() { + ChainedMutatorFactory factory = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()); + SerializingMutator<Boolean> mutator = factory.createOrThrow(boolean.class); + assertThat(mutator.toString()).isEqualTo("Boolean"); + } + + @Test + void testCrossOver() { + SerializingMutator<Boolean> mutator = + new ChainedMutatorFactory(new NullableMutatorFactory(), new BooleanMutatorFactory()) + .createOrThrow(Boolean.class); + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + Boolean valueCrossedOver = mutator.crossOver(Boolean.TRUE, Boolean.TRUE, prng); + assertThat(valueCrossedOver).isNotNull(); + } + try (MockPseudoRandom prng = mockPseudoRandom()) { + Boolean bothNull = mutator.crossOver(null, null, prng); + assertThat(bothNull).isNull(); + } + try (MockPseudoRandom prng = mockPseudoRandom(false)) { + Boolean oneNotNull = mutator.crossOver(null, Boolean.TRUE, prng); + assertThat(oneNotNull).isNotNull(); + } + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + Boolean nullFrequency = mutator.crossOver(null, Boolean.TRUE, prng); + assertThat(nullFrequency).isNull(); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java new file mode 100644 index 00000000..23060359 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.lang; + +import static com.code_intelligence.jazzer.mutation.mutator.lang.StringMutatorFactory.fixUpAscii; +import static com.code_intelligence.jazzer.mutation.mutator.lang.StringMutatorFactory.fixUpUtf8; +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator; +import com.code_intelligence.jazzer.mutation.support.RandomSupport; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import com.google.protobuf.ByteString; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.SplittableRandom; +import org.junit.jupiter.api.*; + +class StringMutatorTest { + /** + * Some tests may set {@link LibFuzzerMutator#MOCK_SIZE_KEY} which can interfere with other tests + * unless cleared. + */ + @AfterEach + void cleanMockSize() { + System.clearProperty(LibFuzzerMutator.MOCK_SIZE_KEY); + } + + @RepeatedTest(10) + void testFixAscii_randomInputFixed(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixAscii_randomInputFixed".hashCode() * info.getCurrentRepetition()); + + for (int length = 0; length < 1000; length++) { + byte[] randomBytes = generateRandomBytes(random, length); + byte[] copy = Arrays.copyOf(randomBytes, randomBytes.length); + fixUpAscii(copy); + if (isValidAscii(randomBytes)) { + assertThat(copy).isEqualTo(randomBytes); + } else { + assertThat(isValidAscii(copy)).isTrue(); + } + } + } + + @RepeatedTest(10) + void testFixAscii_validInputNotChanged(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixAscii_validInputNotChanged".hashCode() * info.getCurrentRepetition()); + + for (int codePoints = 0; codePoints < 1000; codePoints++) { + byte[] validAscii = generateValidAsciiBytes(random, codePoints); + byte[] copy = Arrays.copyOf(validAscii, validAscii.length); + fixUpAscii(copy); + assertThat(copy).isEqualTo(validAscii); + } + } + + @RepeatedTest(20) + void testFixUtf8_randomInputFixed(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixUtf8_randomInputFixed".hashCode() * info.getCurrentRepetition()); + + for (int length = 0; length < 1000; length++) { + byte[] randomBytes = generateRandomBytes(random, length); + byte[] copy = Arrays.copyOf(randomBytes, randomBytes.length); + fixUpUtf8(copy); + if (isValidUtf8(randomBytes)) { + assertThat(copy).isEqualTo(randomBytes); + } else { + assertThat(isValidUtf8(copy)).isTrue(); + } + } + } + + @RepeatedTest(20) + void testFixUtf8_validInputNotChanged(RepetitionInfo info) { + SplittableRandom random = new SplittableRandom( + (long) "testFixUtf8_validInputNotChanged".hashCode() * info.getCurrentRepetition()); + + for (int codePoints = 0; codePoints < 1000; codePoints++) { + byte[] validUtf8 = generateValidUtf8Bytes(random, codePoints); + byte[] copy = Arrays.copyOf(validUtf8, validUtf8.length); + fixUpUtf8(copy); + assertThat(copy).isEqualTo(validUtf8); + } + } + + @Test + void testMinLengthInit() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + try (MockPseudoRandom prng = mockPseudoRandom(5)) { + // mock prng should throw an assert error when given a lower value than min + Assertions.assertThrows(AssertionError.class, () -> { String s = mutator.init(prng); }); + } + } + + @Test + void testMaxLengthInit() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(max = 50) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + try (MockPseudoRandom prng = mockPseudoRandom(60)) { + // mock prng should throw an assert error when given a value higher than max + Assertions.assertThrows(AssertionError.class, () -> { String s = mutator.init(prng); }); + } + } + + @Test + void testMinLengthMutate() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + String s; + try (MockPseudoRandom prng = mockPseudoRandom(10, "foobarbazf".getBytes())) { + s = mutator.init(prng); + } + assertThat(s).isEqualTo("foobarbazf"); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "5"); + try (MockPseudoRandom prng = mockPseudoRandom()) { + s = mutator.mutate(s, prng); + } + assertThat(s).isEqualTo("gqrff\0\0\0\0\0"); + } + + @Test + void testMaxLengthMutate() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(max = 15) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + String s; + try (MockPseudoRandom prng = mockPseudoRandom(10, "foobarbazf".getBytes())) { + s = mutator.init(prng); + } + assertThat(s).isEqualTo("foobarbazf"); + + System.setProperty(LibFuzzerMutator.MOCK_SIZE_KEY, "20"); + try (MockPseudoRandom prng = mockPseudoRandom()) { + Assertions.assertThrows( + ArrayIndexOutOfBoundsException.class, () -> { String s2 = mutator.mutate(s, prng); }); + } + } + + @Test + void testMultibyteCharacters() { + SerializingMutator<String> mutator = + (SerializingMutator<String>) LangMutators.newFactory().createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(min = 10) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + + String s; + try ( + MockPseudoRandom prng = mockPseudoRandom(10, "foobarÖÖ".getBytes(StandardCharsets.UTF_8))) { + s = mutator.init(prng); + } + assertThat(s).hasLength(8); + assertThat(s).isEqualTo("foobarÖÖ"); + } + + private static boolean isValidUtf8(byte[] data) { + return ByteString.copyFrom(data).isValidUtf8(); + } + + private static boolean isValidAscii(byte[] data) { + for (byte b : data) { + if ((b & 0xFF) > 0x7F) { + return false; + } + } + return true; + } + + private static byte[] generateRandomBytes(SplittableRandom random, int length) { + byte[] bytes = new byte[length]; + RandomSupport.nextBytes(random, bytes); + return bytes; + } + + private static byte[] generateValidAsciiBytes(SplittableRandom random, int length) { + return random.ints(0, 0x7F) + .limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString() + .getBytes(StandardCharsets.UTF_8); + } + + private static byte[] generateValidUtf8Bytes(SplittableRandom random, long codePoints) { + return random.ints(0, Character.MAX_CODE_POINT + 1) + .filter(code -> code < Character.MIN_SURROGATE || code > Character.MAX_SURROGATE) + .limit(codePoints) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString() + .getBytes(StandardCharsets.UTF_8); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel new file mode 100644 index 00000000..bf8b551d --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel @@ -0,0 +1,60 @@ +load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite") + +proto_library( + name = "proto3_proto", + srcs = ["proto3.proto"], + deps = [ + "@com_google_protobuf//:any_proto", + ], +) + +java_proto_library( + name = "proto3_java_proto", + testonly = True, + visibility = ["//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__"], + deps = [":proto3_proto"], +) + +proto_library( + name = "proto2_proto", + srcs = ["proto2.proto"], +) + +java_proto_library( + name = "proto2_java_proto", + testonly = True, + visibility = [ + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__", + "//tests:__pkg__", + ], + deps = [":proto2_proto"], +) + +cc_proto_library( + name = "proto2_cc_proto", + testonly = True, + visibility = [ + "//tests:__pkg__", + ], + deps = [":proto2_proto"], +) + +java_test_suite( + name = "ProtoTests", + size = "small", + srcs = glob(["*.java"]), + runner = "junit5", + deps = [ + ":proto2_java_proto", + ":proto3_java_proto", + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto", + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto", + "//src/main/java/com/code_intelligence/jazzer/mutation/support", + "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support", + "@com_google_protobuf//java/core", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java new file mode 100644 index 00000000..7722a6ad --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdaptersTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.proto; + +import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.makeMutableRepeatedFieldView; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedIntegralField3; +import com.google.protobuf.Descriptors.FieldDescriptor; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +class BuilderAdaptersTest { + @Test + void testMakeMutableRepeatedFieldView() { + RepeatedIntegralField3.Builder builder = RepeatedIntegralField3.newBuilder(); + FieldDescriptor someField = builder.getDescriptorForType().findFieldByNumber(1); + assertThat(someField).isNotNull(); + + List<Integer> view = makeMutableRepeatedFieldView(builder, someField); + assertThat(builder.build().getSomeFieldList()).isEmpty(); + + assertThat(view.add(1)).isTrue(); + assertThat(view.get(0)).isEqualTo(1); + assertThat(view).hasSize(1); + assertThat(builder.build().getSomeFieldList()).containsExactly(1).inOrder(); + assertThrows(IndexOutOfBoundsException.class, () -> view.get(1)); + + assertThat(view.add(2)).isTrue(); + assertThat(view.add(3)).isTrue(); + assertThat(view).hasSize(3); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 2, 3).inOrder(); + assertThrows(IndexOutOfBoundsException.class, () -> view.get(3)); + + assertThat(view.set(1, 4)).isEqualTo(2); + assertThat(view).hasSize(3); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 4, 3).inOrder(); + + assertThat(view.set(1, 5)).isEqualTo(4); + assertThat(view).hasSize(3); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 5, 3).inOrder(); + + assertThat(view.remove(1)).isEqualTo(5); + assertThat(view).hasSize(2); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 3).inOrder(); + + assertThrows(IndexOutOfBoundsException.class, () -> view.remove(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> view.remove(2)); + + assertThat(view.addAll(1, Collections.emptyList())).isFalse(); + assertThat(view).hasSize(2); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 3).inOrder(); + + assertThat(view.addAll(1, Arrays.asList(6, 7, 8))).isTrue(); + assertThat(view).hasSize(5); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 6, 7, 8, 3).inOrder(); + + view.subList(2, 4).clear(); + assertThat(view).hasSize(3); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 6, 3).inOrder(); + + assertThat(view.addAll(3, Arrays.asList(9, 10))).isTrue(); + assertThat(view).hasSize(5); + assertThat(builder.build().getSomeFieldList()).containsExactly(1, 6, 3, 9, 10).inOrder(); + + view.clear(); + assertThat(view).hasSize(0); + assertThat(builder.build().getSomeFieldList()).isEmpty(); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java new file mode 100644 index 00000000..9492bcec --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto2Test.java @@ -0,0 +1,447 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.proto; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.InPlaceMutator; +import com.code_intelligence.jazzer.mutation.api.MutatorFactory; +import com.code_intelligence.jazzer.mutation.mutator.collection.CollectionMutators; +import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import com.code_intelligence.jazzer.protobuf.Proto2.MessageField2; +import com.code_intelligence.jazzer.protobuf.Proto2.OneOfField2; +import com.code_intelligence.jazzer.protobuf.Proto2.PrimitiveField2; +import com.code_intelligence.jazzer.protobuf.Proto2.RecursiveMessageField2; +import com.code_intelligence.jazzer.protobuf.Proto2.RepeatedMessageField2; +import com.code_intelligence.jazzer.protobuf.Proto2.RepeatedOptionalMessageField2; +import com.code_intelligence.jazzer.protobuf.Proto2.RepeatedPrimitiveField2; +import com.code_intelligence.jazzer.protobuf.Proto2.RequiredPrimitiveField2; +import org.junit.jupiter.api.Test; + +class BuilderMutatorProto2Test { + private static final MutatorFactory FACTORY = new ChainedMutatorFactory( + LangMutators.newFactory(), CollectionMutators.newFactory(), ProtoMutators.newFactory()); + + @Test + void testPrimitiveField() { + InPlaceMutator<PrimitiveField2.Builder> mutator = + (InPlaceMutator<PrimitiveField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<PrimitiveField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<Boolean>}"); + + PrimitiveField2.Builder builder = PrimitiveField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // present + false, + // boolean + false)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isTrue(); + assertThat(builder.getSomeField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // present + false, + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isTrue(); + assertThat(builder.getSomeField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate as non-null Boolean + false)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isTrue(); + assertThat(builder.getSomeField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // not present + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isFalse(); + assertThat(builder.getSomeField()).isFalse(); + } + + @Test + void testRequiredPrimitiveField() { + InPlaceMutator<RequiredPrimitiveField2.Builder> mutator = + (InPlaceMutator<RequiredPrimitiveField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RequiredPrimitiveField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Boolean}"); + + RequiredPrimitiveField2.Builder builder = RequiredPrimitiveField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* mutate first field */ 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isFalse(); + } + + @Test + void testRepeatedPrimitiveField() { + InPlaceMutator<RepeatedPrimitiveField2.Builder> mutator = + (InPlaceMutator<RepeatedPrimitiveField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RepeatedPrimitiveField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder via List<Boolean>}"); + + RepeatedPrimitiveField2.Builder builder = RepeatedPrimitiveField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // list size 1 + 1, + // boolean, + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).containsExactly(true).inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by adding an entry + 1, + // add a single element + 1, + // add the element at the end + 1, + // value to add + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).containsExactly(true, true).inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by changing an entry + 2, + // mutate a single element + 1, + // mutate the second element + 1)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).containsExactly(true, false).inOrder(); + } + + @Test + void testMessageField() { + InPlaceMutator<MessageField2.Builder> mutator = + (InPlaceMutator<MessageField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<MessageField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<{Builder.Boolean} -> Message>}"); + + MessageField2.Builder builder = MessageField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // init submessage + false, + // boolean submessage field + true)) { + mutator.initInPlace(builder, prng); + } + + assertThat(builder.getMessageField()) + .isEqualTo(RequiredPrimitiveField2.newBuilder().setSomeField(true).build()); + assertThat(builder.hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate submessage as non-null + false, + // mutate first field + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageField()) + .isEqualTo(RequiredPrimitiveField2.newBuilder().setSomeField(false).build()); + assertThat(builder.hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate submessage to null + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.hasMessageField()).isFalse(); + } + + @Test + void testRepeatedOptionalMessageField() { + InPlaceMutator<RepeatedOptionalMessageField2.Builder> mutator = + (InPlaceMutator<RepeatedOptionalMessageField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RepeatedOptionalMessageField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo("{Builder via List<{Builder.Nullable<Boolean>} -> Message>}"); + + RepeatedOptionalMessageField2.Builder builder = RepeatedOptionalMessageField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // list size 1 + 1, + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList().toString()).isEqualTo("[]"); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by adding an entry + 1, + // add a single element + 1, + // add the element at the end + 1, + // Nullable mutator init + false, + // duplicate entry + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList().size()).isEqualTo(2); + } + + @Test + void testRepeatedRequiredMessageField() { + InPlaceMutator<RepeatedMessageField2.Builder> mutator = + (InPlaceMutator<RepeatedMessageField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RepeatedMessageField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder via List<{Builder.Boolean} -> Message>}"); + + RepeatedMessageField2.Builder builder = RepeatedMessageField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // list size 1 + 1, + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList()) + .containsExactly(RequiredPrimitiveField2.newBuilder().setSomeField(true).build()) + .inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by adding an entry + 1, + // add a single element + 1, + // add the element at the end + 1, + // value to add + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList()) + .containsExactly(RequiredPrimitiveField2.newBuilder().setSomeField(true).build(), + RequiredPrimitiveField2.newBuilder().setSomeField(true).build()) + .inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // change an entry + 2, + // mutate a single element + 1, + // mutate the second element, + 1, + // mutate the first element + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList()) + .containsExactly(RequiredPrimitiveField2.newBuilder().setSomeField(true).build(), + RequiredPrimitiveField2.newBuilder().setSomeField(false).build()) + .inOrder(); + } + + @Test + void testRecursiveMessageField() { + InPlaceMutator<RecursiveMessageField2.Builder> mutator = + (InPlaceMutator<RecursiveMessageField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RecursiveMessageField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo("{Builder.Boolean, WithoutInit(Builder.Nullable<(cycle) -> Message>)}"); + RecursiveMessageField2.Builder builder = RecursiveMessageField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + + assertThat(builder.build()) + .isEqualTo(RecursiveMessageField2.newBuilder().setSomeField(true).build()); + assertThat(builder.hasMessageField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate message field (causes init to non-null) + 1, + // bool field in message field + false)) { + mutator.mutateInPlace(builder, prng); + } + // Nested message field *is* set explicitly and implicitly equal to the default + // instance. + assertThat(builder.build()) + .isEqualTo(RecursiveMessageField2.newBuilder() + .setSomeField(true) + .setMessageField(RecursiveMessageField2.newBuilder().setSomeField(false)) + .build()); + assertThat(builder.hasMessageField()).isTrue(); + assertThat(builder.getMessageField().hasMessageField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate message field + 1, + // message field as not null + false, + // mutate message field + 1, + // nested boolean, + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(RecursiveMessageField2.newBuilder() + .setSomeField(true) + .setMessageField( + RecursiveMessageField2.newBuilder().setSomeField(false).setMessageField( + RecursiveMessageField2.newBuilder().setSomeField(true))) + .build()); + assertThat(builder.hasMessageField()).isTrue(); + assertThat(builder.getMessageField().hasMessageField()).isTrue(); + assertThat(builder.getMessageField().getMessageField().hasMessageField()).isFalse(); + } + + @Test + void testOneOfField2() { + InPlaceMutator<OneOfField2.Builder> mutator = + (InPlaceMutator<OneOfField2.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<OneOfField2.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo( + "{Builder.Boolean, Builder.Nullable<Boolean>, Builder.Nullable<Boolean> | Builder.Nullable<{Builder.Boolean} -> Message>}"); + OneOfField2.Builder builder = OneOfField2.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // other_field + true, + // yet_another_field + true, + // oneof: first field + 0, + // bool_field present + false, + // bool_field + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField2.newBuilder().setOtherField(true).setBoolField(true).build()); + assertThat(builder.build().hasBoolField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // preserve oneof state + false, + // mutate bool_field as non-null + false)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField2.newBuilder().setOtherField(true).setBoolField(false).build()); + assertThat(builder.build().hasBoolField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // switch oneof state + true, + // new oneof state + 1, + // init message_field as non-null + false, + // init some_field as true + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField2.newBuilder() + .setOtherField(true) + .setMessageField(RequiredPrimitiveField2.newBuilder().setSomeField(true)) + .build()); + assertThat(builder.build().hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // preserve oneof state + false, + // mutate message_field as non-null + false, + // mutate some_field + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField2.newBuilder() + .setOtherField(true) + .setMessageField(RequiredPrimitiveField2.newBuilder().setSomeField(false)) + .build()); + assertThat(builder.build().hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // preserve oneof state + false, + // mutate message_field to null (and thus oneof state to indeterminate) + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()).isEqualTo(OneOfField2.newBuilder().setOtherField(true).build()); + assertThat(builder.build().hasMessageField()).isFalse(); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java new file mode 100644 index 00000000..ff298540 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorProto3Test.java @@ -0,0 +1,603 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.proto; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.InPlaceMutator; +import com.code_intelligence.jazzer.mutation.api.MutatorFactory; +import com.code_intelligence.jazzer.mutation.mutator.collection.CollectionMutators; +import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import com.code_intelligence.jazzer.protobuf.Proto3.AnyField3; +import com.code_intelligence.jazzer.protobuf.Proto3.AnyField3.Builder; +import com.code_intelligence.jazzer.protobuf.Proto3.EmptyMessage3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumField3.TestEnum; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldOne3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldOne3.TestEnumOne; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldOutside3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3; +import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3.TestEnumRepeated; +import com.code_intelligence.jazzer.protobuf.Proto3.MessageField3; +import com.code_intelligence.jazzer.protobuf.Proto3.OneOfField3; +import com.code_intelligence.jazzer.protobuf.Proto3.OptionalPrimitiveField3; +import com.code_intelligence.jazzer.protobuf.Proto3.PrimitiveField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RecursiveMessageField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedMessageField3; +import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedPrimitiveField3; +import com.code_intelligence.jazzer.protobuf.Proto3.TestEnumOutside3; +import com.google.protobuf.InvalidProtocolBufferException; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class BuilderMutatorProto3Test { + private static final MutatorFactory FACTORY = new ChainedMutatorFactory( + LangMutators.newFactory(), CollectionMutators.newFactory(), ProtoMutators.newFactory()); + + @Test + void testPrimitiveField() { + InPlaceMutator<PrimitiveField3.Builder> mutator = + (InPlaceMutator<PrimitiveField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<PrimitiveField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Boolean}"); + + PrimitiveField3.Builder builder = PrimitiveField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom(true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom(/* mutate first field */ 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isFalse(); + } + + @Test + void testEnumField() { + InPlaceMutator<EnumField3.Builder> mutator = + (InPlaceMutator<EnumField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<EnumField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Enum<TestEnum>}"); + EnumField3.Builder builder = EnumField3.newBuilder(); + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isEqualTo(TestEnum.VAL1); + try (MockPseudoRandom prng = mockPseudoRandom(0, 1)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isEqualTo(TestEnum.VAL2); + } + + @Test + void testEnumFieldOutside() { + InPlaceMutator<EnumFieldOutside3.Builder> mutator = + (InPlaceMutator<EnumFieldOutside3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<EnumFieldOutside3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Enum<TestEnumOutside3>}"); + EnumFieldOutside3.Builder builder = EnumFieldOutside3.newBuilder(); + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isEqualTo(TestEnumOutside3.VAL1); + try (MockPseudoRandom prng = mockPseudoRandom(0, 2)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isEqualTo(TestEnumOutside3.VAL3); + } + + @Test + void testEnumFieldWithOneValue() { + InPlaceMutator<EnumFieldOne3.Builder> mutator = + (InPlaceMutator<EnumFieldOne3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<EnumFieldOne3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.FixedValue(ONE)}"); + EnumFieldOne3.Builder builder = EnumFieldOne3.newBuilder(); + try (MockPseudoRandom prng = mockPseudoRandom()) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isEqualTo(TestEnumOne.ONE); + try (MockPseudoRandom prng = mockPseudoRandom(0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeField()).isEqualTo(TestEnumOne.ONE); + } + + @Test + void testRepeatedEnumField() { + InPlaceMutator<EnumFieldRepeated3.Builder> mutator = + (InPlaceMutator<EnumFieldRepeated3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<EnumFieldRepeated3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder via List<Enum<TestEnumRepeated>>}"); + EnumFieldRepeated3.Builder builder = EnumFieldRepeated3.newBuilder(); + try (MockPseudoRandom prng = mockPseudoRandom( + // list size + 1, // Only possible start value + // enum values + 2)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).isEqualTo(Arrays.asList(TestEnumRepeated.VAL2)); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // change an entry + 2, + // mutate a single element + 1, + // mutate to first enum field + 0, + // mutate to first enum value + 1)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).isEqualTo(Arrays.asList(TestEnumRepeated.VAL1)); + } + + @Test + void testOptionalPrimitiveField() { + InPlaceMutator<OptionalPrimitiveField3.Builder> mutator = + (InPlaceMutator<OptionalPrimitiveField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<OptionalPrimitiveField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<Boolean>}"); + + OptionalPrimitiveField3.Builder builder = OptionalPrimitiveField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // present + false, + // boolean + false)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isTrue(); + assertThat(builder.getSomeField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // present + false, + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isTrue(); + assertThat(builder.getSomeField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate as non-null Boolean + false)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isTrue(); + assertThat(builder.getSomeField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // not present + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.hasSomeField()).isFalse(); + assertThat(builder.getSomeField()).isFalse(); + } + + @Test + void testRepeatedPrimitiveField() { + InPlaceMutator<RepeatedPrimitiveField3.Builder> mutator = + (InPlaceMutator<RepeatedPrimitiveField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RepeatedPrimitiveField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder via List<Boolean>}"); + + RepeatedPrimitiveField3.Builder builder = RepeatedPrimitiveField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // list size 1 + 1, + // boolean, + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).containsExactly(true).inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by adding an entry + 1, + // add a single element + 1, + // add the element at the end + 1, + // value to add + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).containsExactly(true, true).inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by changing an entry + 2, + // mutate a single element + 1, + // mutate the second element + 1)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getSomeFieldList()).containsExactly(true, false).inOrder(); + } + + @Test + void testMessageField() { + InPlaceMutator<MessageField3.Builder> mutator = + (InPlaceMutator<MessageField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<MessageField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder.Nullable<{Builder.Boolean} -> Message>}"); + + MessageField3.Builder builder = MessageField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // init submessage + false, + // boolean submessage field + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getMessageField()) + .isEqualTo(PrimitiveField3.newBuilder().setSomeField(true).build()); + assertThat(builder.hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate submessage as non-null + false, + // mutate first field + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageField()) + .isEqualTo(PrimitiveField3.newBuilder().setSomeField(false).build()); + assertThat(builder.hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate submessage to null + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.hasMessageField()).isFalse(); + } + + @Test + void testRepeatedMessageField() { + InPlaceMutator<RepeatedMessageField3.Builder> mutator = + (InPlaceMutator<RepeatedMessageField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RepeatedMessageField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{Builder via List<{Builder.Boolean} -> Message>}"); + + RepeatedMessageField3.Builder builder = RepeatedMessageField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // list size 1 + 1, + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList()) + .containsExactly(PrimitiveField3.newBuilder().setSomeField(true).build()) + .inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // mutate the list itself by adding an entry + 1, + // add a single element + 1, + // add the element at the end + 1, + // value to add + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList()) + .containsExactly(PrimitiveField3.newBuilder().setSomeField(true).build(), + PrimitiveField3.newBuilder().setSomeField(true).build()) + .inOrder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate first field + 0, + // change an entry + 2, + // mutate a single element + 1, + // mutate the second element, + 1, + // mutate the first element + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.getMessageFieldList()) + .containsExactly(PrimitiveField3.newBuilder().setSomeField(true).build(), + PrimitiveField3.newBuilder().setSomeField(false).build()) + .inOrder(); + } + + @Test + void testRecursiveMessageField() { + InPlaceMutator<RecursiveMessageField3.Builder> mutator = + (InPlaceMutator<RecursiveMessageField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<RecursiveMessageField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo("{Builder.Boolean, WithoutInit(Builder.Nullable<(cycle) -> Message>)}"); + RecursiveMessageField3.Builder builder = RecursiveMessageField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // boolean + true)) { + mutator.initInPlace(builder, prng); + } + + assertThat(builder.build()) + .isEqualTo(RecursiveMessageField3.newBuilder().setSomeField(true).build()); + assertThat(builder.hasMessageField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate message field (causes init to non-null) + 1, + // bool field in message field + false)) { + mutator.mutateInPlace(builder, prng); + } + // Nested message field *is* set explicitly and implicitly equal to the default + // instance. + assertThat(builder.build()) + .isEqualTo(RecursiveMessageField3.newBuilder() + .setSomeField(true) + .setMessageField(RecursiveMessageField3.newBuilder().setSomeField(false)) + .build()); + assertThat(builder.hasMessageField()).isTrue(); + assertThat(builder.getMessageField().hasMessageField()).isFalse(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate message field + 1, + // message field as not null + false, + // mutate message field + 1, + // nested boolean, + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(RecursiveMessageField3.newBuilder() + .setSomeField(true) + .setMessageField( + RecursiveMessageField3.newBuilder().setSomeField(false).setMessageField( + RecursiveMessageField3.newBuilder().setSomeField(true))) + .build()); + assertThat(builder.hasMessageField()).isTrue(); + assertThat(builder.getMessageField().hasMessageField()).isTrue(); + assertThat(builder.getMessageField().getMessageField().hasMessageField()).isFalse(); + } + + @Test + void testOneOfField3() { + InPlaceMutator<OneOfField3.Builder> mutator = + (InPlaceMutator<OneOfField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<OneOfField3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo( + "{Builder.Boolean, Builder.Boolean, Builder.Nullable<Boolean> | Builder.Nullable<{Builder.Boolean} -> Message>}"); + OneOfField3.Builder builder = OneOfField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // other_field + true, + // yet_another_field + true, + // oneof: first field + 0, + // bool_field present + false, + // bool_field + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField3.newBuilder() + .setOtherField(true) + .setBoolField(true) + .setYetAnotherField(true) + .build()); + assertThat(builder.build().hasBoolField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // preserve oneof state + false, + // mutate bool_field as non-null + false)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField3.newBuilder() + .setOtherField(true) + .setBoolField(false) + .setYetAnotherField(true) + .build()); + assertThat(builder.build().hasBoolField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // switch oneof state + true, + // new oneof state + 1, + // init message_field as non-null + false, + // init some_field as true + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField3.newBuilder() + .setOtherField(true) + .setMessageField(PrimitiveField3.newBuilder().setSomeField(true)) + .setYetAnotherField(true) + .build()); + assertThat(builder.build().hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // preserve oneof state + false, + // mutate message_field as non-null + false, + // mutate some_field + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField3.newBuilder() + .setOtherField(true) + .setMessageField(PrimitiveField3.newBuilder().setSomeField(false)) + .setYetAnotherField(true) + .build()); + assertThat(builder.build().hasMessageField()).isTrue(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate oneof + 2, + // preserve oneof state + false, + // mutate message_field to null (and thus oneof state to indeterminate) + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()) + .isEqualTo(OneOfField3.newBuilder().setOtherField(true).setYetAnotherField(true).build()); + assertThat(builder.build().hasMessageField()).isFalse(); + } + + @Test + void testEmptyMessage3() { + InPlaceMutator<EmptyMessage3.Builder> mutator = + (InPlaceMutator<EmptyMessage3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<EmptyMessage3.@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("{<empty>}"); + EmptyMessage3.Builder builder = EmptyMessage3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.build()).isEqualTo(EmptyMessage3.getDefaultInstance()); + + try (MockPseudoRandom prng = mockPseudoRandom()) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build()).isEqualTo(EmptyMessage3.getDefaultInstance()); + } + + @Test + void testAnyField3() throws InvalidProtocolBufferException { + InPlaceMutator<AnyField3.Builder> mutator = + (InPlaceMutator<AnyField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<@NotNull @AnySource( + {PrimitiveField3.class, MessageField3.class}) Builder>() { + }.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo( + "{Builder.Nullable<Builder.{Builder.Boolean} -> Message | Builder.{Builder.Nullable<(cycle) -> Message>} -> Message -> Message>}"); + AnyField3.Builder builder = AnyField3.newBuilder(); + + try (MockPseudoRandom prng = mockPseudoRandom( + // initialize message field + false, + // PrimitiveField3 + 0, + // boolean field + true)) { + mutator.initInPlace(builder, prng); + } + assertThat(builder.build().getSomeField().unpack(PrimitiveField3.class)) + .isEqualTo(PrimitiveField3.newBuilder().setSomeField(true).build()); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate Any field + 0, + // keep non-null message field + false, + // keep Any state, + false, + // mutate boolean field + 0)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build().getSomeField().unpack(PrimitiveField3.class)) + .isEqualTo(PrimitiveField3.newBuilder().setSomeField(false).build()); + + try (MockPseudoRandom prng = mockPseudoRandom( + // mutate Any field + 0, + // keep non-null message field + false, + // switch Any state + true, + // new Any state + 1, + // non-null message + false, + // boolean field, + true)) { + mutator.mutateInPlace(builder, prng); + } + assertThat(builder.build().getSomeField().unpack(MessageField3.class)) + .isEqualTo(MessageField3.newBuilder() + .setMessageField(PrimitiveField3.newBuilder().setSomeField(true)) + .build()); + } + + @Test + void testAnyField3WithoutAnySourceDoesNotCrash() throws InvalidProtocolBufferException { + InPlaceMutator<AnyField3.Builder> mutator = + (InPlaceMutator<AnyField3.Builder>) FACTORY.createInPlaceOrThrow( + new TypeHolder<@NotNull Builder>() {}.annotatedType()); + assertThat(mutator.toString()) + .isEqualTo("{Builder.Nullable<{Builder.String, Builder.byte[] -> ByteString} -> Message>}"); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java new file mode 100644 index 00000000..b804c7fb --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/MessageMutatorTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.mutator.proto; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.MutatorFactory; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.collection.CollectionMutators; +import com.code_intelligence.jazzer.mutation.mutator.lang.LangMutators; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import com.code_intelligence.jazzer.protobuf.Proto2.ExtendedMessage2; +import com.code_intelligence.jazzer.protobuf.Proto2.ExtendedSubmessage2; +import com.code_intelligence.jazzer.protobuf.Proto2.OriginalMessage2; +import com.code_intelligence.jazzer.protobuf.Proto2.OriginalSubmessage2; +import com.code_intelligence.jazzer.protobuf.Proto3.PrimitiveField3; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class MessageMutatorTest { + private static final MutatorFactory FACTORY = new ChainedMutatorFactory( + LangMutators.newFactory(), CollectionMutators.newFactory(), ProtoMutators.newFactory()); + + @Test + void testSimpleMessage() { + SerializingMutator<PrimitiveField3> mutator = FACTORY.createOrThrow(PrimitiveField3.class); + + PrimitiveField3 msg; + + try (MockPseudoRandom prng = mockPseudoRandom( + // not null + false, + // boolean + false)) { + msg = mutator.init(prng); + assertThat(msg).isEqualTo(PrimitiveField3.getDefaultInstance()); + } + + try (MockPseudoRandom prng = mockPseudoRandom( + // not null, + false, + // mutate first field + 0)) { + msg = mutator.mutate(msg, prng); + assertThat(msg).isNotEqualTo(PrimitiveField3.getDefaultInstance()); + } + } + + @Test + void testIncompleteMessageWithRequiredFields() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OriginalMessage2.newBuilder() + .setMessageField(OriginalSubmessage2.newBuilder().setNumericField(42).build()) + .setBoolField(true) + .build() + .writeTo(out); + byte[] bytes = out.toByteArray(); + + SerializingMutator<ExtendedMessage2> mutator = + (SerializingMutator<ExtendedMessage2>) FACTORY.createOrThrow( + new TypeHolder<@NotNull ExtendedMessage2>() {}.annotatedType()); + ExtendedMessage2 extendedMessage = mutator.readExclusive(new ByteArrayInputStream(bytes)); + assertThat(extendedMessage) + .isEqualTo(ExtendedMessage2.newBuilder() + .setMessageField( + ExtendedSubmessage2.newBuilder().setNumericField(42).setMessageField( + OriginalSubmessage2.newBuilder().setNumericField(0).build())) + .setBoolField(true) + .setFloatField(0) + .build()); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto new file mode 100644 index 00000000..ea7c9999 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto2.proto @@ -0,0 +1,161 @@ +// Copyright 2023 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package com.code_intelligence.jazzer.protobuf; +option java_package = "com.code_intelligence.jazzer.protobuf"; + +message PrimitiveField2 { +optional bool some_field = 1; +} + +message RequiredPrimitiveField2 { +required bool some_field = 1; +} + +message RepeatedPrimitiveField2 { +repeated bool some_field = 1; +} + +message MessageField2 { +optional RequiredPrimitiveField2 message_field = 1; +} + +message RepeatedMessageField2 { +repeated RequiredPrimitiveField2 message_field = 1; +} + +message RepeatedOptionalMessageField2 { +repeated PrimitiveField2 message_field = 1; +} + +message RecursiveMessageField2 { +required bool some_field = 1; +optional RecursiveMessageField2 message_field = 2; +} + +message RepeatedRecursiveMessageField2 { +optional bool some_field = 1; +repeated RepeatedRecursiveMessageField2 message_field = 2; +} + +message OneOfField2 { +required bool other_field = 4; +oneof oneof_field { + bool bool_field = 7; + RequiredPrimitiveField2 message_field = 2; +} +optional bool yet_another_field = 1; +} + +message IntegralField2 { +optional uint32 some_field = 1; +} + +message RepeatedIntegralField2 { +repeated uint32 some_field = 1; +} + +message BytesField2 { +optional bytes some_field = 1; +} + +message StringField2 { +optional string some_field = 1; +} + +message Parent { + optional Child child = 1; +} + +message Child { + optional Parent parent = 1; +} + +// Taken from +// https://github.com/google/fuzztest/blob/c5fde4baee6134c84d4f2b618def9f60c7505151/fuzztest/internal/test_protobuf.proto#L24 +message TestSubProtobuf { + optional int32 subproto_i32 = 1; + repeated int32 subproto_rep_i32 = 2 [packed = true]; + optional TestProtobuf parent = 3; +} + +message TestProtobuf { + enum Enum { + Label1 = 0; + Label2 = 1; + Label3 = 2; + Label4 = 3; + Label5 = 4; + } + + optional bool b = 1; + optional int32 i32 = 2; + optional uint32 u32 = 3; + optional int64 i64 = 4; + optional uint64 u64 = 5; + optional float f = 6; + optional double d = 7; + optional string str = 8; + optional Enum e = 9; + optional TestSubProtobuf subproto = 10; + + repeated bool rep_b = 11; + repeated int32 rep_i32 = 12; + repeated uint32 rep_u32 = 13; + repeated int64 rep_i64 = 14; + repeated uint64 rep_u64 = 15; + repeated float rep_f = 16; + repeated double rep_d = 17; + repeated string rep_str = 18; + repeated Enum rep_e = 19; + repeated TestSubProtobuf rep_subproto = 20; + + oneof oneof_field { + int32 oneof_i32 = 21; + int64 oneof_i64 = 22; + uint32 oneof_u32 = 24; + } + + map<int32, int32> map_field = 25; + + // Special cases + enum EnumOneLabel { + OnlyLabel = 17; + } + optional EnumOneLabel enum_one_label = 100; + message EmptyMessage {} + optional EmptyMessage empty_message = 101; +} + +message OriginalSubmessage2 { + required int32 numeric_field = 1; +} + +message OriginalMessage2 { + required OriginalSubmessage2 message_field = 1; + required bool bool_field = 2; +} + +message ExtendedSubmessage2 { + required int32 numeric_field = 1; + required OriginalSubmessage2 message_field = 2; +} + +message ExtendedMessage2 { + required ExtendedSubmessage2 message_field = 1; + required bool bool_field = 2; + required float float_field = 3; +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto new file mode 100644 index 00000000..7bd6ffeb --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto @@ -0,0 +1,144 @@ +// Copyright 2023 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/protobuf/any.proto"; + +option java_package = "com.code_intelligence.jazzer.protobuf"; + +message PrimitiveField3 { + bool some_field = 1; +} + +message OptionalPrimitiveField3 { + optional bool some_field = 1; +} + +message RepeatedPrimitiveField3 { + repeated bool some_field = 1; +} + +message MessageField3 { + PrimitiveField3 message_field = 1; +} + +message RepeatedMessageField3 { + repeated PrimitiveField3 message_field = 1; +} + +message RecursiveMessageField3 { + bool some_field = 1; + RecursiveMessageField3 message_field = 2; +} + +message RepeatedRecursiveMessageField3 { + bool some_field = 1; + repeated RepeatedRecursiveMessageField3 message_field = 2; +} + +message OneOfField3 { + bool other_field = 4; + oneof oneof_field { + bool bool_field = 7; + PrimitiveField3 message_field = 2; + } + bool yet_another_field = 1; +} + +message IntegralField3 { + uint32 some_field = 1; +} + +message RepeatedIntegralField3 { + repeated uint32 some_field = 1; +} + +message BytesField3 { + bytes some_field = 1; +} + +message StringField3 { + string some_field = 1; +} + +message EnumField3 { + enum TestEnum { + VAL1 = 0; + VAL2 = 1; + } + TestEnum some_field = 1; +} + +enum TestEnumOutside3 { + VAL1 = 0; + VAL2 = 1; + VAL3 = 3; +} + +message EnumFieldOutside3 { + TestEnumOutside3 some_field = 1; +} + +message EnumFieldOne3 { + enum TestEnumOne { + ONE = 0; + } + TestEnumOne some_field = 1; +} + +message EnumFieldRepeated3 { + enum TestEnumRepeated { + UNASSIGNED = 0; + VAL1 = 1; + VAL2 = 2; + } + repeated TestEnumRepeated some_field = 1; +} + +message MapField3 { + map<int32, string> some_field = 1; +} + +message MessageMapField3 { + map<string, MapField3> some_field = 1; +} + +message FloatField3 { + float some_field = 1; +} + +message RepeatedFloatField3 { + repeated float some_field = 1; +} + +message DoubleField3 { + double some_field = 1; +} + +message RepeatedDoubleField3 { + repeated double some_field = 1; +} + +message EmptyMessage3 {} + +message AnyField3 { + google.protobuf.Any some_field = 1; +} + +message SingleOptionOneOfField3 { + oneof oneof_field { + bool bool_field = 1; + } +} |