package com.limegroup.gnutella.downloader;
import java.util.Random;
import junit.framework.Test;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.IntervalSet;
import com.limegroup.gnutella.util.PrivilegedAccessor;
import com.limegroup.gnutella.util.SystemUtils;
public class BiasedRandomDownloadStrategyTest extends BaseTestCase {
/** Smallest number that can be subtracted from 1.0f
* and get something different from 1.0f. Note that
* this differs slightly from the standard IEE 754
* definition of EPSILON by using subtraction instead
* of addition.
*/
private static float EPSILON;
static {
float epsilon = 1.0f;
while((1.0f-epsilon)!=1.0f) {
EPSILON = epsilon;
epsilon /= 2.0f;
}
}
private long fileSize;
private long blockSize;
private TestPredeterminedRandom prng;
private SelectionStrategy strategy;
private IntervalSet availableBytes;
public BiasedRandomDownloadStrategyTest(String s) {
super(s);
}
public static Test suite() {
return buildTestSuite(BiasedRandomDownloadStrategyTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public void setUp() throws Exception {
fileSize = 1024 * 1024 * 5; // 5MB
blockSize = 128 * 1024; // 128k
prng = new TestPredeterminedRandom();
strategy = createBiasedRandomStrategy(fileSize, prng);
availableBytes = IntervalSet.createSingletonSet(0, fileSize - 1);
}
/////////////// Tests /////////////////////////////////
/** Test behavior for a file just over MIN_PREVIEW_BYTES (1 MB).
* Make sure downloads are sequential before MIN_PREVIEW_BYTES,
* and then never sequential after MIN_PREVIEW_BYTES.
*/
public void testSmallFile() throws Exception {
assertEquals("Disabling of idle time has not worked. This test cannot pass.",
0L, SystemUtils.getIdleTime());
long previewLimit = ((Integer)
PrivilegedAccessor.getValue(strategy,"MIN_PREVIEW_BYTES")
).longValue(); // 1 MB
fileSize = previewLimit + 2 * blockSize + 7;
availableBytes = IntervalSet.createSingletonSet(0, fileSize-1);
// Cut out everything below 1MB, except 2 blocks at the beginning
// and end of that range.
availableBytes.delete(new Interval(2*blockSize-7, previewLimit-blockSize-2));
// 4 arbitrary floats for our 4 sequential downloads... make sure to test
// 1.0 and 0.0
float[] floats = {1.0f-EPSILON, 0.0f, 0.3728f, 0.18281828f};
prng.setFloats(floats);
strategy = createBiasedRandomStrategy(fileSize, prng);
Interval[] expectations = new Interval[4];
// full block chunk
expectations[0] = new Interval(0, blockSize-1);
// partial block chunk
expectations[1] = new Interval(blockSize, 2*blockSize-8);
// Skip a few...
// single byte chunk
expectations[2] = new Interval(previewLimit-blockSize-1);
// full block
expectations[3] = new Interval(previewLimit-blockSize,
previewLimit-1);
// Check that we got our 4 sequential downloads
testAssignments(strategy, availableBytes, fileSize,
blockSize, expectations);
// Now test that we go into non-sequential downloads
// Set the float so that we get a non-sequential download durring the
// 50% random phase.
prng.setFloat(0.500001f);
// Set the random index location and a non-sequential random location
prng.setInt(3);
prng.setLong(1);
Interval assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
// Check that it wasn't a sequential download
assertNotEquals("Got a sequential download when it should have been random",
previewLimit, assignment.low);
// Check that we cannot still force a sequential download, since the preview
// length is now greater than 50%
prng.setFloat(0.0001f);
prng.setInt(7);
prng.setLong(1);
assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertNotEquals("Expected non-sequential assignment",
availableBytes.getFirst().low,
assignment.low);
}
/** Test behavior for a file just over twice MIN_PREVIEW_BYTES.
* Make sure downloads are sequential before MIN_PREVIEW_BYTES,
* 50% random after MIN_PREVIEW_BYTES, and 100% random after the
* first 50% of the file has been downloaded.
*/
public void testMediumFile() throws Exception {
assertEquals("Disabling of idle time has not worked. This test cannot pass.",
0L, SystemUtils.getIdleTime());
long previewLimit = ((Integer)
PrivilegedAccessor.getValue(strategy,"MIN_PREVIEW_BYTES")
).longValue(); // 1 MB
fileSize = 2*previewLimit + 3;
availableBytes = IntervalSet.createSingletonSet(0, fileSize-1);
// Cut out everything below 1MB, except 2 blocks at the beginning
// and end of that range.
availableBytes.delete(new Interval(2*blockSize-7, previewLimit-blockSize-2));
// 4 arbitrary floats for our 4 sequential downloads... make sure to test
// 1.0 and 0.0
float[] floats = {1.0f-EPSILON, 0.6298f, 0.77f, 0.0f};
prng.setFloats(floats);
strategy = createBiasedRandomStrategy(fileSize, prng);
Interval[] expectations = new Interval[4];
// full block chunk
expectations[0] = new Interval(0, blockSize-1);
// partial block chunk
expectations[1] = new Interval(blockSize, 2*blockSize-8);
// Skip a few...
// single byte chunk
expectations[2] = new Interval(previewLimit-blockSize-1);
// full block
expectations[3] = new Interval(previewLimit-blockSize,
previewLimit-1);
// Check that we got our 4 sequential downloads
testAssignments(strategy, availableBytes, fileSize,
blockSize, expectations);
// Now test that we go into non-sequential downloads
// Set the float so that we get a non-sequential download durring the
// 50% random phase.
prng.setFloat(0.500001f);
// Set the random index location and a non-sequential random location
prng.setInt(3);
prng.setLong(1);
Interval assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
// Check that it wasn't a sequential download
assertNotEquals("Got a sequential download when it should have been random.",
previewLimit, assignment.low);
// Check that we still force a sequential download while the preview
// length is less than 50%
prng.setFloat(0.5f-EPSILON);
assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertEquals("Expected sequential assignment.",
availableBytes.getFirst().low,
assignment.low);
assertEquals("Expected a full block assignment",
blockSize, assignment.high-assignment.low+1);
availableBytes.delete(assignment);
// This last download pushed us over 50% previewable.
// Test that we can no longer force the download to be non-random
prng.setFloat(1.0f-EPSILON); // Nearly highest probability of being sequential
prng.setInt(5); // index into random locations array
prng.setLong(1); // random location block offset
assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertNotEquals("Expected non-sequential assignment",
availableBytes.getFirst().low,
assignment.low);
assertEquals("Expected a full block assignment",
blockSize, assignment.high-assignment.low+1);
}
/** Test behavior for a file where fileSize * MIN_PREVIEW_FRACTION
* is just over MIN_PREVIEW_BYTES.
*/
public void testLargeFile() throws Exception {
assertEquals("Disabling of idle time has not worked. This test cannot pass.",
0L, SystemUtils.getIdleTime());
long minPreviewBytes = ((Integer)
PrivilegedAccessor.getValue(strategy,"MIN_PREVIEW_BYTES")
).longValue(); // 1 MB
double minPreviewFraction = ((Float)
PrivilegedAccessor.getValue(strategy,"MIN_PREVIEW_FRACTION")
).doubleValue();
fileSize = (long) ((minPreviewBytes / minPreviewFraction)+2*blockSize);
// For this test, fileSize has been defined such that the preview limit
// will be fileSize * minPreviewFraction
long previewLimit = (long) Math.ceil(fileSize*minPreviewFraction);
long alignedPreviewLimit = previewLimit + blockSize;
alignedPreviewLimit -= alignedPreviewLimit % blockSize;
availableBytes = IntervalSet.createSingletonSet(0, fileSize-1);
// Cut out everything below previewFraction, except 2 blocks at the beginning
// and end of that range.
availableBytes.delete(new Interval(2*blockSize-7,
alignedPreviewLimit-blockSize-2));
// 4 arbitrary floats for our 4 sequential downloads... make sure to test
// 1.0 and 0.0
float[] floats = {1.0f-EPSILON, 0.6298f, 0.77f, 0.0f};
prng.setFloats(floats);
strategy = createBiasedRandomStrategy(fileSize, prng);
Interval[] expectations = new Interval[4];
// full block chunk
expectations[0] = new Interval(0, blockSize-1);
// partial block chunk
expectations[1] = new Interval(blockSize, 2*blockSize-8);
// Skip a few...
// single byte chunk
expectations[2] = new Interval(alignedPreviewLimit-blockSize-1);
// full block
expectations[3] = new Interval(alignedPreviewLimit-blockSize,
alignedPreviewLimit-1);
// Check that we got our 4 sequential downloads
testAssignments(strategy, availableBytes, fileSize,
blockSize, expectations);
// Now test that we go into non-sequential downloads
// Set the float so that we get a non-sequential download durring the
// 50% random phase.
prng.setFloat(0.500001f);
// Set the random index location and a non-sequential random location
prng.setInt(3);
prng.setLong(1);
Interval assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
// Check that it wasn't a sequential download
assertNotEquals("Got a sequential download when it should have been random.",
previewLimit, assignment.low);
// Check that we still force a sequential download while the preview
// length is less than 50%
prng.setFloat(0.5f-EPSILON);
assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertEquals("Expected sequential assignment.",
availableBytes.getFirst().low,
assignment.low);
assertEquals("Expected a full block assignment",
blockSize, assignment.high-assignment.low+1);
availableBytes.delete(assignment);
// Fast-forward to the case where the download is
// over 50% previewable.
availableBytes.delete(new Interval(0, 1+fileSize/2));
// Test that we can no longer force the download to be non-random
prng.setFloat(1.0f-EPSILON); // Nearly highest probability of being sequential
prng.setInt(5); // index into random locations array
prng.setLong(1); // random location block offset
assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertNotEquals("Expected non-sequential assignment",
availableBytes.getFirst().low,
assignment.low);
assertEquals("Expected a full block assignment",
blockSize, assignment.high-assignment.low+1);
}
/**
* Test that the biased random downloader downloads in
* random order if the user is idle, even if none of
* the file has yet been downloaded. Also test
* that if the user becomes non-idle, the system
* reverts to a sequential download if the number of
* downloaded previewable bytes is low.
*/
public void testIdle() throws Exception {
/* Set up a strategy that thinks the user is idle */
strategy = new BiasedRandomDownloadStrategy(fileSize) {
private long idleTime;
protected long getIdleTime() {
return idleTime;
}
};
PrivilegedAccessor.setValue(strategy, "pseudoRandom", prng);
PrivilegedAccessor.setValue(strategy, "idleTime",
new Long(BiasedRandomDownloadStrategy.MIN_IDLE_MILLISECONDS));
prng.setLong(1);
prng.setInt(1);
Interval assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertNotEquals("Idle download should not be sequential.",
0, assignment.low);
/* Make the user non-idle */
PrivilegedAccessor.setValue(strategy, "idleTime",
new Long(BiasedRandomDownloadStrategy.MIN_IDLE_MILLISECONDS-1));
/* Set prng for the highest probability of a random download */
prng.setFloat(0.0f);
assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
assertEquals("Non-idle download should be sequential if few bytes"+
" have been assigned.", 0, assignment.low);
}
/**
* Test that various invalid inputs throw IllegalArgumentException.
*/
public void testInvalidInputs() {
// Try an invalid block size
try {
strategy.pickAssignment(availableBytes,
availableBytes, 0);
fail("Failed to complain about invalid block size");
} catch (IllegalArgumentException e) {
// Wohoo! Exception thrown... test passed... do nothing
}
IntervalSet badNeededBytes = null;
try {
// createSingletonSet might throw its own IllegalArgumentException
// so create it outside of the try-catch
badNeededBytes = IntervalSet.createSingletonSet(-5,10);
// Try telling the strategy that we need some bytes
// before the beginning of the file
try {
strategy.pickAssignment(availableBytes, badNeededBytes, blockSize);
fail("Failed to complain about negative Intervals in neededBytes");
} catch (IllegalArgumentException e) {
// Wohoo! Exception thrown... test passed... do nothing
}
} catch (IllegalArgumentException e) {
// Wohoo! Exception thrown... test passed... do nothing
}
badNeededBytes = IntervalSet.createSingletonSet(fileSize,fileSize);
// Try telling the strategy that we need a byte after the end
// of the file
try {
strategy.pickAssignment(availableBytes,
badNeededBytes, blockSize);
fail("Failed to complain about neededBytes extending past the end of the file");
} catch (IllegalArgumentException e) {
// Wohoo! Exception thrown... test passed... do nothing
}
}
/**
* Test that asking for bytes from an empty set results
* in NoSuch ElementException.
*/
public void testNoAvailableBytes() {
// Call with empty set of candidateBytes
try {
strategy.pickAssignment(new IntervalSet(),
availableBytes, blockSize);
fail("Failed to complain about no available bytes");
} catch (java.util.NoSuchElementException e) {
// Wohoo! Exception thrown... test passed
}
}
///////////// Helper Methods //////////////////////////
/**
* A helper method that simulates chosing blocks to download and removes
* them from availableBytes.
*
* @param strategy selection strategy to be tested.
* @param availableBytes is passed to strategy.
* @param fileSize is passed to strategy.
* @param blockSize is passed to strategy.
* @param expectedAssignments are checked against the actual assignments,
* in order.
*/
private void testAssignments(SelectionStrategy strategy,
IntervalSet availableBytes, long fileSize, long blockSize,
Interval[] expectedAssignments) {
for (int i = 0; i < expectedAssignments.length; i++) {
Interval assignment = strategy.pickAssignment(availableBytes,
availableBytes, blockSize);
availableBytes.delete(assignment);
assertEquals("Wrong assignment for chunk #" + i,
expectedAssignments[i], assignment);
}
}
/** Creates a BiasedRandomDownloadStrategy, and sets its private static Random. */
private SelectionStrategy createBiasedRandomStrategy(long fileSize, Random rng)
throws IllegalAccessException, NoSuchFieldException {
BiasedRandomDownloadStrategy brds = new BiasedRandomDownloadStrategy(fileSize);
PrivilegedAccessor.setValue(brds, "pseudoRandom", rng);
return brds;
}
}