Skip to content

Commit e7b9fd5

Browse files
committed
tests: Do not retain values in StressTest
StressTest required a lot of memory since it collected all `init` and `mutate` return values in a list. Instead, cross values off of a short list for "contains" type checks and use `hashCode()` to stand in for the actual value in statistical tests. Verified locally that the test now passes with `--jvmopt=-Xmx512M`.
1 parent f90c39d commit e7b9fd5

File tree

1 file changed

+129
-47
lines changed

1 file changed

+129
-47
lines changed

src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java

Lines changed: 129 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithZeros;
2121
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
2222
import static com.code_intelligence.jazzer.mutation.support.TestSupport.anyPseudoRandom;
23+
import static com.code_intelligence.jazzer.mutation.support.TestSupport.asMap;
2324
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asAnnotatedType;
2425
import static com.google.common.truth.Truth.assertThat;
2526
import static com.google.common.truth.Truth.assertWithMessage;
2627
import static java.lang.Math.floor;
2728
import static java.lang.Math.pow;
2829
import static java.lang.Math.sqrt;
30+
import static java.util.Arrays.asList;
31+
import static java.util.Arrays.stream;
2932
import static java.util.Collections.emptyList;
3033
import static java.util.Collections.singletonList;
34+
import static java.util.stream.Collectors.toList;
35+
import static java.util.stream.Collectors.toMap;
3136
import static java.util.stream.IntStream.rangeClosed;
3237
import static org.junit.jupiter.params.provider.Arguments.arguments;
3338

@@ -77,6 +82,7 @@
7782
import java.io.IOException;
7883
import java.lang.reflect.AnnotatedType;
7984
import java.util.ArrayList;
85+
import java.util.HashMap;
8086
import java.util.HashSet;
8187
import java.util.List;
8288
import java.util.Map;
@@ -179,10 +185,22 @@ null, emptyList(), singletonList(null), singletonList(false), singletonList(true
179185
new TypeHolder<@NotNull Map<@NotNull Boolean, @NotNull Boolean>>() {}.annotatedType(),
180186
"Map<Boolean,Boolean>",
181187
false,
182-
// 1 0-element map, 4 1-element maps
183-
distinctElements(1 + 4),
184-
// 1 0-element map, 4 1-element maps, 4 2-element maps
185-
distinctElements(1 + 4 + 4)),
188+
exactly(
189+
asMap(),
190+
asMap(false, false),
191+
asMap(false, true),
192+
asMap(true, false),
193+
asMap(true, true)),
194+
exactly(
195+
asMap(),
196+
asMap(false, false),
197+
asMap(false, true),
198+
asMap(true, false),
199+
asMap(true, true),
200+
asMap(false, false, true, false),
201+
asMap(false, false, true, true),
202+
asMap(false, true, true, false),
203+
asMap(false, true, true, true))),
186204
arguments(
187205
asAnnotatedType(byte.class),
188206
"Byte",
@@ -567,20 +585,25 @@ public static Stream<Arguments> protoStressTestCases() {
567585
SingleOptionOneOfField3.newBuilder().setBoolField(true).build())));
568586
}
569587

570-
@SafeVarargs
571-
private static Consumer<List<Object>> all(Consumer<List<Object>>... checks) {
572-
return list -> {
573-
for (Consumer<List<Object>> check : checks) {
574-
check.accept(list);
588+
private static CloseableConsumer all(CloseableConsumer... checks) {
589+
return new CloseableConsumer() {
590+
@Override
591+
public void close() throws Exception {
592+
for (CloseableConsumer check : checks) {
593+
check.close();
594+
}
575595
}
576-
};
577-
}
578596

579-
private static Consumer<List<Object>> distinctElements(int num) {
580-
return list -> assertThat(new HashSet<>(list).size()).isAtLeast(num);
597+
@Override
598+
public void accept(Object value) {
599+
for (CloseableConsumer check : checks) {
600+
check.accept(value);
601+
}
602+
}
603+
};
581604
}
582605

583-
private static Consumer<List<Object>> manyDistinctElements() {
606+
private static CloseableConsumer manyDistinctElements() {
584607
return distinctElementsRatio(MANY_DISTINCT_ELEMENTS_RATIO);
585608
}
586609

@@ -603,8 +626,7 @@ private static int boundHits(long domainSize, double probability) {
603626
* Asserts that a given list contains at least as many distinct elements as can be expected when
604627
* picking {@code picks} out of {@code domainSize} elements uniformly at random.
605628
*/
606-
private static Consumer<List<Object>> expectedNumberOfDistinctElements(
607-
long domainSize, int picks) {
629+
private static CloseableConsumer expectedNumberOfDistinctElements(long domainSize, int picks) {
608630
// https://www.randomservices.org/random/urn/Birthday.html#mom2
609631
double expectedValue = domainSize * (1 - pow(1 - 1.0 / domainSize, picks));
610632
double variance =
@@ -615,56 +637,118 @@ private static Consumer<List<Object>> expectedNumberOfDistinctElements(
615637
// Allow missing the expected value by two standard deviations. For a normal distribution,
616638
// this would correspond to 95% of all cases.
617639
int almostCertainLowerBound = (int) floor(expectedValue - 2 * standardDeviation);
618-
return list ->
640+
HashSet<Integer> hashes = new HashSet<>();
641+
return new CloseableConsumer() {
642+
@Override
643+
public void accept(Object value) {
644+
hashes.add(Objects.hashCode(value));
645+
}
646+
647+
@Override
648+
public void close() {
619649
assertWithMessage(
620650
"V=distinct elements among %s picked out of %s\nE[V]=%s\nσ[V]=%s",
621651
picks, domainSize, expectedValue, standardDeviation)
622-
.that(new HashSet<>(list).size())
652+
.that(hashes.size())
623653
.isAtLeast(almostCertainLowerBound);
654+
}
655+
};
624656
}
625657

626-
private static Consumer<List<Object>> distinctElementsRatio(double ratio) {
658+
private static CloseableConsumer distinctElementsRatio(double ratio) {
627659
require(ratio > 0);
628660
require(ratio <= 1);
629-
return list -> assertThat(new HashSet<>(list).size() / (double) list.size()).isAtLeast(ratio);
661+
List<Integer> hashes = new ArrayList<>();
662+
return new CloseableConsumer() {
663+
@Override
664+
public void accept(Object value) {
665+
hashes.add(Objects.hashCode(value));
666+
}
667+
668+
@Override
669+
public void close() {
670+
assertThat(new HashSet<>(hashes).size() / (double) hashes.size()).isAtLeast(ratio);
671+
}
672+
};
630673
}
631674

632-
private static Consumer<List<Object>> exactly(Object... expected) {
633-
return list -> assertThat(new HashSet<>(list)).containsExactly(expected);
675+
private static CloseableConsumer exactly(Object... expected) {
676+
return containsInternal(true, expected);
634677
}
635678

636-
private static Consumer<List<Object>> contains(Object... expected) {
637-
return list -> assertThat(new HashSet<>(list)).containsAtLeastElementsIn(expected);
679+
private static CloseableConsumer contains(Object... expected) {
680+
return containsInternal(false, expected);
638681
}
639682

640-
private static Consumer<List<Object>> doesNotContain(Object... expected) {
641-
return list -> assertThat(new HashSet<>(list)).containsNoneIn(expected);
683+
private static CloseableConsumer containsInternal(boolean exactly, Object... expected) {
684+
Map<Object, Boolean> sawValue =
685+
stream(expected)
686+
.collect(
687+
toMap(
688+
value -> value,
689+
value -> false,
690+
(a, b) -> {
691+
throw new IllegalStateException("Duplicate value " + a);
692+
},
693+
HashMap::new));
694+
return new CloseableConsumer() {
695+
@Override
696+
public void accept(Object value) {
697+
if (exactly) {
698+
assertThat(value).isIn(sawValue.keySet());
699+
}
700+
sawValue.put(value, true);
701+
}
702+
703+
@Override
704+
public void close() {
705+
assertThat(sawValue.entrySet().stream().filter(e -> !e.getValue()).collect(toList()))
706+
.isEmpty();
707+
}
708+
};
642709
}
643710

644-
private static Consumer<List<Object>> mapSizeInClosedRange(int min, int max) {
645-
return list -> {
646-
list.forEach(
647-
map -> {
648-
if (map instanceof Map) {
649-
assertThat(((Map) map).size()).isAtLeast(min);
650-
assertThat(((Map) map).size()).isAtMost(max);
651-
} else {
652-
throw new IllegalArgumentException(
653-
"Expected a list of maps, got list of" + map.getClass().getName());
654-
}
655-
});
711+
private static CloseableConsumer doesNotContain(Object... expected) {
712+
return new CloseableConsumer() {
713+
@Override
714+
public void accept(Object value) {
715+
assertThat(value).isNotIn(asList(expected));
716+
}
717+
718+
@Override
719+
public void close() {}
656720
};
657721
}
658722

723+
private static CloseableConsumer mapSizeInClosedRange(int min, int max) {
724+
return new CloseableConsumer() {
725+
@Override
726+
public void accept(Object map) {
727+
if (map instanceof Map) {
728+
assertThat(((Map) map).size()).isAtLeast(min);
729+
assertThat(((Map) map).size()).isAtMost(max);
730+
} else {
731+
throw new IllegalArgumentException(
732+
"Expected a list of maps, got list of" + map.getClass().getName());
733+
}
734+
}
735+
736+
@Override
737+
public void close() {}
738+
};
739+
}
740+
741+
interface CloseableConsumer extends AutoCloseable, Consumer<Object> {}
742+
659743
@ParameterizedTest(name = "{index} {0}, {1}")
660744
@MethodSource({"stressTestCases", "protoStressTestCases"})
661745
void genericMutatorStressTest(
662746
AnnotatedType type,
663747
String mutatorTree,
664748
boolean hasFixedSize,
665-
Consumer<List<Object>> expectedInitValues,
666-
Consumer<List<Object>> expectedMutatedValues)
667-
throws IOException {
749+
CloseableConsumer checkInitValues,
750+
CloseableConsumer checkMutatedValues)
751+
throws Exception {
668752
validateAnnotationUsage(type);
669753
SerializingMutator mutator = Mutators.newFactory().createOrThrow(type);
670754
assertThat(mutator.toString()).isEqualTo(mutatorTree);
@@ -678,8 +762,6 @@ void genericMutatorStressTest(
678762

679763
PseudoRandom rng = anyPseudoRandom();
680764

681-
List<Object> initValues = new ArrayList<>();
682-
List<Object> mutatedValues = new ArrayList<>();
683765
for (int i = 0; i < NUM_INITS; i++) {
684766
Object value = mutator.init(rng);
685767

@@ -689,7 +771,7 @@ void genericMutatorStressTest(
689771
testReadWriteRoundtrip(mutator, fixedValue);
690772
testReadWriteExclusiveRoundtrip(mutator, fixedValue);
691773

692-
initValues.add(mutator.detach(value));
774+
checkInitValues.accept(value);
693775
value = fixFloatingPointsForProtos(value);
694776

695777
for (int mutation = 0; mutation < NUM_MUTATE_PER_INIT; mutation++) {
@@ -705,7 +787,7 @@ void genericMutatorStressTest(
705787
}
706788
}
707789

708-
mutatedValues.add(mutator.detach(value));
790+
checkMutatedValues.accept(value);
709791

710792
// For proto messages, each float field with value -0.0f, and double field with value -0.0
711793
// will be converted to 0.0f and 0.0, respectively. This is because the values -0f and 0f
@@ -720,8 +802,8 @@ void genericMutatorStressTest(
720802
}
721803
}
722804

723-
expectedInitValues.accept(initValues);
724-
expectedMutatedValues.accept(mutatedValues);
805+
checkInitValues.close();
806+
checkMutatedValues.close();
725807
}
726808

727809
private static <T> void testReadWriteExclusiveRoundtrip(Serializer<T> serializer, T value)

0 commit comments

Comments
 (0)