
com.indeed.proctor.common.RandomTestChooser Maven / Gradle / Ivy
package com.indeed.proctor.common;
import com.indeed.proctor.common.model.Allocation;
import com.indeed.proctor.common.model.ConsumableTestDefinition;
import com.indeed.proctor.common.model.Range;
import com.indeed.proctor.common.model.TestBucket;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import com.google.common.annotations.VisibleForTesting;
/**
* Embodies the logic for a single purely random test, including applicability rule and distribution. {@link #choose(Void, java.util.Map)} is the only useful entry point.
* @author ketan
*/
@VisibleForTesting
class RandomTestChooser implements TestChooser {
@Nonnull
private final Random random;
@Nonnull
private final TestRangeSelector testRangeSelector;
@Nonnull
private final List allocations;
public RandomTestChooser(final ExpressionFactory expressionFactory, final FunctionMapper functionMapper, final String testName, @Nonnull final ConsumableTestDefinition testDefinition) {
this(System.nanoTime(), expressionFactory, functionMapper, testName, testDefinition);
}
public RandomTestChooser(final long seed, final ExpressionFactory expressionFactory, final FunctionMapper functionMapper, final String testName, @Nonnull final ConsumableTestDefinition testDefinition) {
testRangeSelector = new TestRangeSelector(expressionFactory, functionMapper, testName, testDefinition);
allocations = testDefinition.getAllocations();
random = new Random(seed);
}
@Nonnull
private Map getDescriptorParameters() {
return Collections.singletonMap("type", testRangeSelector.getTestDefinition().getTestType().name());
}
@Override
public String toString() {
final Map parameters = getDescriptorParameters();
final Writer sw = new StringWriter();
final PrintWriter writer = new PrintWriter(sw);
testRangeSelector.printTestBuckets(writer, parameters);
return sw.toString();
}
@Override
public void printTestBuckets(@Nonnull final PrintWriter writer) {
final Map parameters = getDescriptorParameters();
testRangeSelector.printTestBuckets(writer, parameters);
}
@Nullable
@Override
public TestBucket getTestBucket(final int value) {
return testRangeSelector.getTestBucket(value);
}
@Override
@Nonnull
public String[] getRules() {
return testRangeSelector.getRules();
}
@Override
@Nonnull
public ConsumableTestDefinition getTestDefinition() {
return testRangeSelector.getTestDefinition();
}
@Override
@Nonnull
public String getTestName() {
return testRangeSelector.getTestName();
}
@Nullable
@Override
public TestBucket choose(@Nullable Void identifier, @Nonnull Map values) {
final int matchingRuleIndex = testRangeSelector.findMatchingRule(values);
if (matchingRuleIndex < 0) {
return null;
}
// TODO Reimplement
//noinspection deprecation
return allocateRandomGroup(matchingRuleIndex);
}
/**
* @deprecated Temporary implementation; this should be more like {@link StandardTestChooser}, with the cutoffs etc. set in the constructor.
*/
TestBucket allocateRandomGroup(final int matchingRuleIndex) {
final TestBucket[] matchingBucketRange = testRangeSelector.getBucketRange(matchingRuleIndex);
final Allocation allocation = allocations.get(matchingRuleIndex);
final List ranges = allocation.getRanges();
final double nextDouble = random.nextDouble();
double current = 0;
for (final Range range : ranges) {
final double max = current + range.getLength();
if (nextDouble < max) {
final int matchingBucketValue = range.getBucketValue();
return getBucketForValue(matchingBucketValue, matchingBucketRange);
}
current = max;
}
// fallback because I don't trust double math to always do the right thing
return getBucketForValue(ranges.get(ranges.size() - 1).getBucketValue(), matchingBucketRange);
}
static TestBucket getBucketForValue(final int matchingBucketValue, @Nonnull final TestBucket[] matchingBucketRange) {
for (TestBucket bucket : matchingBucketRange) {
if (matchingBucketValue == bucket.getValue()) {
return bucket;
}
}
throw new IllegalStateException("Unable to find a bucket with value " + matchingBucketValue);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy