package com.laytonsmith.persistence; import com.laytonsmith.PureUtilities.Common.FileUtil; import com.laytonsmith.PureUtilities.Common.Misc; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.PureUtilities.ZipReader; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import com.laytonsmith.persistence.io.ReadWriteFileConnection; import com.laytonsmith.testing.StaticTest; import static com.laytonsmith.testing.StaticTest.GetPrivate; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.junit.After; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; /** * */ public class TestPersistence { /** * TODO: Need to test the following: * Ensuring correct behavior with hidden keys that conflict */ public TestPersistence() { } public Map<String[], String> testData = new HashMap<String[], String>(); List<File> toDelete = new ArrayList<File>(); ConnectionMixinFactory.ConnectionMixinOptions options; DaemonManager dm; @BeforeClass public static void setUpClass() throws Exception { StaticTest.InstallFakeServerFrontend(); } @Before public void setUp() throws Exception { testData.put(new String[]{"a", "b"}, "value1"); testData.put(new String[]{"a", "b", "c1"}, "value2"); testData.put(new String[]{"a", "b", "c2"}, "value3"); options = new ConnectionMixinFactory.ConnectionMixinOptions(); options.setWorkingDirectory(new File(".")); dm = new DaemonManager(); new File("folder").mkdir(); } @After public void tearDown() { for (File f : toDelete) { f.delete(); } } // @Test // public void testYML() { // assertEquals("a:\n" // + " b: {c1: value2, c2: value3, _: value1}\n", doOutput("yml://test.yml", testData)); // } // // @Test // public void testYMLPretty() { // assertEquals("a:\n" // + " b: {\n" // + " c1: value2,\n" // + " c2: value3,\n" // + " _: value1\n" // + " }\n", doOutput("prettyprint:yml://testpretty.yml", testData)); // } //Dumb properties get loaded in different orders, which doesn't matter, but breaks the //string detection here. // @Test // public void testINI(){ // assertEquals("a.b=value1\na.b.c2=value3\na.b.c1=value2\n", doOutput("ini://test.ini", testData)); // } // @Test // public void testJSON() { // assertEquals("{\"a\":{\"b\":{\"c1\":\"value2\",\"c2\":\"value3\",\"_\":\"value1\"}}}", doOutput("json://test.json", testData)); // } @Test @SuppressWarnings("ResultOfObjectAllocationIgnored") public void testFilterExceptions() throws URISyntaxException { try { new DataSourceFilter("$1alias=yml://blah.yml\na.*=$1alias\n", new URI("")); fail("Expected an exception when defining numeric alias"); } catch (DataSourceException e) { //Pass } try { new DataSourceFilter("$alias!=yml://blah.yml\na.*.(**)=$alias\n", new URI("")); fail("Expected an exception when putting bad characters in a filter"); } catch (DataSourceException e) { //Pass } try { new DataSourceFilter("$alias=yml://blah.yml\na.*.**=$aliasnope\n", new URI("")); fail("Expected an exception when using undefined alias"); } catch (DataSourceException e) { //Pass } try { new DataSourceFilter("$alias=!@#$%^&*()blah$1.yml\na.*.**=$alias\n", new URI("")); fail("Expected an exception when having an invalid uri"); } catch (DataSourceException e) { //Pass } } @Test public void testMatch1() throws Exception { assertEquals("yml://test.yml", getConnection("a.b.c", "a.b.c=yml://test.yml", "a.b.c.d=yml://no.yml")); } @Test public void testMatch2() throws Exception { assertEquals("yml://yes.yml", getConnection("a.b.c", "a.b.*=yml://yes.yml", "a.b.c.d=yml://no.yml")); } @Test public void testMatch3() throws Exception { assertEquals("yml://yes.yml", getConnection("a.b.c.d", "a.b.**=yml://no.yml", "a.b.c.*=yml://yes.yml")); assertEquals("yml://yes.yml", getConnection("a.b.c.d", "a.b.c.*=yml://yes.yml", "a.b.**=yml://no.yml")); } @Test public void testMultimatch1() throws Exception { assertEquals(getSet("default", "yml://yes.yml"), getConnections("a.b.c", "a.**=yml://yes.yml")); } @Test public void testMultimatch2() throws Exception { assertEquals(getSet("default", "yml://yes1.yml", "yml://yes2.yml"), getConnections("a.b.c", "a.**=yml://yes1.yml", "a.b.**=yml://yes2.yml", "b.**=yml://no.yml")); } @Test public void testHasValue() throws Exception { PersistenceNetwork network = new PersistenceNetwork("**=json://folder/default.json", new URI("default"), options); network.set(dm, new String[]{"key"}, "value"); dm.waitForThreads(); assertTrue(network.hasKey(new String[]{"key"})); deleteFiles("folder/"); } //This test works fine on its own but not in the group >.> // @Test // public void testClearValue1() throws Exception { // PersistenceNetwork network = new PersistenceNetwork("**=json://folder/default.json", new URI("default"), options); // network.set(dm, new String[]{"key"}, "value"); // network.set(dm, new String[]{"key2"}, "value"); // assertTrue(network.get(new String[]{"key"}).equals("value")); // network.clearKey(dm, new String[]{"key"}); // dm.waitForThreads(); // assertFalse(network.hasKey(new String[]{"key"})); // assertEquals("{\"key2\":\"value\"}", FileUtil.read(new File("folder/default.json"))); // deleteFiles("folder/"); // } @Test public void testNotTransient() throws Exception{ PersistenceNetwork network = new PersistenceNetwork("**=json://folder/default.json", new URI("default"), options); network.set(dm, new String[]{"key"}, "value"); dm.waitForThreads(); assertEquals("value", network.get(new String[]{"key"})); FileUtil.write("{\"key\":\"nope\"}", new File("folder/default.json")); //This should be cached in memory assertEquals("value", network.get(new String[]{"key"})); deleteFiles("folder/"); } @Test public void testTransient() throws Exception{ PersistenceNetwork network = new PersistenceNetwork("**=transient:json://folder/default.json", new URI("default"), options); network.set(dm, new String[]{"key"}, "value1"); dm.waitForThreads(); assertEquals("value1", network.get(new String[]{"key"})); FileUtil.write("{\"key\":\"value2\"}", new File("folder/default.json")); //This should not be cached in memory assertEquals("value2", network.get(new String[]{"key"})); deleteFiles("folder/"); } @Test public void testSer() throws Exception{ //This is hard to test, since it's binary data. Instead, we just check for the file's existance, and to see if //contains the key and value somewhere in the data PersistenceNetwork network = new PersistenceNetwork("**=ser://folder/default.ser", new URI("default"), options); network.set(dm, new String[]{"key"}, "value"); dm.waitForThreads(); String contents = FileUtil.read(new File("folder/default.ser")); assertTrue(contents.contains("value") && contents.contains("key") && contents.contains("java.util.HashMap")); deleteFiles("folder/"); } @Test public void testConflictingKeys() throws Exception{ //If two data sources have the same key, only one should be currently operated on. PersistenceNetwork network = new PersistenceNetwork("**=transient:json://folder/default.json\nkey.*=transient:json://folder/other.json\n", new URI("default"), options); FileUtil.write("{\"key\":{\"key\":\"value1\"}}", new File("folder/other.json"), true); FileUtil.write("{\"key\":{\"key\":\"nope\"}}", new File("folder/default.json"), true); assertEquals("value1", network.get(new String[]{"key", "key"})); deleteFiles("folder/"); } //This test works alone, but not in a group >.> // @Test // public void testSQLiteBasic() throws Exception{ // PersistenceNetwork network = new PersistenceNetwork("**=sqlite://folder/sqlite.db", new URI("default"), options); // network.set(dm, new String[]{"key", "key"}, "value"); // dm.waitForThreads(); // assertEquals("value", network.get(new String[]{"key", "key"})); // deleteFiles("folder/"); // } @Test(expected=IllegalArgumentException.class) public void testNamespaceWithUnderscore() throws Exception { PersistenceNetwork network = new PersistenceNetwork("**=sqlite://folder/sqlite.db", new URI("default"), options); try{ network.set(dm, new String[]{"Bad", "_", "Key"}, "value"); dm.waitForThreads(); } finally { deleteFiles("folder/"); } } @Test public void testMemoryDataSource() throws Exception{ PersistenceNetwork network = new PersistenceNetwork("**=mem:default", new URI("default"), options); String[] key = new String[]{"a", "b"}; network.set(dm, key, "value"); dm.waitForThreads(); assertEquals("value", network.get(key)); assertFalse(network.hasKey(new String[]{"a"})); assertTrue(network.hasKey(key)); network.clearKey(dm, key); dm.waitForThreads(); assertFalse(network.hasKey(key)); network.set(dm, key, "value"); dm.waitForThreads(); assertEquals("value", network.get(key)); MemoryDataSource.ClearDatabases(); assertFalse(network.hasKey(key)); } @Test public void testGetValues() throws Exception { PersistenceNetwork network = new PersistenceNetwork("**=json://folder/persistence.json", new URI("default"), options); try{ network.set(dm, new String[]{"t", "test1"}, "test"); network.set(dm, new String[]{"t", "test2"}, "test"); network.set(dm, new String[]{"t", "test3", "third"}, "test"); dm.waitForThreads(); Map<String[], String> list = network.getNamespace(new String[]{"t"}); List<String> output = new ArrayList<String>(); for(String[] key : list.keySet()){ output.add(StringUtils.Join(key, ".") + ": " + list.get(key)); } Collections.sort(output); String out = StringUtils.Join(output, ", "); assertEquals("t.test1: test, t.test2: test, t.test3.third: test", out); } finally { deleteFiles("folder/"); } } public String doOutput(String uri, Map<String[], String> data) { try { DataSource ds = DataSourceFactory.GetDataSource(uri, options); if (ds instanceof StringSerializableDataSource) { StringSerializableDataSource sdc = (StringSerializableDataSource) ds; if (sdc.getConnectionMixin() instanceof ReadWriteFileConnection) { //It is a file based URI, so we can test this. for (String[] key : data.keySet()) { ds.set(dm, key, data.get(key)); } dm.waitForThreads(); File output = GetPrivate(sdc.getConnectionMixin(), "file", File.class); String out = FileUtil.read(output); output.delete(); return out; } else { fail("Cannot test non-file based URIs with this method!"); return null; } } else { fail("Cannot test non string based data sources with this method!"); return null; } } catch (Exception ex) { fail(Misc.GetStacktrace(ex)); return null; } } File getFileFromDataSource(DataSource ds) { if (ds instanceof StringSerializableDataSource) { Object output = GetPrivate(ds, "output", Object.class); if (output instanceof ZipReader) { //It is a file based URI, so we can test this. File outFile = ((ZipReader) output).getFile(); return outFile; } } return null; } public static void deleteFiles(String... files) { for (String f : files) { FileUtil.recursiveDelete(new File(f)); } } public String getConnection(String key, String... mapping) throws Exception { return getConnection(key, StringUtils.Join(mapping, "\n")); } public String getConnection(String key, String mapping) throws Exception { DataSourceFilter dsf = new DataSourceFilter(mapping, new URI("")); URI conn = dsf.getConnection(key); return conn == null ? null : conn.toString(); } public SortedSet<String> getConnections(String key, String... mapping) throws Exception { DataSourceFilter dsf = new DataSourceFilter(StringUtils.Join(mapping, "\n"), new URI("default")); Set<URI> uris = dsf.getAllConnections(key); SortedSet<String> set = new TreeSet<String>(); for (URI uri : uris) { set.add(uri.toString()); } return set; } public SortedSet<String> getSet(String... strings) { SortedSet<String> set = new TreeSet<String>(); set.addAll(Arrays.asList(strings)); return set; } public String stringifyMap(Map<String[], String> map) { SortedSet<String> append = new TreeSet<String>(); for (String[] key : map.keySet()) { append.add(Arrays.toString(key) + "=" + map.get(key)); } return "[" + StringUtils.Join(append, ", ") + "]"; } }