org.ccnx.ccn.profiles.nameenum.EnumeratedNameListTestRepo.java Source code

Java tutorial

Introduction

Here is the source code for org.ccnx.ccn.profiles.nameenum.EnumeratedNameListTestRepo.java

Source

/*
 * A CCNx library test.
 *
 * Copyright (C) 2008-2013 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.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();
    }
}