/* * Copyright (c) 2001 Sun Microsystems, Inc. All rights reserved. * * 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 the * Sun Microsystems, Inc. for Project JXTA." * 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. * ==================================================================== * * 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.content; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; import net.jxta.content.Content; import net.jxta.content.ContentID; import net.jxta.content.ContentProviderSPI; import net.jxta.content.ContentSourceLocationState; import net.jxta.content.ContentTransfer; import net.jxta.content.ContentTransferAggregatorEvent; import net.jxta.content.ContentTransferAggregatorListener; import net.jxta.content.ContentTransferEvent; import net.jxta.content.ContentTransferListener; import net.jxta.content.ContentTransferState; import net.jxta.content.TransferException; import net.jxta.document.Document; import net.jxta.document.MimeMediaType; import net.jxta.document.StructuredDocumentFactory; import net.jxta.id.IDFactory; import net.jxta.peergroup.PeerGroupID; import net.jxta.protocol.ContentShareAdvertisement; import net.jxta.test.util.TempDir; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.Sequence; import org.jmock.integration.junit4.JMock; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Test the workings of the TransferAggregator class. */ @RunWith(JMock.class) public class TransferAggregatorTest { private static Logger LOG = Logger.getLogger(TransferAggregatorTest.class.getName()); private static final TempDir TEMP_DIR; private TransferAggregator aggregator; private ContentTransferAggregatorListener aggListener; private ContentTransferListener listener; private ContentProviderSPI provider1; private ContentProviderSPI provider2; private ContentProviderSPI provider3; private ContentProviderSPI provider4; private List<ContentProviderSPI> providers = new CopyOnWriteArrayList<ContentProviderSPI>(); private ContentTransfer transfer1; private ContentTransfer transfer2; private ContentTransfer transfer3; private ContentTransfer transfer4; private List<ContentTransfer> transfers = new CopyOnWriteArrayList<ContentTransfer>(); private ContentTransfer selected; private ContentTransfer standby; private Content content; private Mockery context = new Mockery(); /** * Default constructor. */ public TransferAggregatorTest() { } static { try { TEMP_DIR = new TempDir(); } catch (IOException iox) { throw(new IllegalStateException( "Could not intiialize temp dir", iox)); } } @Before public void setUp() throws Exception { LOG.info("==========================================================="); TEMP_DIR.clear(); listener = context.mock(ContentTransferListener.class); aggListener = context.mock(ContentTransferAggregatorListener.class); transfer1 = context.mock(ContentTransfer.class, "transfer1"); transfer2 = context.mock(ContentTransfer.class, "transfer2"); transfer3 = context.mock(ContentTransfer.class, "transfer3"); transfer4 = context.mock(ContentTransfer.class, "transfer4"); transfers.add(transfer1); transfers.add(transfer2); transfers.add(transfer3); transfers.add(transfer4); provider1 = context.mock(ContentProviderSPI.class, "provider1"); provider2 = context.mock(ContentProviderSPI.class, "provider2"); provider3 = context.mock(ContentProviderSPI.class, "provider3"); provider4 = context.mock(ContentProviderSPI.class, "provider4"); providers.add(provider1); providers.add(provider2); providers.add(provider3); providers.add(provider4); PeerGroupID peerGroupID = IDFactory.newPeerGroupID(); ContentID contentID = IDFactory.newContentID(peerGroupID, true); Document document = StructuredDocumentFactory.newStructuredDocument( MimeMediaType.TEXTUTF8, "foo", "bar"); content = new Content(contentID, null, document); } @After public void tearDown() { Thread.yield(); System.out.flush(); System.err.flush(); } @Test public void testConstructionWithNoProviders() throws TransferException { context.checking(new Expectations() {{ one(provider1).retrieveContent((ContentShareAdvertisement)null); will(returnValue(null)); one(provider2).retrieveContent((ContentShareAdvertisement)null); will(returnValue(null)); one(provider3).retrieveContent((ContentShareAdvertisement)null); will(returnValue(null)); one(provider4).retrieveContent((ContentShareAdvertisement)null); will(returnValue(null)); }}); try { aggregator = new TransferAggregator(null, providers, (ContentShareAdvertisement) null); fail("TransferException was not thrown"); } catch (TransferException transx) { /* * For some reason @Test(expected=TransferException.class) * is not working... */ } } @Test public void testConstruction() throws Exception { context.checking(new Expectations() {{ one(provider1).retrieveContent((ContentShareAdvertisement)null); will(returnValue(transfer1)); one(provider2).retrieveContent((ContentShareAdvertisement)null); will(returnValue(transfer2)); one(provider3).retrieveContent((ContentShareAdvertisement)null); will(returnValue(transfer3)); one(provider4).retrieveContent((ContentShareAdvertisement)null); will(returnValue(transfer4)); one(transfer1).addContentTransferListener( with(any(TransferAggregator.class))); one(transfer2).addContentTransferListener( with(any(TransferAggregator.class))); one(transfer3).addContentTransferListener( with(any(TransferAggregator.class))); one(transfer4).addContentTransferListener( with(any(TransferAggregator.class))); }}); aggregator = new TransferAggregator(null, providers, (ContentShareAdvertisement) null); aggregator.addContentTransferAggregatorListener(aggListener); aggregator.addContentTransferAggregatorListener( new ContentTransferAggregatorListener() { public void selectedContentTransfer( ContentTransferAggregatorEvent ctaEvent) { selected = ctaEvent.getDelegateContentTransfer(); } public void updatedContentTransferList( ContentTransferAggregatorEvent ctaEvent) { // Ignore } }); aggregator.addContentTransferListener(listener); context.assertIsSatisfied(); } @Test public void testRandomization() throws Exception { int last = -1; int same = 0; int total = 0; for (int i=0; i<10; i++) { testConstruction(); List<ContentTransfer> list = new ArrayList<ContentTransfer>( aggregator.getContentTransferList()); assertTrue(list.contains(transfer1)); assertTrue(list.contains(transfer2)); assertTrue(list.contains(transfer3)); assertTrue(list.contains(transfer4)); assertEquals(4, list.size()); int value = 0; for (ContentTransfer transfer : list) { value *= 10; if (transfer == transfer1) { value += 1; } else if (transfer == transfer2) { value += 2; } else if (transfer == transfer3) { value += 3; } else { value += 4; } } LOG.info("Last : " + last); LOG.info("Value: " + value); if (last > 0) { total++; if (last == value) { same++; } } last = value; } assertTrue("Element ordering was not sufficiently random (same=" + same + ", total=" + total + ")", ((same / total) < 0.5F)); context.assertIsSatisfied(); } @Test public void testStartSourceLocation() throws Exception { testConstruction(); List<ContentTransfer> xfers = new ArrayList<ContentTransfer>( aggregator.getContentTransferList()); final ContentTransfer first = xfers.remove(0); final ContentTransfer second = xfers.remove(0); final ContentTransfer third = xfers.remove(0); final Sequence firstSeq = context.sequence("selected transfer"); final Sequence secondSeq = context.sequence("standby1 transfer"); final Sequence thirdSeq = context.sequence("standby2 transfer"); standby = second; context.checking(new Expectations() {{ one(first).getTransferState(); will(returnValue(ContentTransferState.PENDING)); inSequence(firstSeq); one(aggListener).selectedContentTransfer( with(any(ContentTransferAggregatorEvent.class))); inSequence(firstSeq); one(first).getSourceLocationState(); will(returnValue(ContentSourceLocationState.NOT_LOCATING)); inSequence(firstSeq); one(first).startSourceLocation(); inSequence(firstSeq); one(second).getSourceLocationState(); will(returnValue(ContentSourceLocationState.NOT_LOCATING)); inSequence(secondSeq); one(second).startSourceLocation(); inSequence(secondSeq); one(third).getSourceLocationState(); will(returnValue(ContentSourceLocationState.NOT_LOCATING)); inSequence(thirdSeq); one(third).startSourceLocation(); inSequence(thirdSeq); }}); aggregator.startSourceLocation(); LOG.info("selected = " + selected); LOG.info("standby = " + standby); assertSame("selected", first, selected); assertSame("getCurrentContentTransfer", first, aggregator.getCurrentContentTransfer()); context.assertIsSatisfied(); } @Test public void testSelectedLocationStateHasEnough() throws Exception { testStartSourceLocation(); final ContentTransferEvent ctEvent = new ContentTransferEvent.Builder(selected) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING_HAS_ENOUGH) .transferState(ContentTransferState.PENDING) .build(); context.checking(new Expectations() {{ one(listener).contentLocationStateUpdated( with(any(ContentTransferEvent.class))); one(selected).getSourceLocationState(); will(returnValue(ContentSourceLocationState.LOCATING_HAS_ENOUGH)); // Location keeps going... }}); aggregator.contentLocationStateUpdated(ctEvent); context.assertIsSatisfied(); } @Test public void testSelectedLocationStateHasMany() throws Exception { testStartSourceLocation(); final ContentTransferEvent ctEvent = new ContentTransferEvent.Builder(selected) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING_HAS_MANY) .transferState(ContentTransferState.PENDING) .build(); context.checking(new Expectations() {{ one(listener).contentLocationStateUpdated( with(any(ContentTransferEvent.class))); one(selected).getSourceLocationState(); will(returnValue(ContentSourceLocationState.LOCATING_HAS_MANY)); one(selected).stopSourceLocation(); }}); aggregator.contentLocationStateUpdated(ctEvent); context.assertIsSatisfied(); } @Test public void testStandbyLocationStateHasEnough() throws Exception { testStartSourceLocation(); final ContentTransferEvent ctEvent = new ContentTransferEvent.Builder(standby) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING_HAS_ENOUGH) .transferState(ContentTransferState.PENDING) .build(); context.checking(new Expectations() {{ one(standby).getSourceLocationState(); will(returnValue(ContentSourceLocationState.LOCATING_HAS_ENOUGH)); one(standby).stopSourceLocation(); }}); aggregator.contentLocationStateUpdated(ctEvent); context.assertIsSatisfied(); } @Test public void testStandbyLocationStateHasMany() throws Exception { testStartSourceLocation(); final ContentTransferEvent ctEvent = new ContentTransferEvent.Builder(standby) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING_HAS_MANY) .transferState(ContentTransferState.PENDING) .build(); context.checking(new Expectations() {{ one(standby).getSourceLocationState(); will(returnValue(ContentSourceLocationState.LOCATING_HAS_MANY)); one(standby).stopSourceLocation(); }}); aggregator.contentLocationStateUpdated(ctEvent); context.assertIsSatisfied(); } @Test public void testStandbyTransferCompletion() throws Exception { testStartSourceLocation(); final ContentTransferEvent ctEvent = new ContentTransferEvent.Builder(standby) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING_HAS_MANY) .transferState(ContentTransferState.COMPLETED) .build(); context.checking(new Expectations() {{ // Ignore everything except selected and standby for (ContentTransfer transfer : transfers) { if (transfer != selected && transfer != standby) { ignoring(transfer); } } // Ignore basic transfer events ignoring(listener); // contentTransferStateUpdated handling on out-of-band success one(standby).getContent(); will(returnValue(content)); one(aggListener).selectedContentTransfer( with(any(ContentTransferAggregatorEvent.class))); // all transfers should be cancelled for cleanup purposes one(standby).removeContentTransferListener(aggregator); one(standby).cancel(); one(selected).removeContentTransferListener(aggregator); one(selected).cancel(); }}); aggregator.contentTransferStateUpdated(ctEvent); context.assertIsSatisfied(); } @Test public void testTransferCancelled() throws Exception { testStartSourceLocation(); context.checking(new Expectations() {{ one(selected).startTransfer(with(any(File.class))); one(transfer1).cancel(); one(transfer2).cancel(); one(transfer3).cancel(); one(transfer4).cancel(); }}); File dest = new File(TEMP_DIR, "content"); aggregator.startTransfer(dest); aggregator.cancel(); context.assertIsSatisfied(); } @Test public void testDontReturnToPending() throws Exception { testStartSourceLocation(); final ContentTransferEvent failedEvent = new ContentTransferEvent.Builder(selected) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING) .transferState(ContentTransferState.FAILED) .build(); final ContentTransferEvent stalledEvent = new ContentTransferEvent.Builder(selected) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING) .transferState(ContentTransferState.STALLED) .build(); context.checking(new Expectations() {{ // Ignore everything except selected and standby for (ContentTransfer transfer : transfers) { if (transfer != selected && transfer != standby) { allowing(transfer).getSourceLocationState(); will(returnValue(ContentSourceLocationState.NOT_LOCATING_HAS_MANY)); } } ignoring(aggListener); one(selected).startTransfer(with(any(File.class))); one(listener).contentTransferStateUpdated( with(any(ContentTransferEvent.class))); // On failure, the getContent is called to extract the exception one(selected).getContent(); will(throwException(new TransferException("Ignored"))); one(selected).cancel(); // Next batter up... one(standby).getTransferState(); will(returnValue(ContentTransferState.PENDING)); one(standby).startTransfer(with(any(File.class))); allowing(standby).getSourceLocationState(); will(returnValue(ContentSourceLocationState.LOCATING_HAS_ENOUGH)); }}); File dest = new File(TEMP_DIR, "content"); aggregator.startTransfer(dest); assertEquals(ContentTransferState.PENDING, aggregator.getTransferState()); aggregator.contentTransferStateUpdated(stalledEvent); assertEquals(ContentTransferState.STALLED, aggregator.getTransferState()); // The FAILED event should have been absorbed aggregator.contentTransferStateUpdated(failedEvent); assertEquals(ContentTransferState.STALLED, aggregator.getTransferState()); context.assertIsSatisfied(); } @Test public void testAllTransfersFail() throws Exception { testStartSourceLocation(); context.checking(new Expectations() {{ // Ignore everything except selected and standby for (ContentTransfer transfer : transfers) { // Each transfer is started once one(transfer).startTransfer(with(any(File.class))); // Each transfer fails once one(transfer).getContent(); will(throwException(new TransferException("Ignored"))); one(transfer).cancel(); if (transfer != selected) { one(transfer).getTransferState(); will(returnValue(ContentTransferState.PENDING)); } allowing(transfer).getSourceLocationState(); will(returnValue(ContentSourceLocationState.LOCATING_HAS_ENOUGH)); allowing(transfer).stopSourceLocation(); } ignoring(aggListener); one(listener).contentTransferStateUpdated( with(any(ContentTransferEvent.class))); }}); File dest = new File(TEMP_DIR, "content"); aggregator.startTransfer(dest); assertEquals(ContentTransferState.PENDING, aggregator.getTransferState()); // The FAILED event should have been absorbed aggregator.contentTransferStateUpdated( new ContentTransferEvent.Builder( aggregator.getCurrentContentTransfer()) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING) .transferState(ContentTransferState.FAILED) .build()); assertEquals(ContentTransferState.PENDING, aggregator.getTransferState()); // The FAILED event should have been absorbed aggregator.contentTransferStateUpdated( new ContentTransferEvent.Builder( aggregator.getCurrentContentTransfer()) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING) .transferState(ContentTransferState.FAILED) .build()); assertEquals(ContentTransferState.PENDING, aggregator.getTransferState()); // The FAILED event should have been absorbed aggregator.contentTransferStateUpdated( new ContentTransferEvent.Builder( aggregator.getCurrentContentTransfer()) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING) .transferState(ContentTransferState.FAILED) .build()); assertEquals(ContentTransferState.PENDING, aggregator.getTransferState()); // This is the last transfer instance, so the failure is exposed aggregator.contentTransferStateUpdated( new ContentTransferEvent.Builder( aggregator.getCurrentContentTransfer()) .locationCount(100) .locationState(ContentSourceLocationState.LOCATING) .transferState(ContentTransferState.FAILED) .build()); assertEquals(ContentTransferState.FAILED, aggregator.getTransferState()); context.assertIsSatisfied(); } }