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