package com.limegroup.gnutella.io;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import junit.framework.Test;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.PrivilegedAccessor;
/**
* Tests that Throttle does its thing.
*/
public final class NBThrottleTest extends BaseTestCase {
private final int TICKS_PER_SECOND = 10;
private final float RATE = 3 * 1024; // 3KB/s
private final int BYTES_PER_TICK = (int)((float)RATE / TICKS_PER_SECOND);
private final int MILLIS_PER_TICK = 1000 / TICKS_PER_SECOND;
private Data[] DATA = new Data[50];
private NBThrottle THROTTLE;
public NBThrottleTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(NBThrottleTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public void setUp() throws Exception {
THROTTLE = newNBThrottle(true, RATE, MILLIS_PER_TICK);
for(int i = 0; i < DATA.length; i++)
DATA[i] = new Data(THROTTLE);
// control.
for(int i = 0; i < DATA.length; i++)
assertEquals(0, DATA[i].STUB.available());
}
// nothing should have bandwidth if nothing was interested
public void testNoBandwidthIfNothingInterested() throws Exception {
THROTTLE.tick(0);
for(int i = 0; i < DATA.length; i++)
assertEquals(0, DATA[i].STUB.available());
}
// only those that were interested should be given bandwidth.
public void testBandwidthOnTickWithInterest() throws Exception {
// Set interest on some things, tick & check.
for(int i = 0; i < DATA.length; i+=3)
THROTTLE.interest(DATA[i].STUB);
THROTTLE.tick(1000);
for(int i = 0; i < DATA.length; i++)
assertEquals(i%3==0 ? 1 : 0, DATA[i].STUB.available());
}
// only those that were NEWLY interested should be given bandwidth
// on the second tick.
public void testBandwidthOnTickToNewInterestOnly() throws Exception {
for(int i = 0; i < DATA.length; i+=3)
THROTTLE.interest(DATA[i].STUB);
THROTTLE.tick(1000);
// When requesting interest again, people who already were interested
// should not be given bandwidth again, but new people should.
for(int i = 0; i < DATA.length; i+=2)
THROTTLE.interest(DATA[i].STUB);
THROTTLE.tick(2000);
// if this wasn't working, i%3 && i%2 would give two availables.
for(int i = 0; i < DATA.length; i++)
assertEquals((i%3==0 || i%2 == 0)? 1 : 0, DATA[i].STUB.available());
}
// make sure that if bandwidth is still available, ticks within the tick interval
// will spread bandwidth also.
public void testBandwidthWithinTickIntervalSpreadsAvailable() throws Exception {
for(int i = 0; i < DATA.length; i+=3)
THROTTLE.interest(DATA[i].STUB);
THROTTLE.tick(1000);
for(int i = 0; i < DATA.length; i+=5)
THROTTLE.interest(DATA[i].STUB);
THROTTLE.tick(1001);
// if this wasn't working (i%5 && !i%3) would give 0 available.
for(int i = 0; i < DATA.length; i++)
assertEquals((i%3==0 || i%5 == 0)? 1 : 0, DATA[i].STUB.available());
}
// make sure it gives no bandwidth if it isn't active (within a selectableKeys call)
public void testNoRequestIfNotActive() throws Exception {
THROTTLE.tick(1000);
assertEquals(0, THROTTLE.request());
}
// tests the internal implementation details of NBThrottle --
// but, if this is wrong, all other tests aren't going to work
// ('cause they rely on this being the implementation)
public void testBytesPerTick() throws Exception {
int bytesPerTick = ((Integer)PrivilegedAccessor.getValue(THROTTLE, "_bytesPerTick")).intValue();
assertEquals(BYTES_PER_TICK, bytesPerTick);
}
// make sure we give bandwidth within a selectableKeys after it was active.
public void testRequestWithinSelectable() throws Exception {
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
THROTTLE.selectableKeys(set(DATA[0].KEY));
assertEquals(BYTES_PER_TICK, DATA[0].ATTACHMENT.given());
}
// make sure that no bandwidth is given if we're ticked and bandwidth is used up.
public void testNoBandwidthIfEmptyWithinTickInterval() throws Exception {
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
assertEquals(1, DATA[0].STUB.available());
THROTTLE.selectableKeys(set(DATA[0].KEY));
assertEquals(BYTES_PER_TICK, DATA[0].ATTACHMENT.given());
DATA[0].STUB.clear();
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(1001);
assertEquals(0, DATA[0].STUB.available());
assertEquals(0, DATA[1].STUB.available());
}
// make sure bandwidth fills back up after tick interval
public void testBandwidthFillsAfterTickInterval() throws Exception {
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
assertEquals(1, DATA[0].STUB.available());
THROTTLE.selectableKeys(set(DATA[0].KEY));
assertEquals(BYTES_PER_TICK, DATA[0].ATTACHMENT.given());
DATA[0].STUB.clear();
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(1001);
assertEquals(0, DATA[0].STUB.available());
assertEquals(0, DATA[1].STUB.available());
THROTTLE.tick(1000 + MILLIS_PER_TICK + 1);
assertEquals(1, DATA[1].STUB.available());
THROTTLE.selectableKeys(set(DATA[1].KEY));
assertEquals(BYTES_PER_TICK, DATA[1].ATTACHMENT.given());
}
// make sure that if the first interested ready host used up all the bandwidth,
// the next one won't be told to write.
public void testStopsReadiesWhenNoneLeft() throws Exception {
// interest in two different ticks to ensure that 0 is in line before 1.
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(2000);
THROTTLE.selectableKeys(set( new Object[] { DATA[0].KEY, DATA[1].KEY } ) );
assertEquals(BYTES_PER_TICK, DATA[0].ATTACHMENT.given());
assertEquals(0, DATA[1].ATTACHMENT.given());
assertEquals(1, DATA[0].ATTACHMENT.wrote());
assertEquals(0, DATA[1].ATTACHMENT.wrote());
}
// make sure that releases are added back into the pool & given to future folks.
public void testReleaseGivesToReadies() throws Exception {
// interest in two different ticks to ensure that 0 is in line before 1.
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(2000);
DATA[0].ATTACHMENT.setAmountToUse(BYTES_PER_TICK - 100);
THROTTLE.selectableKeys(set( new Object[] { DATA[0].KEY, DATA[1].KEY } ) );
assertEquals(BYTES_PER_TICK, DATA[0].ATTACHMENT.given());
assertEquals(100, DATA[1].ATTACHMENT.given());
assertEquals(1, DATA[0].ATTACHMENT.wrote());
assertEquals(1, DATA[1].ATTACHMENT.wrote());
}
// make sure that if someone didn't get to write on this turn, they'll be told to write
// before others on the next turn.
public void testInterestGivenReadiesInOrderThroughSuccessiveCalls() throws Exception {
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(2000);
THROTTLE.selectableKeys(set( new Object[] { DATA[0].KEY, DATA[1].KEY } ) );
assertEquals(BYTES_PER_TICK, DATA[0].ATTACHMENT.given());
assertEquals(0, DATA[1].ATTACHMENT.given());
assertEquals(1, DATA[0].ATTACHMENT.wrote());
assertEquals(0, DATA[1].ATTACHMENT.wrote());
//[1] didn't get used, so it's still in line.
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(3000);
DATA[1].ATTACHMENT.setAmountToUse(BYTES_PER_TICK - 50);
THROTTLE.selectableKeys(set( new Object[] { DATA[0].KEY, DATA[1].KEY } ) );
assertEquals(BYTES_PER_TICK, DATA[1].ATTACHMENT.given());
assertEquals(50, DATA[0].ATTACHMENT.given());
assertEquals(1, DATA[1].ATTACHMENT.wrote());
assertEquals(2, DATA[0].ATTACHMENT.wrote());
}
// make sure that uninterested parties aren't given any data to write.
public void testUniterestedNotUsed() throws Exception {
THROTTLE.tick(1000);
THROTTLE.selectableKeys(set(DATA[0].KEY));
assertEquals(0, DATA[0].ATTACHMENT.given());
assertEquals(0, DATA[0].ATTACHMENT.wrote());
}
// tests that the tick interval is dependent on the millis per tick.
public void testTickFollowsGivenMillis() throws Exception {
THROTTLE = newNBThrottle(true, 5000, 77);
fixDataThrottles();
THROTTLE.interest(DATA[0].STUB);
THROTTLE.tick(1000);
assertEquals(1, DATA[0].STUB.available());
THROTTLE.selectableKeys(set(DATA[0].KEY));
THROTTLE.interest(DATA[1].STUB);
assertEquals(0, DATA[1].STUB.available());
THROTTLE.tick(1076);
assertEquals(0, DATA[1].STUB.available());
THROTTLE.tick(1077);
assertEquals(1, DATA[1].STUB.available());
}
private Set set(Object o) {
Set set = new HashSet();
set.add(o);
return set;
}
private Set set(Object[] os) {
Set set = new HashSet(Arrays.asList(os));
return set;
}
private void fixDataThrottles() {
for(int i = 0; i < DATA.length; i++)
DATA[i].ATTACHMENT.setThrottle(THROTTLE);
}
private static class Data {
private StubReadWriteObserver ATTACHMENT = new StubReadWriteObserver();
private StubThrottleListener STUB = new StubThrottleListener(ATTACHMENT);
private FakeSelectionKey KEY = new FakeSelectionKey(NIODispatcher.instance().new Attachment(ATTACHMENT));
Data(Throttle throttle) {
ATTACHMENT.setThrottle(throttle);
}
}
private NBThrottle newNBThrottle(boolean write, float bps, int millisPerTick) throws Exception {
return (NBThrottle)PrivilegedAccessor.invokeConstructor(
NBThrottle.class,
new Object[] { new Boolean(write), new Float(bps), Boolean.FALSE, new Integer(millisPerTick) },
new Class[] { Boolean.TYPE, Float.TYPE, Boolean.TYPE, Integer.TYPE }
);
}
}