package com.adobe.prefs.testing;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.prefs.NodeChangeListener;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import java.util.prefs.PreferencesFactory;
import static java.util.Arrays.asList;
import static org.testng.Assert.*;
/**
* Acceptance test suite for Preferences implementations.
*/
public abstract class PreferencesAcceptanceTest<F extends PreferencesFactory, P extends Preferences> extends PreferencesTestSupport {
private final Class<F> factoryClass;
private final Class<P> preferencesClass;
/**
* This constructor is here only to make it easy to test platform implementations
* bundled in the JDK (like file-system, MacOSX or Windows), as those classes are usually
* package-private.
*/
@SuppressWarnings("unused")
protected PreferencesAcceptanceTest(String factoryClassName, String preferencesClassName, long delay) throws Exception {
this((Class<F>) Class.forName(factoryClassName),
(Class<P>) Class.<Class<Preferences>>forName(preferencesClassName),delay);
}
protected PreferencesAcceptanceTest(Class<F> factoryClass,
Class<P> preferencesClass,
long delay) {
super(delay);
System.setProperty(PreferencesFactory.class.getName(), factoryClass.getName());
this.factoryClass = factoryClass;
this.preferencesClass = preferencesClass;
}
protected PreferencesAcceptanceTest(final long delay) {
super(delay);
final Type[] typeArgs = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
factoryClass = (Class<F>) typeArgs[0];
preferencesClass = (Class<P>) typeArgs[1];
}
@BeforeClass
public void checkFactoryInitialization() throws Exception {
Field factory = Preferences.class.getDeclaredField("factory");
factory.setAccessible(true);
assertEquals(factory.get(null).getClass(), factoryClass);
factory.setAccessible(false);
}
@Test(groups = "factory", timeOut = 500L)
public void shouldReturnTheSameRoot() {
assertSame(Preferences.systemRoot(), Preferences.systemRoot());
// this is not explicitly required by the javadoc, but it's "implied"
// that the same instance should be returned if the "user context" hasn't changed.
assertSame(Preferences.userRoot(), Preferences.userRoot());
}
@Test(groups = "factory", timeOut = 500L)
public void userAndSystemRootsShouldBeDifferent() {
assertNotEquals(Preferences.systemRoot(), Preferences.userRoot());
}
@Test(dataProvider = "root", groups = "factory", timeOut = 500L)
public void shouldCreatePreferencesOfExpectedType(Preferences root) {
assertEquals(root.getClass(), preferencesClass);
}
@Test(dataProvider = "root", groups = "prefs", dependsOnGroups = "factory", timeOut = 500L)
public void rootAbsolutePathMustBeSlash(Preferences root) {
assertEquals(root.absolutePath(), "/");
}
@Test(groups = "prefs", dependsOnGroups = "factory", timeOut = 500L)
public void rootNodesShouldBeFlagged() {
assertFalse(Preferences.systemRoot().isUserNode(), "systemRoot flagged as userNode");
assertTrue(Preferences.userRoot().isUserNode(), "userRoot not flagged as userNode");
}
@Test(dataProvider = "root", priority = -1, groups = {"prefs", "hierarchy"},
dependsOnGroups = "factory", timeOut = 1000L)
public void shouldIsolateRootNodes(Preferences root) throws Exception {
assertFalse(root.nodeExists(namespace)); // the second invocation fails if the two root nodes are the same
final Preferences prefs = root.node(namespace);
assertTrue(root.nodeExists(namespace));
assertSame(root.node('/' + namespace), prefs);
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy"}, dependsOnGroups = "factory", timeOut = 1000L)
public void shouldCreateChildren(Preferences prefs) throws Exception {
final String childName = "child";
assertFalse(prefs.nodeExists(childName));
final Preferences child = prefs.node(childName);
assertTrue(prefs.nodeExists(childName));
assertSame(prefs.node(childName), child, "not found by name");
assertSame(prefs.node(child.absolutePath()), child, "not found by path using parent");
assertSame(child.node(child.absolutePath()), child, "not found by path using itself");
assertEquals(child.name(), childName, "wrong child name");
assertEquals(child.absolutePath(), prefs.absolutePath() + '/' + childName, "wrong child path");
assertSame(child.parent(), prefs, "wrong child parent");
assertEquals(child.isUserNode(), prefs.isUserNode(), "wrong userNode flag");
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy"}, dependsOnGroups = "factory", timeOut = 1000L)
public void shouldCreateHierarchy(Preferences prefs) throws Exception {
final Preferences a1 = prefs.node("a1");
final Preferences a2 = a1.node("a2");
final Preferences a3 = a2.node("a3");
assertSame(prefs.node("a1/a2/a3"), a3);
assertSame(a1.parent(), prefs, "wrong parent");
assertSame(a2.parent(), a1, "wrong parent");
assertSame(a3.parent(), a2, "wrong parent");
final Preferences b3 = prefs.node("b1/b2/b3");
assertSame(b3.parent(), prefs.node("b1/b2"));
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy"}, priority = 1,
dependsOnMethods = "shouldCreateHierarchy", expectedExceptions = IllegalStateException.class,
timeOut = 1000L)
public void shouldRemoveChildren(Preferences prefs) throws Exception {
assertTrue(prefs.nodeExists("a1/a2/a3"), "precondition failed");
prefs.node("a1/a2/a3").removeNode();
assertFalse(prefs.nodeExists("a1/a2/a3"), "node not removed");
assertTrue(prefs.nodeExists("a1/a2"), "parent node removed by accident");
final Preferences a1 = prefs.node("a1");
a1.removeNode();
assertFalse(prefs.nodeExists("a1/a2"), "descendant not removed");
assertFalse(prefs.nodeExists("a1"), "node with descendants not removed");
a1.get("k", "v");
}
@Test(dataProvider = "chroot", groups = {"prefs", "kv"}, dependsOnGroups = "factory", timeOut = 1000L)
public void shouldStoreKeyValues(Preferences prefs) {
final Preferences container = prefs.node("container");
container.put("k", "v");
assertEquals(container.get("k", ""), "v");
container.putInt("k", 3);
assertEquals(container.get("k", ""), "3");
assertEquals(container.getInt("k", 0), 3);
}
@Test(dataProvider = "chroot", groups = {"prefs", "kv"},
dependsOnMethods = "shouldStoreKeyValues", timeOut = 1000L)
public void shouldRemoveKeys(Preferences prefs) throws Exception {
final Preferences container = prefs.node("container");
assertTrue(asList(container.keys()).contains("k"));
container.remove("k");
assertFalse(asList(container.keys()).contains("k"));
assertEquals(container.get("k", null), null);
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy", "kv"},
dependsOnGroups = "factory", timeOut = 1000L)
public void shouldIsolateKeysAndChildren(Preferences prefs) throws Exception {
final Preferences mixed = prefs.node("mixed");
final String keyOnly = "keyOnly";
final String childOnly = "childOnly";
mixed.putByteArray(keyOnly, new byte[] {11, 12});
assertTrue(asList(mixed.keys()).contains(keyOnly));
assertNotNull(mixed.get(keyOnly, null));
assertFalse(mixed.nodeExists(keyOnly));
assertFalse(asList(mixed.childrenNames()).contains(keyOnly));
mixed.node(childOnly);
assertTrue(mixed.nodeExists(childOnly));
assertTrue(asList(mixed.childrenNames()).contains(childOnly));
assertFalse(asList(mixed.keys()).contains(childOnly));
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy", "kv"},
dependsOnMethods = "shouldIsolateKeysAndChildren", timeOut = 1000L)
public void shouldAllowSameNameForBoth(Preferences prefs) throws Exception {
final Preferences mixed = prefs.node("mixed");
final String both = "bothKeyAndChild";
mixed.put(both, "value");
assertTrue(asList(mixed.keys()).contains(both));
final Preferences child = mixed.node(both);
assertNotNull(child);
assertTrue(mixed.nodeExists(both));
assertTrue(asList(mixed.keys()).contains(both));
assertNotNull(mixed.get(both, null));
assertTrue(asList(mixed.childrenNames()).contains(both));
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy", "notifications"},
dependsOnGroups = "factory", timeOut = 1000L)
public void shouldTriggerNodeAdded(Preferences prefs) throws Exception {
final Preferences watched = prefs.node("watched");
final NodeChangeListener listener = nodeListener(watched);
final Preferences n1 = watched.node("n1");
testNodeAdded(listener, watched, n1.name(), 1).get();
watched.node("n1"); // not a new node, so the invocation count should stay the same
prefs.node("n0"); // shouldn't trigger anything for our listener, as it's a parent event
testNodeAdded(listener, watched, n1.name(), 1).get();
final Preferences n2 = watched.node("n2");
testNodeAdded(listener, watched, n2.name(), 2).get();
watched.node("n3/nested");
testNodeAdded(listener, watched, "n3", 3).get();
}
@Test(dataProvider = "chroot", groups = {"prefs", "hierarchy", "notifications"},
dependsOnMethods = "shouldTriggerNodeAdded", timeOut = 1000L)
public void shouldTriggerNodeRemoved(Preferences prefs) throws Exception {
final Preferences watched = prefs.node("watched");
final NodeChangeListener listener = nodeListener(watched);
watched.node("n1").removeNode();
testNodeRemoved(listener, watched, "n1", 1).get();
watched.node("n2").removeNode();
testNodeRemoved(listener, watched, "n2", 2).get();
watched.node("n3/nested").removeNode();
testNodeRemoved(listener, watched, "n2", 2).get();
watched.node("n3").removeNode();
testNodeRemoved(listener, watched, "n3", 3).get();
}
@Test(dataProvider = "chroot", groups = {"prefs", "kv", "notifications"},
dependsOnGroups = "factory", timeOut = 1000L)
public void shouldTriggerPreferenceChange(Preferences prefs) throws Exception {
final Preferences watched = prefs.node("watchedContainer");
final PreferenceChangeListener listener = prefListener(watched);
watched.putFloat("key", 1.2F);
testPrefChange(listener, watched, "key", "1.2", 1).get();
watched.remove("key");
testPrefChange(listener, watched, "key", null, 2).get();
}
@Test(dataProvider = "chroot", groups = {"prefs", "concurrency"},
dependsOnGroups = {"hierarchy", "kv"}, invocationCount = 5, threadPoolSize = 3, timeOut = 3000L)
public void concurrencyTest(Preferences prefs) throws Exception {
final Preferences child = prefs.node(UUID.randomUUID().toString());
final String key = UUID.randomUUID().toString();
final NodeChangeListener nodeListener = nodeListener(child);
final PreferenceChangeListener prefListener = prefListener(child);
prefs.putInt(key, 12);
final Preferences grandson = child.node("grandson");
ScheduledFuture<?> nodeAdded = testNodeAdded(nodeListener, child, grandson.name(), 1);
child.putBoolean(key, true);
ScheduledFuture<?> prefSet = testPrefChange(prefListener, child, key, "true", 1);
assertEquals(prefs.getInt(key, 0), 12);
assertTrue(child.getBoolean(key, false));
grandson.removeNode();
ScheduledFuture<?> nodeRemoved = testNodeRemoved(nodeListener, child, grandson.name(), 1);
prefSet.get();
child.remove(key);
child.removeNode();
assertEquals(prefs.getInt(key, 0), 12);
nodeAdded.get();
nodeRemoved.get();
testPrefChange(prefListener, child, key, null, 2).get();
}
}