/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ package com.analog.lyric.options.tests; import static com.analog.lyric.util.test.ExceptionTester.*; import static org.junit.Assert.*; import java.io.Serializable; import org.eclipse.jdt.annotation.Nullable; import org.junit.Test; import com.analog.lyric.dimple.exceptions.DimpleException; import com.analog.lyric.options.BooleanOptionKey; import com.analog.lyric.options.ClassOptionKey; import com.analog.lyric.options.DoubleListOptionKey; import com.analog.lyric.options.DoubleOptionKey; import com.analog.lyric.options.DoubleRangeOptionKey; import com.analog.lyric.options.EnumOptionKey; import com.analog.lyric.options.GenericOptionKey; import com.analog.lyric.options.IOptionHolder; import com.analog.lyric.options.IOptionKey; import com.analog.lyric.options.IntegerListOptionKey; import com.analog.lyric.options.IntegerOptionKey; import com.analog.lyric.options.LongOptionKey; import com.analog.lyric.options.OptionDoubleList; import com.analog.lyric.options.OptionIntegerList; import com.analog.lyric.options.OptionKey; import com.analog.lyric.options.OptionKeys; import com.analog.lyric.options.OptionStringList; import com.analog.lyric.options.OptionValidationException; import com.analog.lyric.options.StringListOptionKey; import com.analog.lyric.options.StringOptionKey; import com.analog.lyric.util.test.SerializationTester; public class TestOptionKey { public static final BooleanOptionKey YES = new BooleanOptionKey(TestOptionKey.class, "YES"); public static final BooleanOptionKey LOCAL_YES = new BooleanOptionKey(TestOptionKey.class, "LOCAL_YES", false, IOptionKey.Lookup.LOCAL); public static final IOptionKey<Double> P = new DoubleOptionKey(TestOptionKey.class, "P"); public static final IOptionKey<Double> LOCAL_P = new DoubleOptionKey(TestOptionKey.class, "LOCAL_P", 0.0, 0.0, 1.0, IOptionKey.Lookup.LOCAL); public static final IOptionKey<Integer> I = new IntegerOptionKey(TestOptionKey.class, "I"); public static final IOptionKey<Integer> LOCAL_I = new IntegerOptionKey(TestOptionKey.class, "LOCAL_I", 0, 0, 100, IOptionKey.Lookup.LOCAL); public static final IOptionKey<String> S = new StringOptionKey(TestOptionKey.class, "S"); public static final IOptionKey<String> LOCAL_S = new StringOptionKey(TestOptionKey.class, "LOCAL_S", "", IOptionKey.Lookup.LOCAL); public static final IOptionKey<String> G = new GenericOptionKey<String>(TestOptionKey.class, "G", String.class, "g"); public static final StringListOptionKey SL0 = new StringListOptionKey(TestOptionKey.class, "SL0"); public static final StringListOptionKey SL2 = new StringListOptionKey(TestOptionKey.class, "SL2", "a", "b"); public static final DoubleListOptionKey DL0 = new DoubleListOptionKey(TestOptionKey.class, "DL0"); public static final DoubleListOptionKey DL2 = new DoubleListOptionKey(TestOptionKey.class, "DL2", 2.3, 4.5); public static final DoubleRangeOptionKey UNBOUNDED = new DoubleRangeOptionKey(TestOptionKey.class, "UNBOUNDED"); public static final DoubleRangeOptionKey UNIT = new DoubleRangeOptionKey(TestOptionKey.class, "UNIT", 0.0, 1.0); public static final IntegerListOptionKey IL0 = new IntegerListOptionKey(TestOptionKey.class, "IL0"); public static final IntegerListOptionKey IL2 = new IntegerListOptionKey(TestOptionKey.class, "IL2", 23, 45); public static final IntegerOptionKey DIGIT = new IntegerOptionKey(TestOptionKey.class, "DIGIT", 0, 0, 9); public static final DoubleOptionKey PROB = new DoubleOptionKey(TestOptionKey.class, "PROB", .5, 0.0, 1.0); public static final LongOptionKey LONG_BOUNDED = new LongOptionKey(TestOptionKey.class, "LONG_BOUNDED", 2L, 0L, 0xFFFFFFFFFFL); public static final ClassOptionKey<CharSequence> CLASS = new ClassOptionKey<>(TestOptionKey.class, "CLASS", CharSequence.class, String.class); public static enum Color { RED, GREEN, BLUE; } public static final EnumOptionKey<Color> COLOR = new EnumOptionKey<Color>(TestOptionKey.class, "COLOR", Color.class, Color.RED); public static final EnumOptionKey<Color> LOCAL_COLOR = new EnumOptionKey<Color>(TestOptionKey.class, "LOCAL_COLOR", Color.class, Color.RED, IOptionKey.Lookup.LOCAL); @SuppressWarnings("null") public static enum Option implements IOptionKey<Serializable> { A(42), B("barf"), C(1.0); private final Serializable _defaultValue; private Option(Serializable defaultValue) { _defaultValue = defaultValue; } @Override public @Nullable Object convertToExternal(Serializable value) { return value; } @Override public Serializable convertToValue(@Nullable Object value) { return type().cast(value); } @Override public Class<Serializable> type() { return Serializable.class; } @Override public Serializable defaultValue() { return _defaultValue; } @Override public @Nullable Serializable getOrDefault(IOptionHolder holder) { return holder.getOptionOrDefault(this); } @Override public @Nullable Serializable get(IOptionHolder holder) { return holder.getOption(this); } @Override public boolean local() { return false; } @Override public IOptionKey.Lookup lookupMethod() { return IOptionKey.Lookup.NONLOCAL; } @Override public void set(IOptionHolder holder, Serializable value) { holder.setOption(this, value); } @Override public void unset(IOptionHolder holder) { holder.unsetOption(this); } @Override public boolean validForDelegator(Serializable value, IOptionHolder delegator) { return true; } @Override public Serializable validate(Serializable value, IOptionHolder optionHolder) { return type().cast(value); } } private static enum NotAnOptionKey { INSTANCE; } @Test public void test() { for (IOptionKey<?> key : OptionKeys.declaredInClass(getClass()).values()) { assertOptionInvariants(key); } for (Option key : Option.values()) { assertOptionInvariants(key); } // Test IntegerOptionKey assertEquals((Integer)3, DIGIT.validate(3, null)); assertEquals((Integer)0, DIGIT.validate(0, null)); assertEquals((Integer)9, DIGIT.validate(9, null)); expectThrow(OptionValidationException.class, DIGIT, "validate", -1, null); expectThrow(OptionValidationException.class, DIGIT, "validate", 10, null); assertEquals(0, DIGIT.lowerBound()); assertEquals(9, DIGIT.upperBound()); assertEquals((Integer)42, I.convertToValue(42.0)); try { I.convertToValue(42.5); fail("exception expected"); } catch (IllegalArgumentException ex) { } try { I.convertToValue("42.5"); fail("exception expected"); } catch (ClassCastException ex) { } // Test LongOptionKey assertEquals(0L, LONG_BOUNDED.lowerBound()); assertEquals(0xFFFFFFFFFFL, LONG_BOUNDED.upperBound()); assertEquals((Long)LONG_BOUNDED.lowerBound(), LONG_BOUNDED.validate(LONG_BOUNDED.lowerBound(), null)); assertEquals((Long)LONG_BOUNDED.upperBound(), LONG_BOUNDED.validate(LONG_BOUNDED.upperBound(), null)); expectThrow(OptionValidationException.class, LONG_BOUNDED, "validate", LONG_BOUNDED.lowerBound() - 1, null); expectThrow(OptionValidationException.class, LONG_BOUNDED, "validate", LONG_BOUNDED.upperBound() + 1, null); assertEquals((Long)23L, LONG_BOUNDED.convertToValue(23.0)); expectThrow(IllegalArgumentException.class, LONG_BOUNDED, "convertToValue", (Object)23.4); // Test DoubleOptionKey assertEquals(0.0, PROB.validate(0.0, null), 0.0); assertEquals(1.0, PROB.validate(1.0, null), 0.0); assertEquals(.3, PROB.validate(.3, null), 0.0); expectThrow(OptionValidationException.class, PROB, "validate", -0.0001, null); expectThrow(OptionValidationException.class, PROB, "validate", 1.0001, null); expectThrow(OptionValidationException.class, PROB, "validate", Double.NaN, null); assertEquals(0.0, PROB.lowerBound(), 0.0); assertEquals(1.0, PROB.upperBound(), 0.0); // Test EnumOptionKey assertEquals(Color.RED, COLOR.defaultValue()); assertEquals(Color.RED, COLOR.convertToValue("RED")); assertEquals(Color.BLUE, COLOR.convertToValue("BLUE")); expectThrow(IllegalArgumentException.class, COLOR, "convertToValue", "gag"); expectThrow(IllegalArgumentException.class, COLOR, "convertToValue", "blue"); // case-sensitive // Test ClassOptionKey assertEquals(String.class, CLASS.defaultValue()); assertEquals(CharSequence.class, CLASS.superClass()); assertEquals(String.class, CLASS.convertToValue("java.lang.String")); expectThrow(ClassCastException.class, CLASS, "convertToValue", new int[] {2}); assertSame(Object.class, CLASS.convertToValue(Object.class)); // Does not validate expectThrow(OptionValidationException.class, "Could not construct class.*", CLASS, "convertToValue", "bogus"); expectThrow(OptionValidationException.class, ".*is not a subclass.*", CLASS, "validate", Object.class, null); // Test list keys // ExampleOptionHolder holder = new ExampleOptionHolder(); assertTrue(SL0.defaultValue().isEmpty()); assertArrayEquals(new Object[] { "a", "b" }, SL2.defaultValue().toArray()); OptionKey<String> rogueKey = new StringOptionKey(Object.class, "foo"); assertNull(OptionKey.getCanonicalInstance(rogueKey)); OptionKey<String> rogueKey2 = SerializationTester.clone(rogueKey); assertNotSame(rogueKey2, rogueKey); assertSame(rogueKey.getDeclaringClass(), rogueKey2.getDeclaringClass()); assertEquals(rogueKey.name(), rogueKey2.name()); assertEquals(rogueKey.defaultValue(), rogueKey2.defaultValue()); // DoubleRangeOptionKey expectThrow(OptionValidationException.class, "Expected valid double range.*", UNBOUNDED, "validate", new OptionDoubleList(1.0), null); expectThrow(OptionValidationException.class, "Expected valid double range.*", UNBOUNDED, "validate", new OptionDoubleList(1.0, 0.0), null); // // Error cases // expectThrow(DimpleException.class, "Error loading option key 'INSTANCE'.*", OptionKey.class, "inClass", NotAnOptionKey.class, "INSTANCE"); expectThrow(DimpleException.class, "Error loading option key 'NOT_AN_OPTION'.*", OptionKey.class, "inClass", Option.class, "NOT_AN_OPTION"); expectThrow(DimpleException.class, "'frob' is not a canonical option key name", OptionKey.class, "forCanonicalName", "frob"); expectThrow(DimpleException.class, ".*ClassNotFoundException.*", OptionKey.class, "forCanonicalName", "no.such.package.NoSuchClass"); } <T extends Serializable> void assertOptionInvariants(IOptionKey<T> key) { Class<? extends T> type = key.type(); Class<?> declaringClass = key.getDeclaringClass(); assertEquals(key.local(), key.lookupMethod()== IOptionKey.Lookup.LOCAL); assertEquals(key.local(), key.name().startsWith("LOCAL_")); assertNotNull(key.name()); assertNotNull(type); assertTrue(type.isInstance(key.defaultValue())); assertEquals(key.defaultValue(), key.convertToValue(key.defaultValue())); assertEquals(key.defaultValue(), key.convertToValue(key.convertToExternal(key.defaultValue()))); assertEquals(key.defaultValue(), key.validate(key.defaultValue(), null)); if (declaringClass.isEnum()) { assertEquals(key.name(), key.toString()); } else { assertEquals(OptionKey.qualifiedName(key), key.toString()); } IOptionKey<?> key3 = OptionKey.inClass(key.getDeclaringClass(), key.name()); assertSame(key3, key); IOptionKey<?> key4 = OptionKey.forCanonicalName(OptionKey.canonicalName(key)); assertSame(key, key4); IOptionKey<?> key2 = SerializationTester.clone(key); assertSame(key2, key); assertEquals(declaringClass.getName() + "." + key.name(), OptionKey.canonicalName(key)); assertEquals(declaringClass.getSimpleName() + "." + key.name(), OptionKey.qualifiedName(key)); if (key instanceof OptionKey) { assertEquals(OptionKey.canonicalName(key), ((OptionKey<?>)key).canonicalName()); assertEquals(OptionKey.qualifiedName(key), ((OptionKey<?>)key).qualifiedName()); } ExampleOptionHolder holder = new ExampleOptionHolder(); assertEquals(key.defaultValue(), key.getOrDefault(holder)); for (Object newValue : new Object[] { "foo", 7, .314159, new OptionStringList("foo", "bar"), new OptionDoubleList(1.324, 234234.2), new OptionIntegerList(2,3,4,5)} ) { if (type.isInstance(newValue)) { key.set(holder, type.cast(newValue)); assertEquals(newValue, key.get(holder)); assertEquals(newValue, key.getOrDefault(holder)); key.unset(holder); assertNull(key.get(holder)); assertEquals(key.defaultValue(), key.getOrDefault(holder)); if (key instanceof OptionKey) { ((OptionKey<?>)key).convertAndSet(holder, newValue); assertEquals(newValue, key.get(holder)); assertEquals(newValue, key.getOrDefault(holder)); key.unset(holder); } } } } }