/* * Copyright (c) 2001-2009 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by Sun Microsystems, Inc. for JXTA(TM) technology." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must * not be used to endorse or promote products derived from this software * without prior written permission. For written permission, please contact * Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", nor may * "JXTA" appear in their name, without prior written permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * This software consists of voluntary contributions made by many individuals * on behalf of Project JXTA. For more information on Project JXTA, please see * http://www.jxta.org. * * This license is based on the BSD license adopted by the Apache Foundation. */ package net.jxta.impl.cm; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import net.jxta.document.AdvertisementFactory; import net.jxta.document.Element; import net.jxta.document.MimeMediaType; import net.jxta.document.StructuredDocument; import net.jxta.document.StructuredDocumentFactory; import net.jxta.id.IDFactory; import net.jxta.impl.util.FakeSystemClock; import net.jxta.impl.util.TimeUtils; import net.jxta.peergroup.PeerGroupID; import net.jxta.protocol.PeerAdvertisement; import net.jxta.protocol.SrdiMessage.Entry; import net.jxta.test.util.FileSystemTest; /** * Comprehensive suite of unit tests for AdvertisementCache implementations. * Simply extend this class and implement {@link #getCacheClassName()} and * {@link #createWrappedCache()} to unit test your own implementation. */ public abstract class AbstractCmTest extends FileSystemTest { private static final int NO_THRESHOLD = Integer.MAX_VALUE; protected String cacheImplClassName; protected AdvertisementCache wrappedCache; protected Cm cm; protected PeerAdvertisement adv; protected PeerGroupID groupId; protected FakeSystemClock fakeTimer = new FakeSystemClock(); @Override protected void setUp() throws Exception { super.setUp(); TimeUtils.setClock(fakeTimer); wrappedCache = createWrappedCache("testArea"); cm = new Cm(wrappedCache); groupId = IDFactory.newPeerGroupID(); adv = createPeerAdvert(groupId, "MyPeer100"); } @Override protected void tearDown() throws Exception { cm.stop(); cm = null; wrappedCache = null; super.tearDown(); TimeUtils.resetClock(); } /** * @return the full class name of the implementation to be tested. Used * to ensure the cache can be instantiated from the reflection based approach * used in the standard {@link Cm} constructors. */ public abstract String getCacheClassName(); /** * @return an instance of the advertisement cache to be tested, instantiated * with whatever parameters and options are required for it's normal functioning. */ public abstract AdvertisementCache createWrappedCache(String areaName) throws Exception; protected PeerAdvertisement createPeerAdvert(PeerGroupID pgID, String peerName) { PeerAdvertisement peerAdv = (PeerAdvertisement) AdvertisementFactory.newAdvertisement(PeerAdvertisement.getAdvertisementType()); peerAdv.setPeerGroupID(pgID); peerAdv.setPeerID(IDFactory.newPeerID(pgID)); peerAdv.setName(peerName); return peerAdv; } public void testGetRecords_withNullDn_returnsEmptyResultSet() { List<InputStream> records = cm.getRecords(null, 10, null); assertNotNull(records); assertEquals(0, records.size()); } public void testGetRecords_withThresholdBeneathNumResults() throws IOException { createTestData(); List<InputStream> records = cm.getRecords("a", 3, null); assertEquals(3, records.size()); assertTrue("expected peers not found", containsXOf(extractNames(records), 3, "Peer1", "Peer2", "Peer3", "Peer4", "SuperPeerX", "other")); } public void testGetRecords_checkExpirationsMatch() throws Exception { fakeTimer.currentTime = 50000; cm.save("a", "b", createPeerAdvert(groupId, "Peer1"), 120000, 100000); cm.save("a", "c", createPeerAdvert(groupId, "Peer2"), 160000, 160000); // simulate time moving forward by 50000ms fakeTimer.currentTime = 100000; List<Long> expirations = new ArrayList<Long>(); List<InputStream> records = cm.getRecords("a", 100, expirations); assertNotNull(records); assertEquals(2, records.size()); ArrayList<String> names = new ArrayList<String>(); extractNames(records, names); checkContains(names, "Peer1", "Peer2"); int index = 0; for(String name : names) { if(name.equals("Peer1")) { assertEquals(70000L, expirations.get(index).longValue()); } else { assertEquals(110000L, expirations.get(index).longValue()); } index++; } } public void testGetRecords() throws Exception { cm.save("a", "b", createPeerAdvert(groupId, "Peer1")); cm.save("a", "c", createPeerAdvert(groupId, "Peer1")); cm.save("a", "d", createPeerAdvert(groupId, "Peer1")); cm.save("b", "b", createPeerAdvert(groupId, "Peer1")); cm.save("b", "c", createPeerAdvert(groupId, "Peer1")); cm.save("ab", "d", createPeerAdvert(groupId, "Peer1")); cm.save("bc", "d", createPeerAdvert(groupId, "Peer1")); assertEquals(3, cm.getRecords("a", 100, null).size()); assertEquals(2, cm.getRecords("b", 100, null).size()); // ensure dn strings which are substrings of one another do not affect the outcome assertEquals(1, cm.getRecords("ab", 100, null).size()); assertEquals(1, cm.getRecords("bc", 100, null).size()); } public void testGetLifetime_withUnknownDnFnPair() throws Exception { assertEquals(-1L, cm.getLifetime("does", "notexist")); } public void testGetLifetime() throws Exception { fakeTimer.currentTime = 0; cm.save("a", "b", createPeerAdvert(groupId, "Peer1"), 50000, 100000); assertEquals(50000L, cm.getLifetime("a", "b")); fakeTimer.currentTime = 20000; assertEquals(30000L, cm.getLifetime("a", "b")); fakeTimer.currentTime = 40000; assertEquals(10000L, cm.getLifetime("a", "b")); fakeTimer.currentTime = 60000; assertEquals(-10000L, cm.getLifetime("a", "b")); } public void testGetExpirationTime_withUnknownDnFnPair() throws Exception { assertEquals(-1, cm.getExpirationtime("does", "notexist")); } public void testGetExpirationTime() throws Exception { fakeTimer.currentTime = 10000; cm.save("a", "b", createPeerAdvert(groupId, "Peer1"), 50000, 30000); // expiration = min(relative(lifetime), expiry), and in this case expiry < relative(lifetime) // Note that we set current time initally to 10000 above so that we guarantee the Min() is the expiration time here, and not // 40000, which is the remaining life. assertEquals(30000L, cm.getExpirationtime("a", "b")); fakeTimer.currentTime = 50000; // Original life of 10000 means we're now 10000 away from expiration assertEquals(10000L, cm.getExpirationtime("a", "b")); fakeTimer.currentTime = 80000; assertEquals(-1L, cm.getExpirationtime("a", "b")); } public void testGetInputStream_withUnknownDnFnPair() throws Exception { assertNull(cm.getInputStream("does", "notexist")); } public void testGetInputStream() throws IOException { byte[] data = new byte[64]; for(int i=0; i < data.length; i++) { data[i] = (byte)i; } cm.save("a", "b", data, 10000L, 20000L); InputStream inputStream = cm.getInputStream("a", "b"); assertNotNull("Returned input stream was null", inputStream); for(int i=0; i < data.length; i++) { assertEquals(data[i], inputStream.read()); } assertEquals("input stream is not depleted when expected", -1, inputStream.read()); } public void testRemove() throws Exception { cm.save("a", "b", new byte[64], 10000L, 20000L); assertNotNull(cm.getInputStream("a", "b")); cm.remove("a", "b"); assertNull("Returned input stream should be null", cm.getInputStream("a", "b")); assertEquals(-1, cm.getLifetime("a", "b")); assertEquals(-1, cm.getExpirationtime("a", "b")); assertNull(cm.getInputStream("a", "b")); } public void testSaveAdvWithIllegalLifetime() throws Exception { try { cm.save("test", "test2", adv, -1, 100); fail("IllegalArgumentException expected"); } catch(IllegalArgumentException e) { assertEquals("Bad expiration or lifetime.", e.getMessage()); } } public void testSaveAdvWithIllegalExpiry() throws Exception { try { cm.save("test", "test2", adv, 100, -1); fail("IllegalArgumentException expected"); } catch(IllegalArgumentException e) { assertEquals("Bad expiration or lifetime.", e.getMessage()); } } public void testSaveBytesWithIllegalLifetime() throws Exception { try { cm.save("test", "test2", new byte[64], -1, 100); fail("IllegalArgumentException expected"); } catch(IllegalArgumentException e) { assertEquals("Bad expiration or lifetime.", e.getMessage()); } } public void testSaveBytesWithIllegalExpiry() throws Exception { try { cm.save("test", "test2", new byte[64], 100, -1); fail("IllegalArgumentException expected"); } catch(IllegalArgumentException e) { assertEquals("Bad expiration or lifetime.", e.getMessage()); } } public void testSaveAdv_overridesLifetimeIfLower() throws Exception { PeerAdvertisement ad = createPeerAdvert(groupId, "Peer1"); cm.save("a", "b", ad, 20000L, 30000L); cm.save("a", "b", ad, 10000L, 30000L); assertEquals(20000L, cm.getLifetime("a", "b")); } public void testSaveBytes_overridesLifetimeIfLower() throws Exception { byte[] bytes = new byte[64]; cm.save("a", "b", bytes, 20000L, 30000L); cm.save("a", "b", bytes, 10000L, 30000L); assertEquals(20000L, cm.getLifetime("a", "b")); } public void testSearch_exactMatch() throws IOException { cm.save("a", "b", adv, 100000, 200000); List<InputStream> results = cm.search("a", "Name", "MyPeer100", 5, null); assertEquals(1, results.size()); assertEquals("MyPeer100", getNameFromResult(results.get(0))); } protected String getNameFromResult(InputStream stream) throws IOException { StructuredDocument<?> doc = StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, stream); Enumeration<?> children = doc.getChildren("Name"); return (String)((Element<?>)children.nextElement()).getValue(); } public void testSearch_endsWith() throws IOException { createTestData(); List<InputStream> results = cm.search("a", "Name", "*PeerX", 10, null); assertEquals(1, results.size()); checkContains(extractNames(results), "SuperPeerX"); } public void testSearch_startsWith() throws IOException { createTestData(); List<InputStream> results = cm.search("a", "Name", "Peer*", 10, null); assertEquals(4, results.size()); checkContains(extractNames(results), "Peer1", "Peer2", "Peer3", "Peer4"); } public void testSearch_contains() throws IOException { createTestData(); List<InputStream> results = cm.search("a", "Name", "*Peer*", 10, null); assertEquals(5, results.size()); checkContains(extractNames(results), "Peer1", "Peer2", "Peer3", "Peer4", "SuperPeerX"); } public void testSearch_matchAnything() throws IOException { createTestData(); List<InputStream> results = cm.search("a", "Name", "*", 10, null); assertEquals(6, results.size()); checkContains(extractNames(results), "Peer1", "Peer2", "Peer3", "Peer4", "SuperPeerX", "other"); } private void createTestData() throws IOException { cm.save("a", "b", createPeerAdvert(groupId, "Peer1"), 100000, 200000); cm.save("a", "c", createPeerAdvert(groupId, "Peer2"), 150000, 200000); cm.save("a", "d", createPeerAdvert(groupId, "Peer3"), 160000, 200000); cm.save("a", "e", createPeerAdvert(groupId, "Peer4"), 170000, 200000); cm.save("a", "f", createPeerAdvert(groupId, "SuperPeerX"), 100000, 200000); cm.save("a", "g", createPeerAdvert(groupId, "other"), 100000, 200000); } public void testSearch_withThreshold() throws IOException { createTestData(); // search could return 6 results with this query, but we only want 2 List<InputStream> result = cm.search("a", "Name", "*", 2, null); assertEquals(2, result.size()); // cannot predict which 2 of the 6 will be returned assertTrue("Subset of expected peers not found", containsXOf(extractNames(result), 2, "Peer1", "Peer2", "Peer3", "Peer4")); result = cm.search("a", "Name", "*", 3, null); assertEquals(3, result.size()); assertTrue(containsXOf(extractNames(result), 3, "Peer1", "Peer2", "Peer3", "Peer4")); } public void testSearch_expiredEntriesNotReturned() throws IOException { createTestData(); fakeTimer.currentTime = 150000; List<Long> expirations = new LinkedList<Long>(); List<InputStream> result = cm.search("a", "Name", "Peer*", 10, expirations); assertEquals(2, result.size()); List<String> names = new ArrayList<String>(); extractNames(result, names); HashSet<String> nameSet = new HashSet<String>(); nameSet.addAll(names); checkContains(nameSet, "Peer3", "Peer4"); assertEquals(2, expirations.size()); int index = 0; for(String name : names) { if(name.equals("Peer3")) { assertEquals(10000L, expirations.get(index).longValue()); } else if(name.equals("Peer4")) { assertEquals(20000L, expirations.get(index).longValue()); } index++; } } public void testGetDeltas_generatedBySave() throws Exception { cm.setTrackDeltas(true); assertNotNull(cm.getDeltas("a")); assertEquals(0, cm.getDeltas("a").size()); PeerAdvertisement peer1 = createPeerAdvert(groupId, "Peer1"); PeerAdvertisement peer2 = createPeerAdvert(groupId, "Peer2"); cm.save("a", "b", peer1, 150000L, 100000L); cm.save("a", "c", peer2, 150000L, 100000L); // each added peer advertisement generates two deltas - one for each indexable property List<Entry> expectedDeltas = new ArrayList<Entry>(4); expectedDeltas.add(new Entry("PID", peer1.getPeerID().toString(), 150000L)); expectedDeltas.add(new Entry("Name", peer1.getName(), 150000L)); expectedDeltas.add(new Entry("PID", peer2.getPeerID().toString(), 150000L)); expectedDeltas.add(new Entry("Name", peer2.getName(), 150000L)); List<Entry> deltas = cm.getDeltas("a"); assertEquals(4, deltas.size()); assertTrue(deltas.containsAll(expectedDeltas)); HashSet<String> keys = new HashSet<String>(); keys.add(deltas.get(0).key); keys.add(deltas.get(1).key); checkContains(keys, "PID", "Name"); } public void testGetDeltas_generatedByRemove() throws Exception { cm.setTrackDeltas(false); PeerAdvertisement advert = createPeerAdvert(groupId, "Peer 1"); cm.save("a", "b", advert, 100000L, 200000L); cm.setTrackDeltas(true); cm.remove("a", "b"); List<Entry> expectedDeltas = new ArrayList<Entry>(4); expectedDeltas.add(new Entry("PID", advert.getPeerID().toString(), 100000L)); expectedDeltas.add(new Entry("Name", advert.getName(), 100000L)); List<Entry> deltas = cm.getDeltas("a"); assertEquals(2, deltas.size()); assertTrue(deltas.containsAll(expectedDeltas)); } public void testGetDeltas_clearsOnEachConsecutiveCall() throws Exception { cm.setTrackDeltas(true); cm.save("a","b",createPeerAdvert(groupId, "Peer 1"), 100000L, 200000L); assertEquals(2, cm.getDeltas("a").size()); assertEquals(0, cm.getDeltas("a").size()); cm.remove("a", "b"); assertEquals(2, cm.getDeltas("a").size()); assertEquals(0, cm.getDeltas("a").size()); } public void testSave_deltasNotGeneratedWithZeroExpiration() throws Exception { cm.setTrackDeltas(true); cm.save("a", "b", createPeerAdvert(groupId, "Peer1"), 10000L, 0L); assertEquals(0, cm.getDeltas("a").size()); } public void testSave_deltasNotGeneratedWithTrackingOff() throws Exception { cm.setTrackDeltas(false); cm.save("a", "b", createPeerAdvert(groupId, "Peer2"), 100000L, 200000L); assertEquals(0, cm.getDeltas("a").size()); } public void testRemove_deltasNotGeneratedWithTrackingOff() throws Exception { cm.save("a", "b", createPeerAdvert(groupId, "Peer 1"), 100000L, 200000L); // clear any deltas generated by save cm.getDeltas("a"); cm.setTrackDeltas(false); cm.remove("a", "b"); assertEquals(0, cm.getDeltas("a").size()); } public void testGetEntries() throws Exception { PeerAdvertisement peerAdv = createPeerAdvert(groupId, "Test peer"); cm.save("a", "b", adv, 100000L, 100000L); cm.save("a", "c", peerAdv, 200000L, 200000L); // this advert won't be included in the returned results, wrong dn cm.save("b", "c", createPeerAdvert(groupId, "Test peer 2")); List<Entry> entries = cm.getEntries("a", false); assertNotNull(entries); assertEquals(4, entries.size()); checkContains(entries, new EntryComparator(), new Entry("PID", adv.getPeerID().toString(), 100000L), new Entry("Name", adv.getName(), 100000L), new Entry("PID", peerAdv.getPeerID().toString(), 200000L), new Entry("Name", peerAdv.getName(), 200000L)); } /** * Pseudo-comparator for Entry objects that returns 0 if there is an exact match, -1 otherwise. Used * by the tests to make sure entries returned also have the expected expiration, as the equals() method * on Entry does not take this into account. */ private class EntryComparator implements Comparator<Entry> { public int compare(Entry o1, Entry o2) { return o1.key.equals(o2.key) && o1.value.equals(o2.value) && o1.expiration == o2.expiration ? 0 : -1; } } public void testGetEntries_flushesDeltasIfRequested() throws IOException { cm.setTrackDeltas(true); cm.save("a", "b", adv, 100000L, 100000L); cm.getEntries("a", true); assertEquals(0, cm.getDeltas("a").size()); } public void testGetEntries_doesNotFlushDeltasIfNotRequested() throws IOException { cm.setTrackDeltas(true); cm.save("a", "b", adv, 100000L, 100000L); cm.getEntries("a", false); List<Entry> deltas = cm.getDeltas("a"); assertEquals(2, deltas.size()); checkContains(deltas, new EntryComparator(), new Entry("PID", adv.getPeerID().toString(), 100000L), new Entry("Name", adv.getName(), 100000L)); } public void testGetEntries_immuneToDeltaClear() throws IOException { cm.setTrackDeltas(true); cm.save("a", "b", adv, 100000L, 100000L); cm.getEntries("a", false); // will clear deltas for dn=a cm.getDeltas("a"); // this should still return all entries List<Entry> entries = cm.getEntries("a", false); assertEquals(2, entries.size()); checkContains(entries, new EntryComparator(), new Entry("PID", adv.getPeerID().toString(), 100000L), new Entry("Name", adv.getName(), 100000L)); } public void testGetEntries_returnsExpirationsBasedOnLifetimeOnly() throws IOException { cm.save("a", "b", adv, 100000L, 50000L); List<Entry> entries = cm.getEntries("a", false); assertEquals(2, entries.size()); checkContains(entries, new EntryComparator(), new Entry("PID", adv.getPeerID().toString(), 100000L), new Entry("Name", adv.getName(), 100000L)); fakeTimer.currentTime = 40000L; entries = cm.getEntries("a", false); assertEquals(2, entries.size()); checkContains(entries, new EntryComparator(), new Entry("PID", adv.getPeerID().toString(), 60000L), new Entry("Name", adv.getName(), 60000L)); } public void testSaveIsolation_differentAreaNames() throws Exception { Cm alternateArea = new Cm(createWrappedCache("testArea2")); cm.save("a", "b", adv); assertEquals(1, cm.getRecords("a", NO_THRESHOLD, null).size()); assertEquals(0, alternateArea.getRecords("a", NO_THRESHOLD, null).size()); assertEquals(2, cm.getEntries("a", false).size()); assertEquals(0, alternateArea.getEntries("a", false).size()); assertNotNull(cm.getInputStream("a", "b")); assertNull(alternateArea.getInputStream("a", "b")); alternateArea.stop(); } public void testRemoveIsolation_differentAreaNames() throws Exception { Cm alternateArea = new Cm(createWrappedCache("testArea2")); cm.save("a", "b", adv); alternateArea.remove("a", "b"); // item should still exist assertEquals(1, cm.getRecords("a", NO_THRESHOLD, null).size()); alternateArea.stop(); } public void testConstruct() throws IOException { System.setProperty(Cm.CACHE_IMPL_SYSPROP, getCacheClassName()); Cm cmFromConstructor = new Cm(testRootDir.toURI(), "testArea2"); assertEquals(getCacheClassName(), cmFromConstructor.getImplClassName()); cmFromConstructor.stop(); } public void testConstructWithGcIntervalAndTrackDeltasParams() throws IOException { System.setProperty(Cm.CACHE_IMPL_SYSPROP, getCacheClassName()); Cm cmFromConstructor = new Cm(testRootDir.toURI(), "testArea2", 30000, false); assertEquals(getCacheClassName(), cmFromConstructor.getImplClassName()); cmFromConstructor.stop(); } protected <T, U extends Collection<T>> void checkContains(U results, Comparator<T> comparator, T... expectedSet) { for (T expected : expectedSet) { assertTrue(expected + " not included in set", results.contains(expected)); if(comparator != null) { boolean foundMatch = false; for(T item : results) { if(comparator.compare(item, expected) == 0) { foundMatch = true; break; } } assertTrue("Did not find exact match using comparator for " + expected, foundMatch); } } } protected <T, U extends Collection<T>> void checkContains(U results, T... expectedSet) { checkContains(results, null, expectedSet); } protected boolean containsXOf(HashSet<String> set, int numExpected, String... expectedSet) { int numMatches = 0; for (String expected : expectedSet) { if(set.contains(expected)) { numMatches++; } } return numMatches == numExpected; } protected HashSet<String> extractNames(List<InputStream> results) throws IOException { HashSet<String> names = new HashSet<String>(); extractNames(results, names); return names; } protected void extractNames(List<InputStream> results, Collection<String> output) throws IOException { for (InputStream stream : results) { output.add(getNameFromResult(stream)); } } }