Package com.indeed.proctor.common

Source Code of com.indeed.proctor.common.TestProctorUtils

package com.indeed.proctor.common;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.indeed.proctor.common.model.Allocation;
import com.indeed.proctor.common.model.Audit;
import com.indeed.proctor.common.model.ConsumableTestDefinition;
import com.indeed.proctor.common.model.Payload;
import com.indeed.proctor.common.model.Range;
import com.indeed.proctor.common.model.TestBucket;
import com.indeed.proctor.common.model.TestDefinition;
import com.indeed.proctor.common.model.TestMatrixArtifact;
import com.indeed.proctor.common.model.TestType;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;

import javax.el.ValueExpression;
import java.io.IOException;
import java.io.InputStream;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.indeed.proctor.common.model.Allocation;
import com.indeed.proctor.common.model.Audit;
import com.indeed.proctor.common.model.ConsumableTestDefinition;
import com.indeed.proctor.common.model.Payload;
import com.indeed.proctor.common.model.Range;
import com.indeed.proctor.common.model.TestBucket;
import com.indeed.proctor.common.model.TestDefinition;
import com.indeed.proctor.common.model.TestMatrixArtifact;
import com.indeed.proctor.common.model.TestType;
import org.junit.Test;

import javax.el.ELException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;


/**
* @author parker
*/
public class TestProctorUtils {

    private static final String TEST_A = "testA";
    private static final String TEST_B = "testB";
    private static final String PATH_UNKNOWN_TEST_TYPE = "unknown-test-type.json";

    /**
     * Test that top level and allocation rules all respect the same definition
     * of "empty"
     */
    @Test
    public void convertToConsumableTestDefinitionEmptyRules() {
        // all of the following "rules" should be treated as empty in the TestDefinition
        // rule: null
        // rule: ""
        // rule: " "
        final String[] emptyRules = new String[] { null, "", " " };
        final Range range = new Range(0, 1.0d);

        final String version = "100";
        final TestType testType = TestType.ANONYMOUS_USER;
        final String salt = "testsalt";
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        final Map<String, Object> constants = Collections.emptyMap();
        final Map<String, Object> specialConstants = Collections.emptyMap();
        final String description = "test description";

        for(final String tdRule : emptyRules) {
            for(String allocRule : emptyRules) {
                final Allocation allocation = new Allocation(allocRule, Collections.singletonList(range));
                final TestDefinition testDefinition = new TestDefinition(
                    version,
                    tdRule,
                    testType,
                    salt,
                    buckets,
                    Collections.singletonList(allocation),
                    constants,
                    specialConstants,
                    description
                );

                final ConsumableTestDefinition ctd = ProctorUtils.convertToConsumableTestDefinition(testDefinition);
                assertEquals(version, ctd.getVersion());
                assertEquals(testType, ctd.getTestType());
                assertEquals(salt, ctd.getSalt());
                assertEquals(description, ctd.getDescription());
                assertEquals(0, ctd.getConstants().size());
                assertEquals(buckets, ctd.getBuckets());

                assertEquals(String.format("TestDefinition rule '%s' should convert to a null ConsumableTestDefinition.rule", tdRule), null, ctd.getRule());

                assertEquals(1, ctd.getAllocations().size());
                final Allocation ctdAllocation = ctd.getAllocations().get(0);
                assertEquals(String.format("Allocation rule '%s' should convert to a null ConsumableTestDefinition.Allocation.rule", allocRule), null, ctdAllocation.getRule());
                assertEquals(allocation.getRanges(), ctdAllocation.getRanges());
            }
        }
    }


    /**
     * Checks that allocation and top level rules can optionally be surrounded by ${ ... }
     */
    @Test
    public void convertToConsumableTestDefinitionTopLevelRules() {
        // rules can optionally have the "${}" around them.
        // rule: lang == 'en'
        // rule: ${lang == 'en'}
        final Range range = new Range(0, 1.0d);

        final String version = "100";
        final TestType testType = TestType.ANONYMOUS_USER;
        final String salt = "testsalt";
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        final Map<String, Object> constants = Collections.emptyMap();
        final Map<String, Object> specialConstants = Collections.emptyMap();
        final String description = "test description";


        for(final String rule : new String[] { "lang == 'en'", "${lang == 'en'}"})
        {
            final Allocation allocation = new Allocation(rule, Collections.singletonList(range));
            final TestDefinition testDefinition = new TestDefinition(
                version,
                rule,
                testType,
                salt,
                buckets,
                Collections.singletonList(allocation),
                constants,
                specialConstants,
                description
            );

            final ConsumableTestDefinition ctd = ProctorUtils.convertToConsumableTestDefinition(testDefinition);
            assertEquals(String.format("TestDefinition rule '%s' should convert to a ${lang == 'en'} ConsumableTestDefinition.rule", rule), "${lang == 'en'}", ctd.getRule());

            assertEquals(1, ctd.getAllocations().size());
            final Allocation ctdAllocation = ctd.getAllocations().get(0);
            assertEquals(String.format("Allocation rule '%s' should convert to a ${lang == 'en'} ConsumableTestDefinition.Allocation.rule", rule), "${lang == 'en'}", ctdAllocation.getRule());
            assertEquals(allocation.getRanges(), ctdAllocation.getRanges());
        }
    }

    /**
     * Checks that top level rules respect the special constants and support
     * rule formats that do and do not contain "${}"
     */
    @Test
    public void convertToConsumableTestDefinitionTopLevelRulesSpecialConstants() {
        // Top Level rules can optionally have the "${}" around them.
        // rule: lang == 'en'
        // rule: ${lang == 'en'}
        final Range range = new Range(0, 1.0d);

        final String version = "100";
        final TestType testType = TestType.ANONYMOUS_USER;
        final String salt = "testsalt";
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        final Map<String, Object> constants = Collections.emptyMap();
        final Map<String, Object> specialConstants = Collections.<String, Object>singletonMap("__COUNTRIES", Lists.newArrayList("US", "CA"));
        final String description = "test description";

        final Allocation allocation = new Allocation(null, Collections.singletonList(range));

        for(final String tdRule : new String[] { "lang == 'en'", "${lang == 'en'}"})
        {
            final TestDefinition testDefinition = new TestDefinition(
                version,
                tdRule,
                testType,
                salt,
                buckets,
                Collections.singletonList(allocation),
                constants,
                specialConstants,
                description
            );

            final ConsumableTestDefinition ctd = ProctorUtils.convertToConsumableTestDefinition(testDefinition);
            assertEquals(String.format("TestDefinition rule '%s' should convert to ${proctor:contains(__COUNTRIES, country) && lang == 'en'} ConsumableTestDefinition.rule", tdRule), "${proctor:contains(__COUNTRIES, country) && lang == 'en'}", ctd.getRule());
            assertEquals(1, ctd.getConstants().size());
            assertEquals("special constants should be added to constants", Lists.newArrayList("US", "CA"), ctd.getConstants().get("__COUNTRIES"));
        }
    }

    @Test
    public void testRemoveElExpressionBraces() {
        assertEquals(null, ProctorUtils.removeElExpressionBraces(""));
        assertEquals(null, ProctorUtils.removeElExpressionBraces(" "));
        assertEquals(null, ProctorUtils.removeElExpressionBraces(null));
        assertEquals(null, ProctorUtils.removeElExpressionBraces("\t"));
        assertEquals(null, ProctorUtils.removeElExpressionBraces(" ${} "));
        assertEquals(null, ProctorUtils.removeElExpressionBraces(" ${ } "));
        assertEquals("a", ProctorUtils.removeElExpressionBraces("${a}"));
        assertEquals("a", ProctorUtils.removeElExpressionBraces(" ${a} "));
        assertEquals("a", ProctorUtils.removeElExpressionBraces(" ${ a } "));
        assertEquals("a", ProctorUtils.removeElExpressionBraces(" ${ a}"));
        assertEquals("a", ProctorUtils.removeElExpressionBraces("${a } "));
        assertEquals("a", ProctorUtils.removeElExpressionBraces(" a "));
        assertEquals("lang == 'en'", ProctorUtils.removeElExpressionBraces("lang == 'en'"));
        assertEquals("lang == 'en'", ProctorUtils.removeElExpressionBraces("${lang == 'en'}"));
        // whitespace should be trimmed
        assertEquals("lang == 'en'", ProctorUtils.removeElExpressionBraces("${ lang == 'en' }"));
        // whitespace should be removed around braces
        assertEquals("lang == 'en'", ProctorUtils.removeElExpressionBraces(" ${ lang == 'en' } "));
        // only single level of braces are removed
        assertEquals("${lang == 'en'}", ProctorUtils.removeElExpressionBraces("${${lang == 'en'}}"));
        // mis matched braces are not handled
        assertEquals("${lang == 'en'", ProctorUtils.removeElExpressionBraces("${lang == 'en'"));
        // mis matched braces are not handled
        assertEquals("lang == 'en'}", ProctorUtils.removeElExpressionBraces("lang == 'en'}"));
    }

    @Test
    public void testEmptyWhitespace() {
        assertTrue(ProctorUtils.isEmptyWhitespace(""));
        assertTrue(ProctorUtils.isEmptyWhitespace(null));
        assertTrue(ProctorUtils.isEmptyWhitespace("  "));
        assertTrue(ProctorUtils.isEmptyWhitespace("  \t"));
        assertFalse(ProctorUtils.isEmptyWhitespace(" x "));
        assertFalse(ProctorUtils.isEmptyWhitespace("/"));
    }


    @Test
    public void verifyAndConsolidateShouldTestAllocationSum() throws IncompatibleTestMatrixException {
        List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets));
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations do not add up to 1
                                                      fromCompactAllocationFormat("ruleA|-1:0.5,0:0.5,1:0.0", "-1:0.0,0:0.0,1:0.0")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // verifyAndConsolidate should not throw an error because the 'invalidbuckets' test is not required.
            assertEquals(1, matrix.getTests().size());
            assertValid("invalid test not required, sum{allocations} < 1.0", matrix, Collections.<String, TestSpecification>emptyMap());
            assertEquals("non-required tests should be removed from the matrix", 0, matrix.getTests().size());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations do not add up to 1
                                                      fromCompactAllocationFormat("ruleA|-1:0.5,0:0.5,1:0.0", "-1:0.0,0:0.0,1:0.0")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // verifyAndConsolidate should throw an error because the 'invalidbuckets' test is required.
            assertEquals(1, matrix.getTests().size());
            assertInvalid("bucket allocation sums are unchecked, sum{allocations} < 1.0", matrix, requiredTests);
            assertEquals("required tests should not be removed from the matrix", 1, matrix.getTests().size());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations do not add up to 1
                                                      fromCompactAllocationFormat("ruleA|-1:0.5,0:0.5,1:0.0", "-1:0.5,0:0.5,1:0.5")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertEquals(1, matrix.getTests().size());
            // verifyAndConsolidate should not throw an error because the 'invalidbuckets' test is not required.
            assertValid("invalid test not required, sum{allocations} > 1.0", matrix, Collections.<String, TestSpecification>emptyMap());
            assertEquals("non-required tests should be removed from the matrix", 0, matrix.getTests().size());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations do not add up to 1
                                                      fromCompactAllocationFormat("ruleA|-1:0.5,0:0.5,1:0.0", "-1:0.5,0:0.5,1:0.5")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertEquals(1, matrix.getTests().size());
            // verifyAndConsolidate should throw an error because the 'testa' test is required.
            assertInvalid("bucket allocation sums are unchecked, sum{allocations} > 1.0", matrix, requiredTests);
            assertEquals("required tests should not be removed from the matrix", 1, matrix.getTests().size());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations add up to 1.0
                                                      fromCompactAllocationFormat("ruleA|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertEquals(1, matrix.getTests().size());
            assertValid("bucket allocation sums are unchecked, sum{allocations} == 1.0", matrix, Collections.<String, TestSpecification>emptyMap());
            assertEquals("non-required tests should be removed from the matrix", 0, matrix.getTests().size());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations add up to 1.0
                                                      fromCompactAllocationFormat("ruleA|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertEquals(1, matrix.getTests().size());
            assertValid("bucket allocation sums are unchecked, sum{allocations} == 1.0", matrix, requiredTests);
            assertEquals("required tests should not be removed from the matrix", 1, matrix.getTests().size());
        }
    }

    @Test
    public void testELValidity_inProctorBuilderAllocationRules() throws IncompatibleTestMatrixException {

        //testing invalid allocation rule
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        final ConsumableTestDefinition testDefInVal = constructDefinition(buckets,
                fromCompactAllocationFormat("${b4t#+=}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25")); // invalid EL, nonsense rule
        try {
            ProctorUtils.verifyInternallyConsistentDefinition("testELevalInval", "test el recognition - inval", testDefInVal);
            fail("expected IncompatibleTestMatrixException");
        } catch (IncompatibleTestMatrixException e) {
            //expected
        }

        //testing valid functions pass with proctor included functions (will throw exception if can't find) and backwards compatibility
        final ConsumableTestDefinition testDefVal1 = constructDefinition(buckets,
                fromCompactAllocationFormat("${proctor:now()==indeed:now()}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
        ProctorUtils.verifyInternallyConsistentDefinition("testELevalProctor", "test el recognition", testDefVal1);

    }
    @Test
    public void testELValidity_inProctorBuilderTestRule() throws IncompatibleTestMatrixException {
        //Testing syntax validation with a test rule
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        final ConsumableTestDefinition testDefInValTestRule = constructDefinition(buckets,
                fromCompactAllocationFormat("${proctor:now()>-1}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
        testDefInValTestRule.setRule("${b4t#+=}");
        try {
            ProctorUtils.verifyInternallyConsistentDefinition("testELevalInValTestRule", "test el recognition - inval test rule", testDefInValTestRule);
            fail("expected IncompatibleTestMatrixException");
        } catch (IncompatibleTestMatrixException e) {
            //expected
        }

        //testing the test rule el function recognition
        final ConsumableTestDefinition testDefValTestRule = constructDefinition(buckets,
                fromCompactAllocationFormat("${true}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
        testDefValTestRule.setRule("${proctor:now()==indeed:now()}");
        ProctorUtils.verifyInternallyConsistentDefinition("testELevalValTestRule", "test el recognition - val test rule and functions", testDefValTestRule);


    }

    @Test
    public void testProvidedContextConversion() throws IncompatibleTestMatrixException {
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        { //verify primitive types convert correctly
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time eq ''}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextString = new HashMap<String, String>();
            providedContextString.put("time", "String");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextString);
            ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion String", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
            //checking to make sure it can evaluate with converted provided context
        }
        { //verify primitive types convert correctly
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time eq 0}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextInteger = new HashMap<String, String>();
            providedContextInteger.put("time", "int");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextInteger);
            ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Integer", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
            //checking to make sure it can evaluate with converted provided context
        }
        { //verify primitive types convert correctly
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time eq ''}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextChar = new HashMap<String, String>();
            providedContextChar.put("time", "char");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextChar);
            ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Char", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
            //checking to make sure it can evaluate with converted provided context
        }
        { //verify primitive types convert correctly
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextBoolean = new HashMap<String, String>();
            providedContextBoolean.put("time", "Boolean");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextBoolean);
            ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Boolean", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
            //checking to make sure it can evaluate with converted provided context
        }
        { //verify User Defined enum Classes convert correctly
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time eq 'SPADES'}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextClass = new HashMap<String, String>();
            providedContextClass.put("time", "com.indeed.proctor.common.TestEnumType");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextClass);
            ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Class", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
            //checking to make sure it can evaluate with converted provided context
        }
        { //verify enums are actually used and an error is thrown with a nonexistent constant
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time eq 'SP'}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextClass = new HashMap<String, String>();
            providedContextClass.put("time", "com.indeed.proctor.common.TestEnumType");
            try {
                final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextClass);
                ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Class", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
                fail("expected IncompatibleTestMatrixException due to nonexistent enum constant");
            } catch (IncompatibleTestMatrixException e) {
                //expected
            }
        }
        { //verify class names are verified correctly
            final Map<String, String> providedContextBadClass = new HashMap<String, String>();
            providedContextBadClass.put("time", "com.indeed.proctor.common.TestRulesCla");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextBadClass);
            assertFalse(providedContext.isEvaluable());
            //false due to bad classname
        }
        { //verify missing constructors are verified correctly
            final Map<String, String> providedContextNoConstructor = new HashMap<String, String>();
            providedContextNoConstructor.put("time", "com.indeed.proctor.common.AbstractProctorLoader");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextNoConstructor);
            assertFalse(providedContext.isEvaluable());
            //false due to no default constructor
        }
    }

    @Test
    public void testELValidity_atTestMatrixLoadTime() throws IncompatibleTestMatrixException, IOException {
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        {
            //testing recognition of test constants
            final ConsumableTestDefinition testDefValConstants = constructDefinition(buckets,
                    fromCompactAllocationFormat("${proctor:now()>time}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, Object> providedConstantsVal = new HashMap<String, Object>();
            providedConstantsVal.put("time", "1");
            testDefValConstants.setConstants(providedConstantsVal);
            final Map<String, String> providedContext = Collections.emptyMap();
            ProctorUtils.verifyInternallyConsistentDefinition("testELevalwithcontext", "test context recognition", testDefValConstants, RuleEvaluator.FUNCTION_MAPPER,
                    ProctorUtils.convertContextToTestableMap(providedContext));
        }
        {//test if the providedContext is read in correctly
            final ConsumableTestDefinition testDefValConstants2 = constructDefinition(buckets,
                    fromCompactAllocationFormat("${proctor:now()>time}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final ObjectMapper objectMapper = new ObjectMapper();
            final ProctorSpecification spec = objectMapper.readValue(getClass().getResourceAsStream("no-context-specification.json"), ProctorSpecification.class);
            final Map<String, String> providedContext2 = spec.getProvidedContext(); //needs to read in empty provided context as Collections.emptyMap() and not null
            try {
                ProctorUtils.verifyInternallyConsistentDefinition("testELevalwithcontext", "test context recognition", testDefValConstants2, RuleEvaluator.FUNCTION_MAPPER,
                        ProctorUtils.convertContextToTestableMap(providedContext2));
                fail("expected IncompatibleTestMatrixException");
            } catch (IncompatibleTestMatrixException e) {
                //expected
            }
        }
        {//test that an error is thrown with missing providedContext
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${time eq ''}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            try {
                ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextMissing", "test Provided Context Missing", testDef, RuleEvaluator.FUNCTION_MAPPER, new ProvidedContext(ProvidedContext.EMPTY_CONTEXT,true));
                //checking to make sure it can evaluate with converted provided context
                fail("expected IncompatibleTestMatrixException due to missing provided Context");
            } catch (IncompatibleTestMatrixException e) {
                //expected
            }
        }
        {//testing recognition of providedContext in testRule
            final Map<String, String> providedContextVal = new HashMap<String, String>();
            providedContextVal.put("time", "Integer");
            ConsumableTestDefinition testDefValContextTestRule = constructDefinition(buckets,
                    fromCompactAllocationFormat("${proctor:now()>-1}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            testDefValContextTestRule.setRule("${proctor:now()>time}");
            ProctorUtils.verifyInternallyConsistentDefinition("testELevalwithcontext", "test context recognition in test rule", testDefValContextTestRule, RuleEvaluator.FUNCTION_MAPPER,
                    ProctorUtils.convertContextToTestableMap(providedContextVal));
        }
        { //testing that invalid properties are recognized
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${ua.iPad}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextClass = new HashMap<String, String>();
            providedContextClass.put("ua", "com.indeed.proctor.common.TestRulesClass");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextClass);
            try {
                ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Class", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
                fail("expected IncompatibleTestMatrixException due to missing attribute");
            } catch (IncompatibleTestMatrixException e) {
                // expected due to incorrect spelling
            }
        }
        { //testing that valid properties are recognized
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${ua.IPad}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));
            final Map<String, String> providedContextClass = new HashMap<String, String>();
            providedContextClass.put("ua", "com.indeed.proctor.common.TestRulesClass");
            final ProvidedContext providedContext = ProctorUtils.convertContextToTestableMap(providedContextClass);
            ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Class", testDef, RuleEvaluator.FUNCTION_MAPPER, providedContext);
        }
        { //testing that invalid functions are recognized
            final ConsumableTestDefinition testDef = constructDefinition(buckets,
                    fromCompactAllocationFormat("${proctor:notafunction(5)}|-1:0.5,0:0.5,1:0.0", "-1:0.25,0:0.5,1:0.25"));

            try {
                ProctorUtils.verifyInternallyConsistentDefinition("testProvidedContextConversion", "test Provided Context Conversion Class", testDef, RuleEvaluator.FUNCTION_MAPPER, new ProvidedContext(ProvidedContext.EMPTY_CONTEXT,true));
                fail("expected IncompatibleTestMatrixException due to missing function");
            } catch (IncompatibleTestMatrixException e) {
                // expected due to incorrect spelling
            }
        }
    }


    @Test
    public void verifyAndConsolidateShouldFailIfMissingDefaultAllocation() throws IncompatibleTestMatrixException {
        final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets));
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // Allocations all have rules
                                                      fromCompactAllocationFormat("ruleA|-1:0.0,0:0.0,1:1.0", "ruleB|-1:0.5,0:0.5,1:0.0")));

            assertValid("test missing empty rule is not required", constructArtifact(tests), Collections.<String, TestSpecification>emptyMap());
            assertMissing("test missing empty rule is required", constructArtifact(Collections.<String, ConsumableTestDefinition>emptyMap()), requiredTests);
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // non-final allocation lacks a non-empty rule
                                                      fromCompactAllocationFormat("|-1:0.0,0:0.0,1:1.0", "-1:0.5,0:0.5,1:0.0")));

            assertInvalid("non-final rule lacks non-empty rule", constructArtifact(tests), requiredTests);
            assertValid("non-final rule lacks non-empty rule is allowed when not required", constructArtifact(tests), Collections.<String, TestSpecification>emptyMap());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      fromCompactAllocationFormat("ruleA|-1:0.0,0:0.0,1:1.0", "|-1:0.5,0:0.5,1:0.0")));

            assertValid("allocation with '' rule is valid for final last allocation", constructArtifact(tests), requiredTests);
        }
        // NOTE: the two test below illustrate current behavior.
        // The "${}" rule is treated as non-empty for validation.
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      // non-final allocation lacks a non-empty rule
                                                      fromCompactAllocationFormat("${}|-1:0.0,0:0.0,1:1.0", "-1:0.5,0:0.5,1:0.0")));

            assertInvalid("non-final rule of '${}' should be treated as empty rule", constructArtifact(tests), requiredTests);
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets,
                                                      fromCompactAllocationFormat("${}|-1:0.5,0:0.5,1:0.0")));

            assertValid("allocation with '${}' rule is valid for final last allocation", constructArtifact(tests), requiredTests);
        }

    }

    @Test
    public void verifyAndConsolidateShouldFailIfNoAllocations() throws IncompatibleTestMatrixException {
        List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");

        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets));
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets, Collections.<Allocation>emptyList()));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertValid("test missing allocations is not required", matrix, Collections.<String, TestSpecification>emptyMap());
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets, Collections.<Allocation>emptyList()));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertInvalid("test missing allocations is required", matrix, requiredTests);
        }
    }

    @Test
    public void unknownBucketWithAllocationGreaterThanZero() throws IncompatibleTestMatrixException {
        // The test-matrix has 3 buckets
        List<TestBucket> buckets = fromCompactBucketFormat("zero:0,one:1,two:2");
        // The proctor-specification only knows about two of the buckets
        final TestSpecification testSpecification = transformTestBuckets(fromCompactBucketFormat("zero:0,one:1"));
        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, testSpecification);

        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            // Allocation of bucketValue=2 is > 0
            final ConsumableTestDefinition testDefinition = constructDefinition(buckets, fromCompactAllocationFormat("0:0,1:0,2:1.0"));
            tests.put(TEST_A, testDefinition);

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // externally inconsistent matrix
            assertInvalid("allocation for externally unknown bucket (two) > 0", matrix, requiredTests);
            assertEquals("trivially expected only one test in the matrix", 1, matrix.getTests().size());
            final List<Allocation> allocations = matrix.getTests().values().iterator().next().getAllocations();
            assertEquals("trivially expected only one allocation in the test (same as source)", 1, allocations.size());
            final List<Range> ranges = allocations.iterator().next().getRanges();
            assertEquals("Expected the ranges to be reduced from 3 to 1, since only the fallback value is now present", 1, ranges.size());
            final Range onlyRange = ranges.iterator().next();
            assertEquals("Should have adopted the fallback value from the test spec", onlyRange.getBucketValue(), testSpecification.getFallbackValue());
            assertEquals("Trivially should have been set to 100% fallback", 1.0, onlyRange.getLength(), 0.005);
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            // Allocation of bucketValue=2 is == 0
            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("0:0.5,1:0.5,2:0")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertValid("allocation for externally unknown bucket (two) == 0", matrix, requiredTests);
        }
    }

    @Test
    public void internallyUnknownBucketWithAllocationGreaterThanZero() throws IncompatibleTestMatrixException {
         // The test-matrix has 3 buckets
        List<TestBucket> buckets = fromCompactBucketFormat("zero:0,one:1,two:2");
        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets));

        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            // Allocation has 4 buckets, bucket with non-zero allocation in an unknown bucket
            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("0:0,1:0,2:0.5,3:0.5")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally inconsistent matrix
            assertInvalid("allocation for internally unknown bucket (three) > 0", matrix, requiredTests);
        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            // There is an unknown bucket with non-zero allocation
            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("0:0,1:0,2:0.5,3:0.5")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally inconsistent matrix
            assertInvalid("allocation for internally unknown bucket (three) == 0", matrix, requiredTests);
        }
    }

    @Test
    public void requiredTestBucketsMissing() throws IncompatibleTestMatrixException {
         // The test-matrix has fewer buckets than the required tests
        List<TestBucket> buckets_matrix = fromCompactBucketFormat("zero:0,one:1");
        List<TestBucket> buckets_required = fromCompactBucketFormat("zero:0,one:1,two:2,three:3");
        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets_required));

        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets_matrix, fromCompactAllocationFormat("0:0,1:1.0")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally inconsistent matrix
            assertValid("test-matrix has a subset of required buckets", matrix, requiredTests);
        }
    }

    @Test
    public void bucketsNameAndValuesShouldBeConsistent() throws IncompatibleTestMatrixException {
        {
            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(fromCompactBucketFormat("zero:0,one:1")));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            // The Bucket Names and Values intentionally do not match
            tests.put(TEST_A, constructDefinition(fromCompactBucketFormat("zero:1,one:0"),
                                                  fromCompactAllocationFormat("0:0,1:1.0")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertValid("test-matrix has a different {bucketValue} -> {bucketName} mapping than required Tests", matrix, requiredTests);
        }
        {
            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(fromCompactBucketFormat("zero:0,one:1,two:2")));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            // The Bucket Names and Values intentionally do not match
            tests.put(TEST_A, constructDefinition(fromCompactBucketFormat("zero:0,one:2"),
                                                  fromCompactAllocationFormat("0:0,2:1.0")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertValid("test-matrix has a different {bucketValue} -> {bucketName} mapping than required Tests", matrix, requiredTests);
        }
    }

    @Test
    public void requiredTestIsMissing() throws IncompatibleTestMatrixException {
         // The test-matrix has 3 buckets
        List<TestBucket> buckets_A = fromCompactBucketFormat("zero:0,one:1,two:2");
        final TestSpecification testSpecA = transformTestBuckets(buckets_A);

        List<TestBucket> buckets_B = fromCompactBucketFormat("foo:0,bar:1");
        final TestSpecification testSpecB = transformTestBuckets(buckets_B);
        testSpecB.setFallbackValue(-2); // unusual value;

        Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, testSpecA, TEST_B, testSpecB);

        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets_A, fromCompactAllocationFormat("0:0,1:0.5,2:0.5")));

            // Artifact only has 1 of the 2 required tests
            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally inconsistent matrix
            assertNull("missing testB should not be present prior to consolidation", matrix.getTests().get(TEST_B));
            assertMissing("required testB is missing from the test matrix", matrix, requiredTests);

            final ConsumableTestDefinition consolidatedTestB = matrix.getTests().get(TEST_B);
            assertNotNull("autogenerated testB definition missing from consolidated matrix", consolidatedTestB);
            assertEquals(
                    "autogenerated testB definition should have used custom fallback value",
                    testSpecB.getFallbackValue(),
                    consolidatedTestB.getAllocations().get(0).getRanges().get(0).getBucketValue());

        }
        {
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets_A, fromCompactAllocationFormat("0:0,1:0.5,2:0.5")));
            tests.put(TEST_B, constructDefinition(buckets_B, fromCompactAllocationFormat("0:0.5,1:0.5")));

            // Artifact both of the required tests
            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("both required tests are present in the matrix", matrix, requiredTests);
        }
        {
            Map<String, TestSpecification> only_TestA_Required = ImmutableMap.of(TEST_A, transformTestBuckets(buckets_A));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();
            tests.put(TEST_A, constructDefinition(buckets_A, fromCompactAllocationFormat("0:0,1:0.5,2:0.5")));
            // Intentionally making the non-required test B allocation sum to 0.5
            tests.put(TEST_B, constructDefinition(buckets_B, fromCompactAllocationFormat("0:0,1:0.5")));

            // Artifact both of the required tests
            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertEquals(2, matrix.getTests().size());
            assertValid("required test A is present in the matrix", matrix, only_TestA_Required);

            assertEquals("Only required test A should remain in the matrix", 1, matrix.getTests().size());
            assertTrue("Only required test A should remain in the matrix", matrix.getTests().containsKey(TEST_A));
            assertFalse("Only required test A should remain in the matrix", matrix.getTests().containsKey(TEST_B));
        }
    }

    @Test
    public void verifyBucketPayloads() throws IncompatibleTestMatrixException {
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(-1L);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setLongValue(1L);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setLongValue(1L);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("all payloads of the same type", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(-1L);
            buckets.get(0).setPayload(p);
            // bucket 1 is missing a payload here.
            p = new Payload();
            p.setLongValue(1L);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("not all payloads of the test defined", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setStringValue("inact");
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setStringValue("foo");
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setStringValue("bar");
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("all payloads of the wrong type", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(-1L);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setLongValue(0L);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setStringValue("foo");
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally inconsistent matrix: different payload types in test
            assertInvalid("all payloads not of the same type", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setStringArray(new String[]{})// empty arrays are allowed.
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setStringArray(new String[]{"foo", "bar"});
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setStringArray(new String[]{"baz", "quux", "xyzzy"});
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "stringArray", null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("vector payloads can be different lengths", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2","one","val3",new ArrayList<String>()));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2","tw","val3",new ArrayList<String>(){{add("a");add("c");}}));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2","th","val3",new ArrayList<String>(){{add("foo");add("bar");}}));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","stringValue","val3","stringArray"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertValid("correct allocation and object, map with double values", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2",3.0,"val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val3",1.0,"val4",3.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",2.0,"val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","doubleValue","val3","doubleValue"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("map payloads can't have different variable names", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2","yea1","val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2","yea2","val3",3.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2","yea3","val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","doubleValue","val3","doubleValue"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("map payloads can't have different variable types than specified", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2",3.0,"val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",1.0,"val3",3.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",2.0,"val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","doubleValue","val3","doubleValue","val4","doubleArray"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("map payloads can't have less variable types than specified", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2",3.0,"val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",new ArrayList<Double>(){{add(1.0D);}},"val3",3.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",2.0,"val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","doubleValue","val3","doubleValue"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("map payloads can't have different variable types than specified -- an array instead of a single value", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2",ImmutableMap.<String,Object>of("a",1,"b",2),"val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",ImmutableMap.<String,Object>of("c",3,"d",4),"val3",3.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",ImmutableMap.<String,Object>of("e",5,"f",6),"val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","map","val3","doubleValue"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("map payloads can't nested map payloads", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2","one","val3",new ArrayList<String>()));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2","tw","val3",new ArrayList<String>(){{add("a");add("c");}}));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2","th","val3",new ArrayList(){{add(2.1D);add("bar");}}));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets,"map",
                    ImmutableMap.of("val1","doubleValue","val2","stringValue","val3","stringArray"), null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            assertInvalid("map payload nested arrays can't have multiple types", matrix, requiredTests);
        }
    }

    @Test
    public void verifyBucketPayloadValueValidators() throws IncompatibleTestMatrixException {
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setDoubleValue(0D);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setDoubleValue(10D);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setDoubleValue(20D);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "doubleValue", "${value >= 0}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("doubleValue: all payload values pass validation", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setDoubleValue(0D);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setDoubleValue(10D);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setDoubleValue(-1D);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "doubleValue", "${value >= 0}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("doubleValue: a payload value doesn't pass validation", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(0L);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setLongValue(10L);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setLongValue(20L);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", "${value >= 0}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("longValue: all payload values pass validation", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(0L);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setLongValue(10L);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setLongValue(-1L);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", "${value >= 0}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("longValue: a payload value doesn't pass validation", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setStringValue("inactive");
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setStringValue("foo");
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setStringValue("bar");
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "stringValue", "${value >= \"b\"}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("stringValue: all payload values pass validation", matrix, requiredTests);
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setStringValue("inactive");
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setStringValue("foo");
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setStringValue("abba");
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "stringValue", "${value >= \"b\"}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("stringValue: a payload value doesn't pass validation", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2",3.0,"val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",4.0,"val3",1.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",2.0,"val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "map", ImmutableMap.of("val1","doubleValue","val2","doubleValue","val3","doubleValue"), "${val1 + val2 + val3 < 10}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("map: a payload value that should pass validation", matrix, requiredTests);
        }
        {
            final List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",1.0,"val2",3.0,"val3",1.0));
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",4.0,"val3",1.0));
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setMap(ImmutableMap.<String,Object>of("val1",2.0,"val2",6.0,"val3",2.0));
            buckets.get(2).setPayload(p);

            final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "map", ImmutableMap.of("val1","doubleValue","val2","doubleValue","val3","doubleValue"), "${val1 + val2 + val3 < 10}"));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertInvalid("map: a payload value doesn't pass validation", matrix, requiredTests);
        }
    }

    public void verifyBucketPayloadArrayValidators() throws IncompatibleTestMatrixException {
        // TODO(pwp): add
    }

    @Test
    public void verifyPayloadDeploymentScenerios() throws IncompatibleTestMatrixException {
        {
            // Proctor should not break if it consumes a test matrix
            // that has a payload even if it's not expecting one.
            // (So long as all the payloads are of the same type.)
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(-1L);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setLongValue(0L);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setLongValue(1L);
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets)); // no payload type specified
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("no payload expected; payloads supplied of the same type", matrix, requiredTests);

            // Should have gotten back a test.
            assertEquals("required tests should not be removed from the matrix", 1, matrix.getTests().size());

            // Make sure we don't have any payloads in the resulting tests.
            for (Entry<String, ConsumableTestDefinition> next : matrix.getTests().entrySet()) {
                final ConsumableTestDefinition testDefinition = next.getValue();

                for (final TestBucket bucket : testDefinition.getBuckets()) {
                    assertNull(bucket.getPayload());
                }
            }
        }
        {
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
            Payload p = new Payload();
            p.setLongValue(-1L);
            buckets.get(0).setPayload(p);
            p = new Payload();
            p.setLongValue(0L);
            buckets.get(1).setPayload(p);
            p = new Payload();
            p.setStringValue("foo");
            buckets.get(2).setPayload(p);

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets)); // no payload type specified
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally inconsistent matrix: different payload types in test
            assertInvalid("no payload expected; payloads supplied of different types", matrix, requiredTests);
        }
        {
            // Proctor should not break if it consumes a test matrix
            // with no payloads when it is expecting one.
            List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");

            Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets, "longValue", null));
            final Map<String, ConsumableTestDefinition> tests = Maps.newHashMap();

            tests.put(TEST_A, constructDefinition(buckets, fromCompactAllocationFormat("-1:0.2,0:0.4,1:0.4")));

            final TestMatrixArtifact matrix = constructArtifact(tests);

            // internally consistent matrix
            assertValid("payload expected; no payloads supplied", matrix, requiredTests);
            // Should have gotten back a test.
            assertEquals("required tests should not be removed from the matrix", 1, matrix.getTests().size());
        }
    }

    @Test
    public void testCompactAllocationFormat() {
//        List<Allocation> allocations_empty = fromCompactAllocationFormat("");
//        assertEquals(0, allocations_empty.size());
        double DELTA = 0;

        List<Allocation> allocations = fromCompactAllocationFormat("ruleA|10:0,11:0.5,12:0.5", "0:0,1:0.5,2:0.5");
        assertEquals(2, allocations.size());
        {
            Allocation allocationA = allocations.get(0);
            assertEquals("ruleA", allocationA.getRule());
            assertEquals(3, allocationA.getRanges().size());
            assertEquals(10, allocationA.getRanges().get(0).getBucketValue());
            assertEquals(0, allocationA.getRanges().get(0).getLength(), DELTA);
            assertEquals(11, allocationA.getRanges().get(1).getBucketValue());
            assertEquals(0.5, allocationA.getRanges().get(1).getLength(), DELTA);
            assertEquals(12, allocationA.getRanges().get(2).getBucketValue());
            assertEquals(0.5, allocationA.getRanges().get(2).getLength(), DELTA);
        }
        {
            Allocation allocationB = allocations.get(1);
            assertNull(allocationB.getRule());
            assertEquals(3, allocationB.getRanges().size());
            assertEquals(0, allocationB.getRanges().get(0).getBucketValue());
            assertEquals(0, allocationB.getRanges().get(0).getLength(), DELTA);
            assertEquals(1, allocationB.getRanges().get(1).getBucketValue());
            assertEquals(0.5, allocationB.getRanges().get(1).getLength(), DELTA);
            assertEquals(2, allocationB.getRanges().get(2).getBucketValue());
            assertEquals(0.5, allocationB.getRanges().get(2).getLength(), DELTA);
        }
    }

    @Test
    public void testCompactBucketFormatHelperMethods() {
//        List<TestBucket> buckets_empty = fromCompactBucketFormat("");
//        assertEquals(0, buckets_empty.size());
        List<TestBucket> buckets = fromCompactBucketFormat("zero:0,one:1,two:2");

        assertEquals(3, buckets.size());
        assertEquals("zero", buckets.get(0).getName());
        assertEquals(0, buckets.get(0).getValue());
        assertEquals("one", buckets.get(1).getName());
        assertEquals(1, buckets.get(1).getValue());
        assertEquals("two", buckets.get(2).getName());
        assertEquals(2, buckets.get(2).getValue());
    }

    @Test
    public void testUnrecognizedTestType() throws Exception {
        final InputStream input = Preconditions.checkNotNull(getClass().getResourceAsStream(PATH_UNKNOWN_TEST_TYPE), "Missing test definition");
        final ConsumableTestDefinition test = Serializers.lenient().readValue(input, ConsumableTestDefinition.class);

        List<TestBucket> buckets = fromCompactBucketFormat("inactive:-1,control:0,test:1");
        final Map<String, TestSpecification> requiredTests = ImmutableMap.of(TEST_A, transformTestBuckets(buckets));
        final Map<String, ConsumableTestDefinition> tests = ImmutableMap.of(TEST_A, test);

        {
            final TestMatrixArtifact matrix = constructArtifact(tests);

            // verifyAndConsolidate should not throw an error because the 'invalidbuckets' test is not required.
            assertEquals(1, matrix.getTests().size());
            assertInvalid("Test not recognized, replaced with 'invalid' marker test", matrix, requiredTests);
            assertEquals(1, matrix.getTests().size());
            final ConsumableTestDefinition replacement = matrix.getTests().values().iterator().next();
            assertEquals(TestType.RANDOM, replacement.getTestType());
            assertEquals(1, replacement.getBuckets().size());
            assertEquals(-1, replacement.getBuckets().iterator().next().getValue());
        }

        final TestType unrecognizedTestType = TestType.register("UNRECOGNIZED");
        {
            final TestMatrixArtifact matrix = constructArtifact(tests);

            // verifyAndConsolidate should not throw an error because the 'invalidbuckets' test is not required.
            assertEquals(1, matrix.getTests().size());
            final ConsumableTestDefinition original = matrix.getTests().values().iterator().next();
            assertEquals("Expected only the control and test buckets", 2, original.getBuckets().size());

            assertValid("Test now valid", matrix, requiredTests);
            assertEquals(1, matrix.getTests().size());
            final ConsumableTestDefinition stillOriginal = matrix.getTests().values().iterator().next();
            assertEquals(unrecognizedTestType, stillOriginal.getTestType());
            assertEquals("Expected only the control and test buckets", 2, stillOriginal.getBuckets().size());
        }
    }



    /* Test Helper Methods Below */

    private void assertInvalid(String msg, TestMatrixArtifact matrix, Map<String, TestSpecification> requiredTests) throws IncompatibleTestMatrixException {
        assertErrorCreated(false, true, msg, matrix, requiredTests);
    }
    private void assertMissing(String msg, TestMatrixArtifact matrix, Map<String, TestSpecification> requiredTests) throws IncompatibleTestMatrixException {
        assertErrorCreated(true, false, msg, matrix, requiredTests);
    }
    private void assertValid(String msg, TestMatrixArtifact matrix, Map<String, TestSpecification> requiredTests) throws IncompatibleTestMatrixException {
        assertErrorCreated(false, false, msg, matrix, requiredTests);
    }
    private void assertErrorCreated(boolean hasMissing, boolean hasInvalid, String msg, TestMatrixArtifact matrix, Map<String, TestSpecification> requiredTests) throws IncompatibleTestMatrixException {
        final ProctorLoadResult proctorLoadResult = ProctorUtils.verifyAndConsolidate(matrix, "[ testcase: " + msg + " ]", requiredTests, RuleEvaluator.FUNCTION_MAPPER);

        final Set<String> missingTests = proctorLoadResult.getMissingTests();
        assertEquals(msg + " missing tests is not empty", hasMissing, !missingTests.isEmpty());

        final Set<String> testsWithErrors = proctorLoadResult.getTestsWithErrors();
        assertEquals(msg+ " invalid tests is not empty", hasInvalid, !testsWithErrors.isEmpty());
    }


    private TestMatrixArtifact constructArtifact(Map<String, ConsumableTestDefinition> tests) {
        final TestMatrixArtifact matrix = new TestMatrixArtifact();

        matrix.setAudit(constructAudit());

        matrix.setTests(tests);
        return matrix;
    }

    private Audit constructAudit() {
        final Audit audit = new Audit();
        audit.setVersion("1");
        audit.setUpdatedBy("unit test");
        audit.setUpdated(1337133701337L);
        return audit;
    }

    private ConsumableTestDefinition constructDefinition(List<TestBucket> buckets,
                                                         List<Allocation> allocations) {

        final ConsumableTestDefinition test = new ConsumableTestDefinition();
        test.setVersion(""); // don't care about version for this test
        test.setSalt(null); // don't care about salt for this test
        test.setRule(null); // don't care about rule for this test
        test.setTestType(TestType.ANONYMOUS_USER);    // don't really care, but need a valid value
        test.setConstants(Collections.<String, Object>emptyMap()); // don't care about constants for this test

        test.setBuckets(buckets);
        test.setAllocations(allocations);
        return test;
    }

    /*  *********************************************************************
        The compact format is used because it's easier to quickly list bucket
        allocations and bucket values than to use string JSON
     *  ********************************************************************* */

    private List<Allocation> fromCompactAllocationFormat(String ... allocations) {
        final List<String> allocationList = Lists.newArrayListWithExpectedSize(allocations.length);
        for(String s : allocations) {
            allocationList.add(s);
        }
        return fromCompactAllocationFormat(allocationList);
    }
    private List<Allocation> fromCompactAllocationFormat(List<String> allocations) {
        final List<Allocation> allocationList = Lists.newArrayListWithExpectedSize(allocations.size());
        // rule|0:0,0:.0.1,0:.2
        for(String allocation : allocations) {
            final String[] parts = allocation.split("\\|");
            final String rule;
            final String sRanges;
            if(parts.length == 1) {
                rule = null;
                sRanges = parts[0];
            } else if (parts.length == 2) {
                rule = parts[0];
                sRanges = parts[1];
            } else {
                System.out.println("parts : " + parts.length);
                throw new IllegalArgumentException("Invalid compact allocation format [" + allocation + "], expected: rule|<bucketValue>:<length>, ...<bucketValue-N>:<length-N>.");
            }
            String[] allRanges = sRanges.split(",");
            final List<Range> ranges = Lists.newArrayListWithCapacity(allRanges.length);
            for(String sRange : allRanges) {
                // Could handle index-out of bounds + number formatting exception better.
                String[] rangeParts = sRange.split(":");
                ranges.add(new Range(Integer.parseInt(rangeParts[0], 10), Double.parseDouble(rangeParts[1])));
            }
            allocationList.add(new Allocation(rule, ranges));
        }
        return allocationList;
    }

    private List<TestBucket> fromCompactBucketFormat(String sBuckets){
        String[] bucketParts = sBuckets.split(",");
        List<TestBucket> buckets = Lists.newArrayListWithCapacity(bucketParts.length);
        for(int i = 0; i < bucketParts.length; i++) {
            // Could handle index-out of bounds + number formatting exception better.
            final String[] nameAndValue = bucketParts[i].split(":");
            buckets.add(new TestBucket(nameAndValue[0], Integer.parseInt(nameAndValue[1]), "bucket " + i, null));
        }
        return buckets;
    }

    private TestSpecification transformTestBuckets(List<TestBucket> testBuckets) {
        TestSpecification testSpec = new TestSpecification();
        Map<String, Integer> buckets = Maps.newLinkedHashMap();
        for(TestBucket b : testBuckets) {
            buckets.put(b.getName(), b.getValue());
        }
        testSpec.setBuckets(buckets);
        return testSpec;
    }
    private TestSpecification transformTestBuckets(final List<TestBucket> testBuckets, final String payloadType, final Map<String,String> schema, final String validator) {
        TestSpecification testSpec = transformTestBuckets(testBuckets);
        PayloadSpecification payloadSpec = new PayloadSpecification();
        payloadSpec.setType(payloadType);
        payloadSpec.setValidator(validator);
        payloadSpec.setSchema(schema);
        testSpec.setPayload(payloadSpec);
        return testSpec;
    }
    private TestSpecification transformTestBuckets(List<TestBucket> testBuckets, String payloadType, String validator) {
        TestSpecification testSpec = transformTestBuckets(testBuckets);
        PayloadSpecification payloadSpec = new PayloadSpecification();
        payloadSpec.setType(payloadType);
        payloadSpec.setValidator(validator);
        testSpec.setPayload(payloadSpec);
        return testSpec;
    }


    //
}
TOP

Related Classes of com.indeed.proctor.common.TestProctorUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.