/* * A CCNx library test. * * Copyright (C) 2008-2012 Palo Alto Research Center, Inc. * * This work is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * This work is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ package org.ccnx.ccn.test.profiles.nameenum; import java.io.IOException; import java.util.ArrayList; import java.util.Random; import java.util.SortedSet; import java.util.logging.Level; import junit.framework.Assert; import org.bouncycastle.util.Arrays; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.config.SystemConfiguration; import org.ccnx.ccn.impl.CCNFlowControl.SaveType; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.CCNStringObject; import org.ccnx.ccn.profiles.nameenum.EnumeratedNameList; import org.ccnx.ccn.protocol.Component; import org.ccnx.ccn.protocol.ContentName; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; /** * Test the synchronous interface to name enumeration. * Put a bunch of data in repo (in one directory) * Make a enumerate name list of directory (check that it works) * call update in background on enumerated name list (asynchronously) * add data using a different interface and see if it shows up on the lists */ public class EnumeratedNameListTestRepo { EnumeratedNameList testList; //the enumeratednamelist object used to test the class static Random rand = new Random(); static final String directoryString = "/test/parc" + "/directory-"; static final String directoryString2 = "/test/parc" + "/directory2-"; static final String directoryString3 = "/test/parc" + "/directory3-"; static ContentName directory; static ContentName directory2; static ContentName directory3; static String nameString = "name-"; static String name1String; static String name2String; static String name3String; static String name4String; static String name5String; static String name6String; static ContentName name1; static ContentName name2; static ContentName name3; // For thread test static ContentName name4; static ContentName name5; static ContentName name6; static ContentName name7; static ContentName name8; static ContentName name9; static CCNHandle putHandle; static String prefix1StringError = "/park.com/csl/ccn/repositories"; ArrayList<ContentName> names; static ContentName brokenPrefix; ContentName c1; ContentName c2; static int contentSeenNoPool = 0; static int contentSeenPool = 0; @BeforeClass public static void setUpBeforeClass() throws Exception { //assign content names from strings to content objects // randomize names to minimize stateful effects of ccnd/repo caches. directory = ContentName.fromNative(directoryString + Integer.toString(rand.nextInt(10000))); directory2 = ContentName.fromNative(directoryString2 + Integer.toString(rand.nextInt(10000))); directory3 = ContentName.fromNative(directoryString3 + Integer.toString(rand.nextInt(10000))); // These must all be different int value = rand.nextInt(10000); name1String = nameString + Integer.toString(value); name2String = nameString + Integer.toString(value+1); name3String = nameString + Integer.toString(value+2); name4String = nameString + Integer.toString(value+3); name1 = new ContentName(directory, name1String); name2 = new ContentName(directory, name2String); name3 = new ContentName(directory, name3String); name4 = new ContentName(directory2, name1String); name5 = new ContentName(directory2, name2String); name6 = new ContentName(directory2, name3String); name7 = new ContentName(directory2, name4String); name8 = new ContentName(directory3, name1String); name9 = new ContentName(directory3, name2String); brokenPrefix = ContentName.fromNative(prefix1StringError); putHandle = CCNHandle.open(); } @AfterClass public static void tearDownAfterClass() { putHandle.close(); } @Test public void testEnumeratedName() throws Exception { Log.info(Log.FAC_TEST, "Starting testEnumeratedName"); try { CCNHandle handle = CCNHandle.open(); Assert.assertNotNull(directory); Assert.assertNotNull(name1); Assert.assertNotNull(name2); Assert.assertNotNull(name3); Assert.assertNotNull(brokenPrefix); Log.info(Log.FAC_TEST, "*****************Creating Enumerated Name List Object"); //creates Enumerated Name List testList = new EnumeratedNameList(directory, putHandle); Log.info(Log.FAC_TEST, "*****************assert creation of handle and enumeratednamelist object"); //verify that the class and handle is setup Assert.assertNotNull(putHandle); Assert.assertNotNull(testList); Log.info(Log.FAC_TEST, "*****************assert creation of prefix"); //Verify that the object has been created with the right prefix ContentName prefixTest = testList.getName(); Assert.assertNotNull(prefixTest); Log.info(Log.FAC_TEST, "***************** Prefix is "+ prefixTest.toString()); Assert.assertEquals(prefixTest, directory); Assert.assertFalse(brokenPrefix.equals(prefixTest)); //run it on a name that isn't there and make sure it is empty // DKS -- this won't work -- it will wait forever. The point is not to enumerate, // the point is to wait for an answer that says something, potentially forever. No repo // currently NACKs -- answers "nothing there", so this will simply wait until something // appears. //testList.waitForData(); Log.info(Log.FAC_TEST, "****************** adding name1 to repo"); // adding content to repo ContentName latestName = addContentToRepo(name1, handle); testList.waitForChildren(); Log.info(Log.FAC_TEST, "Added data to repo: " + latestName); //testing that the enumerator has new data Assert.assertTrue(testList.hasNewData()); //Testing that hasNewData returns true Assert.assertTrue(testList.hasNewData()); //gets new data SortedSet<ContentName> returnedBytes = testList.getNewData(); Assert.assertNotNull(returnedBytes); Assert.assertFalse(returnedBytes.isEmpty()); // getNewData gets *new* data -- you got the last new data, there won't be any more // until you add something else to the repo. i.e. this next call would block // until there was new data for getNewData to return, and it *wouldn't* match the previous set. // Assert.assertEquals(testList.getNewData(), returnedBytes.size()); System.out.println("Got " + returnedBytes.size() + " children: " + returnedBytes); //only one thing has been added, so we can only expect one name //System.out.println("Predicted strings " + name1String + ", " + name2String + ", " + name3String); System.out.println("Predicted strings " + name1String); // DKS -- previous version of this was failing: // Assert.assertEquals(name1String.getBytes(UTF8), returnedBytes.get(0))); // this will fail, because byte [] does not define equals (it's a native type, not an object), so // you get Object.equals -- which tests if the two pointers are the same. // If we run this more than once on the same repo, will get all the data back -- so won't know which child // is first. Don't try to test on the content. //Assert.assertTrue(Arrays.areEqual(name1String.getBytes(UTF8), returnedBytes.first().component(0))); System.out.print("names in list:"); for(ContentName n: returnedBytes) System.out.print(" "+n); System.out.println(); //testing that children exist // DKS -- if you're testing a boolean, use assertTrue, not assertNotNull Assert.assertTrue(testList.hasChildren()); //Testing that Name1 Exists // only true if run on clean repo -- if not clean repo and clean ccnd cache, might be in second response Assert.assertTrue(testList.hasChild(name1String)); // Only definite if run on fresh repo //as long as the EnumeratedNameList object isn't starting a new interest, this is correct. a repo // wouldn't return old names after the last response Assert.assertFalse(testList.hasNewData()); // Now add some more data System.out.println("adding name2: "+name2); addContentToRepo(name2, handle); System.out.println("adding name3: "+name3); addContentToRepo(name3, handle); //both of these actions could generate a new response since there is an outstanding interest on the data. //this means the response can come a few different ways //1 - an interest.last request gets there and sets the interest flag. then a save will generate a response //2 - an interest.last request gets there after the save, generating a new response //3 - same thing happens for the second object as 1 //4 - same thing happens for the second object as 2 //5 - after both things are added an interest.last arrives from the CCNNameEnumerator. SortedSet<ContentName> returnedBytes2 = testList.getNewData(); // will block for new data Assert.assertNotNull(returnedBytes2); System.out.print("names in list after adding name2 and name3:"); for(ContentName n: returnedBytes2) System.out.print(" "+n); System.out.println(); System.out.print("names in testlist after adding name2 and name3:"); for(ContentName n: testList.getChildren()) System.out.print(" "+n); System.out.println(); // Might have older stuff from previous runs, so don't insist we get back only what we put in. System.out.println("Got new data, second round size: " + returnedBytes2.size() + " first round " + returnedBytes.size()); //this is new data... so comparing new data from one save to another doesn't really make sense Assert.assertTrue(returnedBytes2.size() >= 1); //since we have a new response, the first name has to be in there... Assert.assertTrue(testList.hasChild(name2String)); //we might not have a response since the second name... need to check again if it isn't in there yet if(!testList.hasChild(name3String)){ returnedBytes2 = testList.getNewData(); // will block for new data //now we have the third response... System.out.print("names in list after asking for new data again:"); for(ContentName n: returnedBytes2) System.out.print(" "+n); System.out.println(); } System.out.print("names in testlist after adding name2 and name3:"); for(ContentName n: testList.getChildren()) System.out.print(" "+n); System.out.println(); Assert.assertTrue(testList.hasChild(name3String)); // This will add new versions for (int i=0; i < 5; ++i) { latestName = addContentToRepo(name1, handle); Log.info(Log.FAC_TEST, "Added data to repo: " + latestName); } EnumeratedNameList versionList = new EnumeratedNameList(name1, handle); versionList.waitForChildren(); Assert.assertTrue(versionList.hasNewData()); // Even though the addition of versions above is blocking and the new EnumeratedNameList // is not created to start enumerating names until after the versions have been written, // we don't have a guarantee that the repository will have fully processed the writes // before it answers the name enumeration request. (At some point when full repository // commitment is obtained before returning from write this may change). There is a timing // race with the last content written and the first name enumeration result. For this reason // we must be prepared to wait a second time. // It could be possible that only waiting one more time is not sufficient... if the writes are very slow, // the timing could work out that there is a response per object. adding loop to account for that for(int attempts = 1; attempts < 5; attempts++){ // 5 versions written just above plus 1 earlier addition under name1 versionList.getNewData(); // ignore result, we want to look at entire set once available if(versionList.getChildren().size() >= 6) attempts = 5; } // Now we should have everything ContentName latestReturnName = versionList.getLatestVersionChildName(); System.out.println("Got children: " + versionList.getChildren()); System.out.println("Got latest name " + latestReturnName + " expected " + new ContentName(latestName.lastComponent())); Assert.assertTrue(Arrays.areEqual(latestName.lastComponent(), latestReturnName.lastComponent())); } catch (Exception e) { Log.logException(Log.FAC_TEST, Level.WARNING, "Failed test with exception " + e.getMessage(), e); Assert.fail("Failed test with exception " + e.getMessage()); } Log.info(Log.FAC_TEST, "Completed testEnumeratedName"); } @Test public void testEnumeratedNameListWithThreads() throws Exception { Log.info(Log.FAC_TEST, "Starting testEnumeratedNameListWithThreads"); EnumeratedNameList poolList = new EnumeratedNameList(directory3, putHandle); Thread poolThread = new Thread(new WaiterThreadForPool(poolList)); poolThread.start(); addContentToRepo(name8, putHandle); addContentToRepo(name9, putHandle); poolOps(poolList); Assert.assertEquals(2, contentSeenPool); EnumeratedNameList noPoolList = new EnumeratedNameList(directory2, putHandle); Thread noPoolThread = new Thread(new WaiterThread(noPoolList)); noPoolThread.start(); addContentToRepo(name4, putHandle); addContentToRepo(name5, putHandle); addContentToRepo(name6, putHandle); addContentToRepo(name7, putHandle); noPoolOps(noPoolList); Assert.assertEquals(4, contentSeenNoPool); Log.info(Log.FAC_TEST, "Completed testEnumeratedNameListWithThreads"); } private class WaiterThreadForPool implements Runnable { private EnumeratedNameList myList = null; private WaiterThreadForPool(EnumeratedNameList list) { this.myList = list; } public void run() { poolOps(myList); } } public class WaiterThread implements Runnable { private EnumeratedNameList myList = null; private WaiterThread(EnumeratedNameList list) { this.myList = list; } public void run() { noPoolOps(myList); } } private static void poolOps(EnumeratedNameList list) { long currentTime = System.currentTimeMillis(); long lastTime = currentTime + (2 * SystemConfiguration.MAX_TIMEOUT); while (contentSeenPool < 2 && currentTime < lastTime) { synchronized (list) { SortedSet<ContentName> names = list.getNewDataThreadPool(SystemConfiguration.MAX_TIMEOUT); if (null != names) contentSeenPool += names.size(); } currentTime = System.currentTimeMillis(); } list.shutdown(); } private static void noPoolOps(EnumeratedNameList list) { long currentTime = System.currentTimeMillis(); long lastTime = currentTime + (4 * SystemConfiguration.MAX_TIMEOUT); while (contentSeenNoPool < 4 && currentTime < lastTime) { synchronized (list) { SortedSet<ContentName> names = list.getNewData(SystemConfiguration.MAX_TIMEOUT); if (null != names) contentSeenNoPool += names.size(); } currentTime = System.currentTimeMillis(); } list.shutdown(); } /* * Adds data to the repo for testing * */ private ContentName addContentToRepo(ContentName name, CCNHandle handle) throws ConfigurationException, IOException { //method to load something to repo for testing CCNStringObject cso = new CCNStringObject(name, Component.printNative(name.lastComponent()), SaveType.REPOSITORY, handle); cso.save(); Log.info(Log.FAC_TEST, "Saved new object: " + cso.getVersionedName()); return cso.getVersionedName(); } }