package com.limegroup.bittorrent.choking; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.hamcrest.Matchers; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.States; import org.limewire.collection.NECallable; import org.limewire.core.settings.BittorrentSettings; import junit.framework.Test; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.name.Names; import com.limegroup.bittorrent.Chokable; import com.limegroup.gnutella.LimeTestUtils; import com.limegroup.gnutella.UploadServices; import com.limegroup.gnutella.stubs.ScheduledExecutorServiceStub; import com.limegroup.gnutella.util.LimeTestCase; public class LeechChokerTest extends LimeTestCase { public LeechChokerTest(String name) { super(name); } public static Test suite() { return buildTestSuite(LeechChokerTest.class); } private Mockery mockery; @Override public void setUp() throws Exception { mockery = new Mockery(); } /** * tests that the choker uses the provided scheduler service when started, executed and shutdown. */ public void testStartRunShutdown() throws Exception { final ScheduledExecutorService ses = mockery.mock(ScheduledExecutorService.class); Module m = new AbstractModule() { @Override public void configure() { bind(ScheduledExecutorService.class).annotatedWith(Names.named("nioExecutor")).toInstance(ses); } }; final ScheduledFuture chokerFuture = mockery.mock(ScheduledFuture.class); final ScheduledFuture chokerFuture2 = mockery.mock(ScheduledFuture.class); NECallable<List<? extends Chokable>> emptyCallable = new NECallable<List<? extends Chokable>>() { public List<? extends Chokable> call() { return Collections.emptyList(); } }; Injector inj = LimeTestUtils.createInjector(m); ChokerFactory factory = inj.getInstance(ChokerFactory.class); final Choker c = factory.getChoker(emptyCallable, false); final States sesState = mockery.states("sesStates").startsAs("empty"); mockery.checking(new Expectations() {{ one(ses).schedule(c, 10 * 1000, TimeUnit.MILLISECONDS); when(sesState.is("empty")); will(returnValue(chokerFuture)); then(sesState.is("scheduled")); one(ses).schedule(c, 10 * 1000, TimeUnit.MILLISECONDS); when(sesState.is("scheduled")); will(returnValue(chokerFuture2)); then(sesState.is("rescheduled")); one(chokerFuture2).cancel(false); when(sesState.is("rescheduled")); }}); c.start(); c.run(); c.shutdown(); mockery.assertIsSatisfied(); } /** * tests that calls to rechoke() happen immediately on the nio executor. */ @SuppressWarnings("unchecked") public void testImmediateRechoke() throws Exception { final NECallable<List<? extends Chokable>> callable = mockery.mock(NECallable.class); final ScheduledExecutorService myService = new ScheduledExecutorServiceStub() { @Override public void execute(Runnable command) { command.run(); } }; Module m = new AbstractModule() { @Override public void configure() { bind(ScheduledExecutorService.class).annotatedWith(Names.named("nioExecutor")).toInstance(myService); } }; Injector inj = LimeTestUtils.createInjector(m); ChokerFactory factory = inj.getInstance(ChokerFactory.class); final Choker c = factory.getChoker(callable, false); mockery.checking(new Expectations() {{ one(callable).call(); will(returnValue(Collections.emptyList())); }}); c.rechoke(); mockery.assertIsSatisfied(); } /** * tests that getting the number of allowed uploads depends on the * manual setting or the upload speed. */ public void testNumUploads() throws Exception { final UploadServices uServices = mockery.mock(UploadServices.class); Module m = new AbstractModule() { @Override public void configure() { bind(UploadServices.class).toInstance(uServices); } }; Injector inj = LimeTestUtils.createInjector(m); ChokerFactory factory = inj.getInstance(ChokerFactory.class); Choker c = factory.getChoker(null, false); mockery.checking(new Expectations() {{ exactly(5).of(uServices).getRequestedUploadSpeed(); // would be 6 if setting were ignored. will(onConsecutiveCalls( returnValue(Float.MAX_VALUE), returnValue(100000.0f), returnValue(41000.0f), returnValue(14000.0f), returnValue(8000.0f))); }}); BittorrentSettings.TORRENT_MAX_UPLOADS.setValue(1); assertEquals(1,c.getNumUploads()); BittorrentSettings.TORRENT_MAX_UPLOADS.setValue(0); assertEquals(7,c.getNumUploads()); assertEquals(244, c.getNumUploads()); // Math.sqrt(rate * 0.6f) // copied from bram assertEquals(4,c.getNumUploads()); assertEquals(3,c.getNumUploads()); assertEquals(2,c.getNumUploads()); mockery.assertIsSatisfied(); } /** * tests unchokings for speed and optimistic */ public void testRechokeSpeedOptimistic() throws Exception { // provide an executor that executes stuff immediately final ScheduledExecutorService myService = new ScheduledExecutorServiceStub() { @Override public void execute(Runnable command) { command.run(); } }; Module m = new AbstractModule() { @Override public void configure() { bind(ScheduledExecutorService.class).annotatedWith(Names.named("nioExecutor")).toInstance(myService); } }; Injector inj = LimeTestUtils.createInjector(m); ChokerFactory factory = inj.getInstance(ChokerFactory.class); // create a bunch of mock Chockables final List<Chokable> chokables = new ArrayList<Chokable>(10); for (int i = 0; i < 4; i++) chokables.add(mockery.mock(Chokable.class)); NECallable<List<? extends Chokable>> neCallable = new NECallable<List<? extends Chokable>>() { public List<? extends Chokable> call() { return chokables; } }; final Choker c = factory.getChoker(neCallable, false); // two will get unchoked for speed, one will get unchoked optimistically. BittorrentSettings.TORRENT_MAX_UPLOADS.setValue(3); // each chokable will be queried for interest, supposed interest and speed mockery.checking(new Expectations(){{ // chokable 0 is interested, and slow Chokable c = chokables.get(0); atLeast(1).of(c).isInterested(); will(returnValue(true)); atLeast(1).of(c).shouldBeInterested(); will(returnValue(true)); atLeast(1).of(c).getMeasuredBandwidth(true, false); will(returnValue(0.1f)); atMost(1).of(c).isChoked(); // may get unchoked optimistically will(returnValue(true)); atMost(1).of(c).unchoke(with(Matchers.any(Integer.class))); // chokable 1 is not interested at all c = chokables.get(1); one(c).isInterested(); will(returnValue(false)); allowing(c).shouldBeInterested(); // may check if it should be interested will(returnValue(false)); // but it wont' be never(c).getMeasuredBandwidth(true, false); // not checking its bw never(c).unchoke(with(Matchers.any(Integer.class))); // not unchoked one(c).choke(); // choked never(c).isChoked(); // regardless of previous status // chokable 2 is interested and fast It will be unchoked c = chokables.get(2); one(c).isInterested(); will(returnValue(true)); one(c).shouldBeInterested(); will(returnValue(true)); atLeast(1).of(c).getMeasuredBandwidth(true, false); will(returnValue(0.4f)); never(c).isChoked(); // don't check if it was choked one(c).unchoke(with(Matchers.any(Integer.class))); // don't care for round // chokable 3 is interested and faster than 2. It will be unchoked the same way c = chokables.get(3); one(c).isInterested(); will(returnValue(true)); one(c).shouldBeInterested(); will(returnValue(true)); atLeast(1).of(c).getMeasuredBandwidth(true, false); will(returnValue(0.5f)); never(c).isChoked(); // don't check if it was choked one(c).unchoke(with(Matchers.any(Integer.class))); // don't care for round }}); c.rechoke(); mockery.assertIsSatisfied(); } /** * all chokables are slow, but one gets unchoked nevertheless */ public void testOptimisticOnly() throws Exception { // provide an executor that executes stuff immediately final ScheduledExecutorService myService = new ScheduledExecutorServiceStub() { @Override public void execute(Runnable command) { command.run(); } }; Module m = new AbstractModule() { @Override public void configure() { bind(ScheduledExecutorService.class).annotatedWith(Names.named("nioExecutor")).toInstance(myService); } }; Injector inj = LimeTestUtils.createInjector(m); ChokerFactory factory = inj.getInstance(ChokerFactory.class); // create a bunch of mock Chockables final List<Chokable> chokables = new ArrayList<Chokable>(10); for (int i = 0; i < 2; i++) chokables.add(mockery.mock(Chokable.class)); NECallable<List<? extends Chokable>> neCallable = new NECallable<List<? extends Chokable>>() { public List<? extends Chokable> call() { return chokables; } }; final Choker c = factory.getChoker(neCallable, false); BittorrentSettings.TORRENT_MIN_UPLOADS.setValue(1); mockery.checking(new Expectations(){{ // 0 is not and should not be interested Chokable c = chokables.get(0); atLeast(1).of(c).isInterested(); will(returnValue(false)); allowing(c).shouldBeInterested(); will(returnValue(false)); never(c).getMeasuredBandwidth(true, false); never(c).isChoked(); one(c).choke(); // 1 is interested but slow, gets unchoked optimistically c = chokables.get(1); atLeast(1).of(c).isInterested(); will(returnValue(true)); atLeast(1).of(c).shouldBeInterested(); will(returnValue(true)); atLeast(1).of(c).getMeasuredBandwidth(true, false); will(returnValue(0.1f)); one(c).isChoked(); will(returnValue(true)); one(c).unchoke(with(Matchers.any(Integer.class))); }}); c.rechoke(); mockery.assertIsSatisfied(); } /** * No chokables are interested, but one should be */ public void testShouldBeInterested() throws Exception { // provide an executor that executes stuff immediately final ScheduledExecutorService myService = new ScheduledExecutorServiceStub() { @Override public void execute(Runnable command) { command.run(); } }; Module m = new AbstractModule() { @Override public void configure() { bind(ScheduledExecutorService.class).annotatedWith(Names.named("nioExecutor")).toInstance(myService); } }; Injector inj = LimeTestUtils.createInjector(m); ChokerFactory factory = inj.getInstance(ChokerFactory.class); // create a bunch of mock Chockables final List<Chokable> chokables = new ArrayList<Chokable>(10); for (int i = 0; i < 2; i++) chokables.add(mockery.mock(Chokable.class)); NECallable<List<? extends Chokable>> neCallable = new NECallable<List<? extends Chokable>>() { public List<? extends Chokable> call() { return chokables; } }; final Choker c = factory.getChoker(neCallable, false); BittorrentSettings.TORRENT_MIN_UPLOADS.setValue(1); mockery.checking(new Expectations(){{ // 0 is not and should not be interested Chokable c = chokables.get(0); atLeast(1).of(c).isInterested(); will(returnValue(false)); allowing(c).shouldBeInterested(); will(returnValue(false)); never(c).getMeasuredBandwidth(true, false); never(c).isChoked(); one(c).choke(); // 1 is not interested but should be c = chokables.get(1); atLeast(1).of(c).isInterested(); will(returnValue(false)); atLeast(1).of(c).shouldBeInterested(); will(returnValue(true)); never(c).getMeasuredBandwidth(true, false); will(returnValue(0.1f)); one(c).isChoked(); will(returnValue(true)); one(c).unchoke(with(Matchers.any(Integer.class))); }}); c.rechoke(); mockery.assertIsSatisfied(); } }