package com.limegroup.gnutella.downloader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.core.settings.ConnectionSettings; import org.limewire.core.settings.DownloadSettings; import org.limewire.core.settings.SharingSettings; import org.limewire.gnutella.tests.ActivityCallbackStub; import org.limewire.gnutella.tests.LimeTestCase; import org.limewire.gnutella.tests.LimeTestUtils; import org.limewire.gnutella.tests.NetworkManagerStub; import org.limewire.io.ConnectableImpl; import org.limewire.io.GUID; import org.limewire.io.IpPortImpl; import org.limewire.io.IpPortSet; import org.limewire.io.LimeWireIOTestModule; import org.limewire.io.NetworkUtils; import org.limewire.net.SocketsManager; import org.limewire.util.FileUtils; import org.limewire.util.PrivilegedAccessor; import org.limewire.util.TestUtils; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.limegroup.gnutella.Acceptor; import com.limegroup.gnutella.ActivityCallback; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.DownloadManager; import com.limegroup.gnutella.DownloadServices; import com.limegroup.gnutella.Downloader; import com.limegroup.gnutella.LifecycleManager; import com.limegroup.gnutella.NetworkManager; import com.limegroup.gnutella.PushEndpoint; import com.limegroup.gnutella.PushEndpointFactory; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.Downloader.DownloadState; import com.limegroup.gnutella.altlocs.AltLocManager; import com.limegroup.gnutella.altlocs.AlternateLocationFactory; import com.limegroup.gnutella.auth.ContentManager; import com.limegroup.gnutella.browser.MagnetOptions; import com.limegroup.gnutella.library.FileCollection; import com.limegroup.gnutella.library.FileView; import com.limegroup.gnutella.library.GnutellaFiles; import com.limegroup.gnutella.library.IncompleteFileCollection; import com.limegroup.gnutella.library.Library; import com.limegroup.gnutella.messages.MessageFactory; import com.limegroup.gnutella.messages.vendor.HeadPongFactory; import com.limegroup.gnutella.stubs.ConnectionManagerStub; import com.limegroup.gnutella.tigertree.HashTreeCache; public abstract class DownloadTestCase extends LimeTestCase { protected final Log LOG = LogFactory.getLog(getClass()); protected static final String filePath = "com/limegroup/gnutella/downloader/DownloadTestData/"; protected File dataDir = TestUtils.getResourceFile(filePath); protected File saveDir = (TestUtils.getResourceFile(filePath + "saved")).getAbsoluteFile(); // a random name for the saved file protected final String savedFileName = "DownloadTester2834343.out"; protected File savedFile; protected TestUploader[] testUploaders = new TestUploader[5]; protected int[] PORTS = { 6321, 6322, 6323, 6324, 6325 }; protected final Object COMPLETE_LOCK = new Object(); protected boolean REMOVED = false; // default to waiting for 2 defaults. protected final long DEFAULT_WAIT_TIME = 1000 * 60 * 1; protected long DOWNLOAD_WAIT_TIME = DEFAULT_WAIT_TIME; protected boolean saveAltLocs = false; protected Set validAlts = null; protected Set invalidAlts = null; @Inject protected Injector injector; @Inject protected DownloadManager downloadManager; protected ActivityCallbackStub activityCallback; protected ManagedDownloaderImpl managedDownloader; @Inject protected HashTreeCache tigerTreeCache; @Inject protected DownloadServices downloadServices; @Inject protected AlternateLocationFactory alternateLocationFactory; protected NetworkManagerStub networkManager; @Inject protected UDPService udpService; @Inject protected PushEndpointFactory pushEndpointFactory; @Inject protected Acceptor acceptor; @Inject protected VerifyingFileFactory verifyingFileFactory; @Inject protected AltLocManager altLocManager; @Inject protected Library library; @Inject protected SourceRankerFactory sourceRankerFactory; @Inject protected ContentManager contentManager; @Inject protected HeadPongFactory headPongFactory; @Inject protected SocketsManager socketsManager; @Inject protected MessageFactory messageFactory; @Inject private LifecycleManager lifecycleManager; @Inject protected RemoteFileDescFactory remoteFileDescFactory; @Inject protected DownloadStatsTracker statsTracker; @Inject @GnutellaFiles protected FileView gnutellaFileView; @Inject @GnutellaFiles protected FileCollection gnutellaFileCollection; @Inject protected IncompleteFileCollection incompleteFileCollection; @Inject @Named("backgroundExecutor") ScheduledExecutorService scheduledExecutorService; protected DownloadTestCase(String name) { super(name); } protected void setDownloadWaitTime(long time) { DOWNLOAD_WAIT_TIME = time; } @Override protected void setUp() throws Exception { setDownloadWaitTime(DEFAULT_WAIT_TIME); // raise the download-bytes-per-sec so stealing is easier DownloadSettings.MAX_DOWNLOAD_BYTES_PER_SEC.setValue(10); activityCallback = new MyCallback(); injector = LimeTestUtils.createInjector(new LimeWireIOTestModule(), NetworkManagerStub.MODULE, new AbstractModule() { @Override protected void configure() { bind(ActivityCallback.class).toInstance(activityCallback); bind(ConnectionManager.class).to(ConnectionManagerStub.class); } }, LimeTestUtils.createModule(this)); networkManager = (NetworkManagerStub) injector.getInstance(NetworkManager.class); networkManager.setAcceptedIncomingConnection(true); networkManager.setAddress(NetworkUtils.getLocalAddress().getAddress()); networkManager.setTls(true); networkManager.setOutgoingTLSEnabled(false); networkManager.setIncomingTLSEnabled(true); ConnectionManagerStub connectionManager = (ConnectionManagerStub) injector .getInstance(ConnectionManager.class); connectionManager.setConnected(true); downloadManager.start(); Runnable click = new Runnable() { public void run() { downloadManager.measureBandwidth(); } }; scheduledExecutorService.scheduleWithFixedDelay(click, 0, 1000, TimeUnit.MILLISECONDS); lifecycleManager.start(); networkManager.setPort(acceptor.getPort(false)); managedDownloader = null; ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false); // Don't wait for network connections for testing RequeryManager.NO_DELAY = true; for (int i = 0; i < testUploaders.length; i++) { testUploaders[i] = injector.getInstance(TestUploader.class); testUploaders[i].start("PORT_" + i, PORTS[i], false); } deleteAllFiles(); dataDir.mkdirs(); saveDir.mkdirs(); SharingSettings.setSaveDirectory(saveDir); //Pick random name for file. savedFile = new File(saveDir, savedFileName); savedFile.delete(); ConnectionSettings.CONNECTION_SPEED.setValue(1000); tigerTreeCache = injector.getInstance(HashTreeCache.class); tigerTreeCache.purgeTree(TestFile.hash()); } @Override protected void tearDown() throws Exception { if (lifecycleManager != null) lifecycleManager.shutdown(); for (int i = 0; i < testUploaders.length; i++) { if (testUploaders[i] != null) { testUploaders[i].reset(); testUploaders[i].stopThread(); } } deleteAllFiles(); if (injector != null) injector.getInstance( Key.get(ScheduledExecutorService.class, Names.named("backgroundExecutor"))) .shutdownNow(); if (lifecycleManager != null) { lifecycleManager.shutdown(); } } private void deleteAllFiles() { if (!dataDir.exists()) return; File[] files = dataDir.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { if (files[i].getName().equalsIgnoreCase("incomplete")) FileUtils.deleteRecursive(files[i]); else if (files[i].getName().equals(saveDir.getName())) FileUtils.deleteRecursive(files[i]); } } dataDir.delete(); } protected void tGeneric(RemoteFileDesc[] rfds) throws Exception { tGeneric(rfds, new RemoteFileDesc[0]); } protected void tGeneric(RemoteFileDesc[] rfds, List<? extends RemoteFileDesc> alts) throws Exception { tGeneric(rfds, null, alts); } protected void tGeneric(RemoteFileDesc[] now, RemoteFileDesc[] later) throws Exception { tGeneric(now, later, RemoteFileDesc.EMPTY_LIST); } /** * Performs a generic download of the file specified in <tt>rfds</tt>. */ protected void tGeneric(RemoteFileDesc[] rfds, RemoteFileDesc[] later, List<? extends RemoteFileDesc> alts) throws Exception { Downloader download; download = downloadServices.download(rfds, alts, null, false); tGeneric(download, later, rfds); } protected void tGeneric(MagnetOptions magnet) throws Exception { Downloader download = downloadServices.download(magnet, false); tGeneric(download, null, null); } /** * Performs a generic download of the file specified in <tt>rfds</tt>. * * @param later can be null * @param rfds can be null */ protected void tGeneric(Downloader download, RemoteFileDesc[] later, RemoteFileDesc[] rfds) throws Exception { if (later != null) { Thread.sleep(100); for (int i = 0; i < later.length; i++) ((ManagedDownloader) download).addDownload(later[i], true); } waitForComplete(); if (isComplete()) LOG.debug("pass" + "\n"); else fail("FAILED: complete corrupt"); IncompleteFileManager ifm = downloadManager.getIncompleteFileManager(); for (int i = 0; rfds != null && i < rfds.length; i++) { File incomplete = ifm.getFile(rfds[i]); VerifyingFile vf = ifm.getEntry(incomplete); assertNull("verifying file should be null", vf); } } /** * Performs a generic download of the file specified in <tt>rfds</tt>. */ protected void tGenericCorrupt(RemoteFileDesc[] rfds, RemoteFileDesc[] later) throws Exception { Downloader download; download = downloadServices.download(rfds, false, null); if (later != null) { Thread.sleep(100); for (int i = 0; i < later.length; i++) ((ManagedDownloader) download).addDownload(later[i], true); } waitForCorrupt(); if (isComplete()) fail("should be corrupt"); else LOG.debug("pass"); IncompleteFileManager ifm = downloadManager.getIncompleteFileManager(); for (int i = 0; i < rfds.length; i++) { File incomplete = ifm.getFile(rfds[i]); VerifyingFile vf = ifm.getEntry(incomplete); assertNull("verifying file should be null", vf); } } protected RemoteFileDesc newRFDPush(GUID guid, int port, int suffix) throws Exception { return newRFDPush(guid, port, suffix, 1); } protected RemoteFileDesc newRFDPush(GUID guid, int port, int rfdSuffix, int proxySuffix) throws Exception { Set<URN> urns = new HashSet<URN>(); urns.add(TestFile.hash()); PushEndpoint pe = pushEndpointFactory.createPushEndpoint(guid.bytes(), new IpPortSet(new IpPortImpl("127.0.0." + proxySuffix, port)), (byte) 0, (byte) 0, new IpPortImpl("127.0.0." + rfdSuffix, 6346)); pe.updateProxies(true); return remoteFileDescFactory.createRemoteFileDesc(pe, 0, savedFile.getName(), TestFile.length(), pe.getClientGUID(), 100, 1, false, null, urns, false, "ALT", 0); } protected RemoteFileDesc newRFD(int port, boolean useTLS) throws Exception { return remoteFileDescFactory.createRemoteFileDesc(new ConnectableImpl("127.0.0.1", port, useTLS), 0, savedFile.getName(), TestFile.length(), GUID.makeGuid(), 100, 4, false, null, URN.NO_URN_SET, false, "", -1); } protected RemoteFileDesc newRFDWithURN(int port, boolean useTLS) throws Exception { return newRFDWithURN(port, null, useTLS); } protected RemoteFileDesc newRFDWithURN(int port, String urn, boolean useTLS) throws Exception { Set<URN> set = new HashSet<URN>(); try { // for convenience, don't require that they pass the urn. // assume a null one is the TestFile's hash. if (urn == null) set.add(TestFile.hash()); else set.add(URN.createSHA1Urn(urn)); } catch (Exception e) { fail("SHA1 not created for: " + savedFile, e); } return remoteFileDescFactory.createRemoteFileDesc(new ConnectableImpl("127.0.0.1", port, useTLS), 0, savedFile.getName(), TestFile.length(), GUID.makeGuid(), 100, 4, false, null, set, false, "", -1); } /** Returns true if the complete file exists and is complete */ protected final boolean isComplete() { return isComplete(savedFile, TestFile.length()); } protected final boolean isComplete(File f, long length) { LOG.debug("file is " + f.getPath()); if (f.length() < length) { LOG.debug("File too small by: " + (length - f.length())); return false; } else if (savedFile.length() > TestFile.length()) { LOG.debug("File too large by: " + (length - f.length())); return false; } FileInputStream stream = null; try { stream = new FileInputStream(f); for (int i = 0;; i++) { int c = stream.read(); if (c == -1)//eof break; if ((byte) c != TestFile.getByte(i)) { LOG.debug("Bad byte at " + i + "\n"); return false; } } } catch (IOException ioe) { //ioe.printStackTrace(); return false; } finally { if (stream != null) { try { stream.close(); } catch (IOException ignored) { } } } return true; } protected final int CORRUPT = 1; protected final int COMPLETE = 2; protected final int INVALID = 3; protected void waitForComplete(boolean corrupt) { waitForCompleteImpl(corrupt ? CORRUPT : COMPLETE); } protected void waitForCorrupt() { waitForCompleteImpl(CORRUPT); } protected void waitForInvalid() { waitForCompleteImpl(INVALID); } protected void waitForComplete() { waitForCompleteImpl(COMPLETE); } protected void waitForCompleteImpl(int state) { synchronized (COMPLETE_LOCK) { try { REMOVED = false; LOG.debug("starting wait"); COMPLETE_LOCK.wait(DOWNLOAD_WAIT_TIME); LOG.debug("finished waiting"); } catch (InterruptedException e) { LOG.debug("interrupted", e); //good. } } if (!REMOVED) { downloadManager.remove(managedDownloader, false); fail("download did not finish, last state was: " + managedDownloader.getState()); } if (state == CORRUPT) assertEquals("unexpected state", DownloadState.CORRUPT_FILE, managedDownloader .getState()); else if (state == INVALID) assertEquals("unexpected state", DownloadState.INVALID, managedDownloader.getState()); else if (state == COMPLETE) assertEquals("unexpected state", DownloadState.COMPLETE, managedDownloader.getState()); else fail("bad expectation: " + state); } protected void waitForBusy(Downloader downloader) { for (int i = 0; i < 12; i++) { //wait 12 seconds if (downloader.getState() == DownloadState.BUSY) return; try { Thread.sleep(1000);// try again after a second } catch (InterruptedException e) { fail("downloader unexpecteted interrupted", e); return; } } } protected final class MyCallback extends ActivityCallbackStub { @Override public void addDownload(Downloader d) { managedDownloader = (ManagedDownloaderImpl) d; } @SuppressWarnings("unchecked") @Override public void removeDownload(Downloader d) { synchronized (COMPLETE_LOCK) { REMOVED = true; COMPLETE_LOCK.notify(); } if (saveAltLocs) { try { validAlts = new HashSet(); validAlts.addAll((Set) PrivilegedAccessor.getValue(d, "validAlts")); invalidAlts = new HashSet(); invalidAlts.addAll((Set) PrivilegedAccessor.getValue(d, "invalidAlts")); } catch (Exception err) { throw new RuntimeException(err); } } } } }