All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.indeed.proctor.common.TestRangeSelector Maven / Gradle / Ivy

package com.indeed.proctor.common;

import com.google.common.collect.Maps;
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 org.apache.log4j.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

/**
 * This is perhaps not the greatest abstraction the world has seen; is meant to consolidate common functionality needed for different types of choosers WITHOUT using inheritance
 * @author ketan
 *
 */
public class TestRangeSelector {
    private static final Logger LOGGER = Logger.getLogger(RandomTestChooser.class);

    @Nonnull
    private final String testName;
    @Nonnull
    private final ConsumableTestDefinition testDefinition;
    @Nonnull
    private final String[] rules;
    @Nonnull
    private final TestBucket[][] rangeToBucket;
    private final RuleEvaluator ruleEvaluator;

    TestRangeSelector(@Nonnull final ExpressionFactory expressionFactory, @Nonnull final FunctionMapper functionMapper, final String testName, @Nonnull final ConsumableTestDefinition testDefinition) {
        this(new RuleEvaluator(expressionFactory, functionMapper, testDefinition.getConstants()), testName, testDefinition);
    }

    TestRangeSelector(
            @Nonnull final RuleEvaluator ruleEvaluator,
            @Nonnull final String testName,
            @Nonnull final ConsumableTestDefinition testDefinition
    ) {
        this.ruleEvaluator = ruleEvaluator;

        this.testName = testName;
        this.testDefinition = testDefinition;

        final Map bucketValueToTest = Maps.newHashMap();
        for (final TestBucket testBucket : testDefinition.getBuckets()) {
            bucketValueToTest.put(testBucket.getValue(), testBucket);
        }

        final List allocations = testDefinition.getAllocations();
        this.rangeToBucket = new TestBucket[allocations.size()][];
        this.rules = new String[allocations.size()];
        for (int i = 0; i < allocations.size(); i++) {
            final Allocation allocation = allocations.get(i);
            rules[i] = allocation.getRule();
            final List ranges = allocation.getRanges();
            this.rangeToBucket[i] = new TestBucket[ranges.size()];
            for (int j = 0; j < ranges.size(); j++) {
                this.rangeToBucket[i][j] = bucketValueToTest.get(ranges.get(j).getBucketValue());
            }
        }
    }

    public TestBucket[] getBucketRange(final int index) {
        return rangeToBucket[index];
    }

    public int findMatchingRule(@Nonnull final Map values) {
        try {
            @Nullable final String rule = testDefinition.getRule();
            if (rule != null) {
                if (! evaluateRule(rule, values)) {
                    return -1;
                }
            }

            for (int i = 0; i < rules.length; i++) {
                if (evaluateRule(rules[i], values)) {
                    return i;
                }
            }

        } catch (InvalidRuleException e) {
            LOGGER.error("Failed to evaluate test rules; ", e);
        }

        return -1;
    }

    private boolean evaluateRule(@Nonnull final String rule, @Nonnull final Map values) throws InvalidRuleException {
        try {
            return ruleEvaluator.evaluateBooleanRule(rule, values);

        } catch (@Nonnull final RuntimeException e) {
            throw new InvalidRuleException(e, String.format(
                    "Error evaluating rule '%s' for test '%s': '%s'. Failing evaluation and continuing.",
                    rule, testName, e.getMessage()));
        }
    }

    @Nonnull
    public String[] getRules() {
        return rules;
    }

    @Nonnull
    public ConsumableTestDefinition getTestDefinition() {
        return testDefinition;
    }

    /**
     * Do not evaluate the rule, do not use the pseudo-random allocation algorithm, do not collect $200.
     * This should ONLY be used by privileged code for debugging.
     * @return a {@link TestBucket} with the specified value or null if none exists
     */
    @Nullable
    public TestBucket getTestBucket(final int value) {
        for (final TestBucket testBucket : testDefinition.getBuckets()) {
            if (testBucket.getValue() == value) {
                return testBucket;
            }
        }
        return null;
    }

    @Nonnull
    public String getTestName() {
        return testName;
    }

    protected void printTestBuckets(@Nonnull final PrintWriter writer, @Nonnull final Map parameters) {
        // TODO (parker) 5/4/12 - figure out why jasx RandomChooser uses a ThreadLocal NumberFormatter
        final NumberFormat fmt = NumberFormat.getPercentInstance(Locale.US);
        fmt.setMaximumFractionDigits(2);

        writer.printf("{ ");
        for (final Iterator> iterator = parameters.entrySet().iterator(); iterator.hasNext(); ) {
            final Entry entry = iterator.next();
            writer.print(entry.getKey());
            writer.print("'");
            writer.print(entry.getValue());
            writer.print("'");
            if (iterator.hasNext()) {
                writer.print(", ");
            }
        }

        if (testDefinition.getRule() != null) {
            writer.printf(", rule='%s'", testDefinition.getRule());
        }
        final String separator;
        if (testDefinition.getAllocations().size() > 1) {
            separator = ",\n\t";
        } else {
            separator = ", ";
        }
        final List allocations = testDefinition.getAllocations();
        for (final Allocation allocation : allocations) {
            writer.print(separator);
            if (allocation.getRule() != null) {
                writer.printf("rule='%s', ", allocation.getRule());
            }
            final List ranges = allocation.getRanges();
            for (int j = 0; j < ranges.size(); j++) {
                // ignoring the trailing comma
                if (j > 0) {
                    writer.print(", ");
                }
                final Range range = ranges.get(j);
                writer.printf("%s%d='%s'", testName, range.getBucketValue(), fmt.format(range.getLength()));
            }
        }
        writer.print(" },");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy