package com.limegroup.gnutella.downloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import junit.framework.Test;
import com.limegroup.gnutella.FileDesc;
import com.limegroup.gnutella.FileManager;
import com.limegroup.gnutella.IncompleteFileDesc;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.settings.SharingSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.ConverterObjectInputStream;
import com.limegroup.gnutella.util.PrivilegedAccessor;
public class IncompleteFileManagerTest extends com.limegroup.gnutella.util.BaseTestCase {
private IncompleteFileManager ifm;
private RemoteFileDesc rfd1, rfd2;
private FileManager fm;
public IncompleteFileManagerTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(IncompleteFileManagerTest.class);
}
public static void globalSetUp() {
new RouterService(new ActivityCallbackStub());
}
public void setUp() {
ifm=new IncompleteFileManager();
fm = RouterService.getFileManager();
}
/** @param urn a SHA1 urn, or null */
public static RemoteFileDesc newRFD(String name, int size, String urn) {
try {
Set urns=new HashSet(1);
if (urn!=null)
urns.add(URN.createSHA1Urn(urn));
return new RemoteFileDesc(
"18.239.0.144", 6346, 13l,
name, size,
new byte[16], 56, false, 4, true, null,
urns, false, false,"",0,null, -1);
} catch (IOException e) {
fail("Invalid URN", e);
return null;
}
}
/////////////////////////////////////////////////////////////
public void testLegacy() throws Throwable {
File file=new File(getSaveDirectory(), "T-748-test.txt");
IncompleteFileManager ifm=new IncompleteFileManager();
Iterator iter=null;
VerifyingFile vf = new VerifyingFile(748);
//0 blocks
assertNull(ifm.getEntry(file));
assertEquals(0, ifm.getBlockSize(file));
//1 block
vf.addInterval(new Interval(0,10));
ifm.addEntry(file,vf);
assertEquals(11, ifm.getBlockSize(file));//full inclusive now
iter=ifm.getEntry(file).getBlocks();
assertEquals(new Interval(0, 10), iter.next());
assertTrue(! iter.hasNext());
SharingSettings.INCOMPLETE_PURGE_TIME.setValue(26);
File young=new FakeTimedFile(25);
File old=new FakeTimedFile(27);
assertTrue(!isOld(young));
assertTrue(isOld(old));
}
public void testTempName() throws Throwable {
assertEquals("T-748-abc def",
tempName("abc def", 748, 0));
assertEquals("T-748-abc def (2)",
tempName("abc def", 748, 2));
assertEquals("T-748-abc.txt",
tempName("abc.txt", 748, 1));
assertEquals("T-748-abc (2).txt",
tempName("abc.txt", 748, 2));
assertEquals("T-748-.txt",
tempName(".txt", 748, 1));
assertEquals("T-748- (2).txt",
tempName(".txt", 748, 2));
}
/** Different name or size ==> different temp file */
public void testGetFile_DifferentSize() throws Throwable {
rfd1=newRFD("some file name", 1839, null);
rfd2=newRFD("some file name", 1223, null);
assertTrue(! IncompleteFileManager.same(rfd1, rfd2));
File tmp1=ifm.getFile(rfd1);
File tmp2=ifm.getFile(rfd2);
assertNotEquals(tmp2, tmp1);
}
/**
* You should be able to resume to a file with same hash but different name.
*/
public void testGetFile_DifferentNameSameHash() throws Throwable {
rfd1=newRFD("some file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
rfd2=newRFD("another file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
assertTrue(IncompleteFileManager.same(rfd1, rfd2));
File tmp1=ifm.getFile(rfd1);
File tmp2=ifm.getFile(rfd2);
assertEquals(tmp1, tmp2);
}
/**
* You should NOT be able to resume to file w/ same name but different hash.
*/
public void testGetFile_SameNameDifferentHash() throws Throwable {
rfd1=newRFD("some file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
rfd2=newRFD("some file name", 1839,
"urn:sha1:LSTHIPQGSGSZTS5FJPAKPZWUGYQYPFBU");
assertTrue(! IncompleteFileManager.same(rfd1, rfd2));
File tmp1=ifm.getFile(rfd1);
File tmp2=ifm.getFile(rfd2);
assertNotEquals(tmp2, tmp1);
}
/** Risky resumes are allowed: no hashes */
public void testGetFile_NoHash() throws Throwable {
rfd1=newRFD("some file name", 1839, null);
rfd2=newRFD("some file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
assertTrue(IncompleteFileManager.same(rfd1, rfd2));
File tmp1=ifm.getFile(rfd1);
File tmp2=ifm.getFile(rfd2);
assertEquals(tmp1, tmp2);
}
/** Checks that removeEntry clears blocks AND hashes. */
public void testRemoveEntry() throws Throwable {
//Populate IFM with a hash.
rfd1=newRFD("some file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
File tmp1=ifm.getFile(rfd1);
VerifyingFile vf=new VerifyingFile(1839);
ifm.addEntry(tmp1, vf);
//After deleting entry there should be no more blocks...
ifm.removeEntry(tmp1);
assertNull(ifm.getEntry(tmp1));
//...and same temp file should be used for different hash. [sic]
rfd2=newRFD("some file name", 1839,
"urn:sha1:LSTHIPQGSGSZTS5FJPAKPZWUGYQYPFBU");
File tmp2=ifm.getFile(rfd2);
assertEquals(tmp1, tmp2);
assertEquals(tmp2, ifm.getFile(newRFD("some file name", 1839, null)));
}
/**
* Checks that addEntry & removeEntry notify the FileManager of the
* added / removed incomplete file.
*/
public void testFileManagerIsNotified() throws Exception {
assertEquals(0, fm.getNumIncompleteFiles()); // begin with 0 shared.
//Populate IFM with a hash.
rfd1=newRFD("some file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
File tmp1=ifm.getFile(rfd1);
VerifyingFile vf=new VerifyingFile(1839);
ifm.addEntry(tmp1, vf);
assertEquals(1, fm.getNumIncompleteFiles()); // 1 added.
// make sure it's associated with a URN.
URN urn = URN.createSHA1Urn(
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
FileDesc fd = fm.getFileDescForUrn(urn);
assertNotNull(urn);
assertInstanceof(IncompleteFileDesc.class, fd);
ifm.removeEntry(tmp1);
assertEquals(0, fm.getNumIncompleteFiles()); // back to 0 shared.
}
public void testCompletedHash_NotFound() throws Throwable{
File tmp2=new File("T-1839-some file name");
assertNull(ifm.getCompletedHash(tmp2));
}
public void testCompletedHash_Found() throws Throwable {
rfd1=newRFD("some file name", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
File tmp1=ifm.getFile(rfd1);
try {
URN urn=URN.createSHA1Urn(
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
assertEquals(urn, ifm.getCompletedHash(tmp1));
} catch (IOException e) {
fail("Couldn't make URN", e);
}
}
public void testCompletedName() throws Throwable {
File tmp1=new File("T-1839-some file.txt");
assertEquals("some file.txt", ifm.getCompletedName(tmp1));
}
public void testCompletedName_IllegalArgument() throws Throwable {
try {
ifm.getCompletedName(new File("no dash.txt"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
try {
ifm.getCompletedName(new File("T-one dash.txt"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
try {
ifm.getCompletedName(new File("T-123-"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
}
public void testCompletedSize() throws Throwable {
File tmp1=new File("T-1839-some file.txt");
assertEquals(1839, ifm.getCompletedSize(tmp1));
}
public void testCompletedSize_IllegalArgument() throws Throwable {
try {
ifm.getCompletedSize(new File("no dash.txt"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
try {
ifm.getCompletedSize(new File("T-one dash.txt"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
try {
ifm.getCompletedSize(new File("T--no number.txt"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
try {
ifm.getCompletedSize(new File("T-x-bad number.txt"));
fail("Accepted bad file");
} catch (IllegalArgumentException pass) { }
}
/** Tests that hash information is purged when calling purge(true). */
public void testPurgeHash_Yes() throws Throwable {
//These files have the same hash, but no blocks have been written.
RemoteFileDesc rfd1=newRFD("file name.txt", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
RemoteFileDesc rfd1b=newRFD("other file.txt", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
File file1=ifm.getFile(rfd1);
file1.delete(); // getFile will create it, we don't want it created.
File file1b=ifm.getFile(rfd1b);
file1b.delete();
assertEquals(file1, file1b);
//These files have the same hash, but blocks have been written to disk.
RemoteFileDesc rfd2=newRFD("another name.txt", 1839,
"urn:sha1:LSTHIPQGSSZGTS5FJUPAKPZWUGYQYPFB");
RemoteFileDesc rfd2b=newRFD("yet another file.txt", 1839,
"urn:sha1:LSTHIPQGSSZGTS5FJUPAKPZWUGYQYPFB");
File file2=ifm.getFile(rfd2);
file2.delete();
File file2b=ifm.getFile(rfd2b);
file2b.delete();
assertEquals(file2, file2b);
try {
file2.createNewFile();
} catch (IOException e) {
fail("Couldn't create "+file2, e);
}
//After purging, only hashes associated with files that exists remain.
ifm.initialPurge(Collections.EMPTY_LIST);
File file1c=ifm.getFile(rfd1b);
assertNotEquals(file1b, file1c);
File file2c=ifm.getFile(rfd2b);
assertEquals(file2b ,file2c);
file2.delete();
}
/** Tests that hash information is not purged when calling purge(false). */
public void testPurgeHash_No() throws Throwable {
RemoteFileDesc rfd1=newRFD("file name.txt", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
RemoteFileDesc rfd2=newRFD("other file.txt", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
File file1=ifm.getFile(rfd1);
File file2=ifm.getFile(rfd2);
assertEquals(file1, file2);
ifm.purge(); //Does nothing
File file2b=ifm.getFile(rfd2);
assertEquals(file2, file2b);
}
/** Serializes, then deserializes. */
public void testSerialize() throws Exception {
File tmp=null;
try {
//Create an IFM with one entry, with hash.
IncompleteFileManager ifm1=new IncompleteFileManager();
RemoteFileDesc rfd=newRFD("file name.txt", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB");
File incomplete=ifm1.getFile(rfd);
VerifyingFile vf=new VerifyingFile(1839);
vf.addInterval(new Interval(10, 100)); //inclusive
ifm1.addEntry(incomplete, vf);
//Write to disk.
tmp=File.createTempFile("IncompleteFileManagerTest", ".dat");
ObjectOutputStream out=new ObjectOutputStream(
new FileOutputStream(tmp));
out.writeObject(ifm1);
out.close();
ifm1=null;
//Read IFM from disk.
ObjectInputStream in=new ObjectInputStream(
new FileInputStream(tmp));
IncompleteFileManager ifm2=(IncompleteFileManager)in.readObject();
in.close();
//Make sure it's the same.
File incomp = null;
File inDir = null;
inDir = SharingSettings.INCOMPLETE_DIRECTORY.getValue();
if( inDir == null ||
!inDir.isDirectory() ||
!inDir.exists() ||
!inDir.canWrite() ) {
fail("unable to set up test-cannot find incomplete directory");
}
incomp = new File(inDir, "T-1839-file name.txt");
VerifyingFile vf2=(VerifyingFile)ifm2.getEntry(incomp);
assertTrue(vf2!=null);
Iterator /* of Interval */ iter=vf2.getBlocks();
assertTrue(iter.hasNext());
assertEquals(new Interval(10, 100), iter.next());
assertTrue(!iter.hasNext());
assertEquals(new File(inDir, "T-1839-file name.txt"),
ifm2.getFile(newRFD("different name.txt", 1839,
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB")));
} finally {
if (tmp!=null)
tmp.delete();
}
}
/** Test that serialized versions of IncompleteFileManager v. 1.9 can be
* deserialized. (No hashes, didn't use VerifyingFile internally.) */
public void testDeserialize_19() throws Exception{
doDeserializeTest("IncompleteFileManager.1_9.dat");
}
/** Test that serialized versions of IncompleteFileManager v. 1.14 can be
* deserialized. (No hashes.) */
public void testOldDeserialize_114() throws Exception {
doDeserializeTest("IncompleteFileManager.1_14.dat");
}
private IncompleteFileManager doDeserializeTest(String filename) throws Exception {
IncompleteFileManager read=null;
ObjectInputStream in = new ConverterObjectInputStream(
new FileInputStream( CommonUtils.getResourceFile(
"com/limegroup/gnutella/downloader/"+filename
) )
);
read=(IncompleteFileManager)in.readObject();
in.close();
VerifyingFile vf=(VerifyingFile)read.getEntry(
new File("another file.txt"));
assertTrue(vf!=null);
Iterator /* of Interval */ iter=vf.getBlocks();
assertTrue(iter.hasNext());
assertEquals(new Interval(3, 999), iter.next());
assertTrue(!iter.hasNext());
vf=(VerifyingFile)read.getEntry(new File("hello world.txt"));
assertTrue(vf!=null);
iter=vf.getBlocks();
assertTrue(iter.hasNext());
assertEquals(new Interval(1, 99), iter.next());
assertTrue(!iter.hasNext());
return ifm;
}
private static String tempName(String s, int one, int two) throws Throwable {
try {
return (String)PrivilegedAccessor.invokeMethod(
IncompleteFileManager.class, "tempName",
new Object[] {s, new Integer(one), new Integer(two)},
new Class[] {String.class, Integer.TYPE, Integer.TYPE});
} catch(Exception e) {
if ( e.getCause() != null )
throw e.getCause();
else throw e;
}
}
private static boolean isOld(File f) throws Throwable {
try {
return ((Boolean)PrivilegedAccessor.invokeMethod(
IncompleteFileManager.class, "isOld",
new Object[] {f},
new Class[] {File.class} )).booleanValue();
} catch(Exception e) {
if ( e.getCause() != null )
throw e.getCause();
else throw e;
}
}
static class FakeTimedFile extends File {
private long days;
FakeTimedFile(int days) {
super("whatever.txt");
this.days=days;
}
public long lastModified() {
//30 days ago
return System.currentTimeMillis()-days*24l*60l*60l*1000l;
}
}
}