package org.limewire.nio;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import junit.framework.Test;
import org.limewire.nio.observer.StubReadWriteObserver;
import org.limewire.util.BaseTestCase;
import org.limewire.util.PrivilegedAccessor;
/**
* Tests that Throttle does its thing.
*/
@SuppressWarnings( { "unchecked", "cast" } )
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());
}
@Override
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.bandwidthAvailableCalls());
}
// 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.bandwidthAvailableCalls());
}
// 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.bandwidthAvailableCalls());
}
// 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++) {
int calls = DATA[i].STUB.bandwidthAvailableCalls();
if (i%3==0 || i%5 == 0)
assertGreaterThan(0, calls);
else
assertEquals(0, calls);
}
}
// 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].STUB.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.bandwidthAvailableCalls());
THROTTLE.selectableKeys(set(DATA[0].KEY));
assertEquals(BYTES_PER_TICK, DATA[0].STUB.given());
DATA[0].STUB.clear();
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(1001);
assertEquals(0, DATA[0].STUB.bandwidthAvailableCalls());
assertEquals(0, DATA[1].STUB.bandwidthAvailableCalls());
}
// 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.bandwidthAvailableCalls());
THROTTLE.selectableKeys(set(DATA[0].KEY));
assertEquals(BYTES_PER_TICK, DATA[0].STUB.given());
DATA[0].STUB.clear();
THROTTLE.interest(DATA[1].STUB);
THROTTLE.tick(1001);
assertEquals(0, DATA[0].STUB.bandwidthAvailableCalls());
assertEquals(0, DATA[1].STUB.bandwidthAvailableCalls());
THROTTLE.tick(1000 + MILLIS_PER_TICK + 1);
assertEquals(1, DATA[1].STUB.bandwidthAvailableCalls());
THROTTLE.selectableKeys(set(DATA[1].KEY));
assertEquals(BYTES_PER_TICK, DATA[1].STUB.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].STUB.given());
assertEquals(0, DATA[1].STUB.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].STUB.given());
assertEquals(100, DATA[1].STUB.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].STUB.given());
assertEquals(0, DATA[1].STUB.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);
// make sure the selector ticks so that the key can get processed
NIODispatcher.instance().wakeup();
Thread.sleep(10);
THROTTLE.selectableKeys(set( new Object[] { DATA[0].KEY, DATA[1].KEY } ) );
assertEquals(BYTES_PER_TICK, DATA[1].STUB.given());
assertEquals(50, DATA[0].STUB.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].STUB.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.bandwidthAvailableCalls());
THROTTLE.selectableKeys(set(DATA[0].KEY));
THROTTLE.interest(DATA[1].STUB);
assertEquals(0, DATA[1].STUB.bandwidthAvailableCalls());
THROTTLE.tick(1076);
assertEquals(0, DATA[1].STUB.bandwidthAvailableCalls());
THROTTLE.tick(1077);
assertEquals(1, DATA[1].STUB.bandwidthAvailableCalls());
}
// tests whether retrieving the next tick time works properly
public void testNextTickTime() {
// with nobody interested, the next tick is never
assertEquals(Long.MAX_VALUE, THROTTLE.nextTickTime());
THROTTLE.tick(0);
assertEquals(Long.MAX_VALUE, THROTTLE.nextTickTime());
// with somebody interested, the next tick is 100
THROTTLE.interest(DATA[0].STUB);
assertEquals(100, THROTTLE.nextTickTime());
// and until that time elapses, it stays 100
THROTTLE.tick(50);
assertEquals(100, THROTTLE.nextTickTime());
THROTTLE.tick(99);
assertEquals(100, THROTTLE.nextTickTime());
// after it elapses, it becomes 200
THROTTLE.tick(100);
assertEquals(200, THROTTLE.nextTickTime());
// if nobody is interested anymore, its never again
DATA[0].STUB.setClosed(true);
THROTTLE.selectableKeys( set (new Object[] { DATA[0].KEY }) );
assertEquals(Long.MAX_VALUE, THROTTLE.nextTickTime());
}
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].STUB.setThrottle(THROTTLE);
}
private static class Data {
private StubReadWriteObserver ATTACHMENT;
private StubThrottleListener STUB;
private FakeSelectionKey KEY;
Data(Throttle throttle) {
ATTACHMENT = new StubReadWriteObserver();
STUB = new StubThrottleListener(ATTACHMENT, throttle);
KEY = new FakeSelectionKey(NIODispatcher.instance().new Attachment(ATTACHMENT));
}
}
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 }
);
}
}