package org.limewire.core.impl.connection; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.lib.legacy.ClassImposteriser; import org.limewire.core.api.connection.ConnectionItem; import org.limewire.core.api.connection.ConnectionLifecycleEventType; import org.limewire.core.api.connection.ConnectionStrength; import org.limewire.friend.api.FriendPresence; import org.limewire.lifecycle.Service; import org.limewire.lifecycle.ServiceRegistry; import org.limewire.net.SocketsManager.ConnectType; import org.limewire.util.BaseTestCase; import org.limewire.util.MatchAndCopy; import org.limewire.util.PrivateAccessor; import ca.odell.glazedlists.EventList; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.ConnectionServices; import com.limegroup.gnutella.connection.ConnectionLifecycleEvent; import com.limegroup.gnutella.connection.ConnectionLifecycleListener; import com.limegroup.gnutella.connection.RoutedConnection; import com.limegroup.gnutella.util.LimeWireUtils; /** * Various tests for GnutellaConnectionManagerImpl. Tests event listeners, * delegate functions, and ConnectionStrength calculation. */ public class GnutellaConnectionManagerImplTest extends BaseTestCase { private Mockery context; private ConnectionManager connectionManager; public GnutellaConnectionManagerImplTest(String name) { super(name); } @Override protected void setUp() throws Exception { context = new Mockery() {{ setImposteriser(ClassImposteriser.INSTANCE); }}; connectionManager = context.mock(ConnectionManager.class); } /** * Confirms calling registerListener() on an instance of gnutellaConnectionManager * will register the instance as an event listener on ConnectionManager */ public void testRegisterConnectionListener() { final GnutellaConnectionManagerImpl gnutellaConnectionManager = new GnutellaConnectionManagerImpl( connectionManager, null); context.checking(new Expectations() {{ exactly(1).of(connectionManager).addEventListener(gnutellaConnectionManager); }}); gnutellaConnectionManager.registerListener(); context.assertIsSatisfied(); } /** * An integration test based around registering the service that goes all the way from * the initial register to running the service once. */ public void testRegisterService() { final GnutellaConnectionManagerImpl gnutellaConnectionManager = new GnutellaConnectionManagerImpl( connectionManager, null); final ServiceRegistry registry = context.mock(ServiceRegistry.class); final ScheduledExecutorService backgroundExecutor = context.mock(ScheduledExecutorService.class); final PropertyChangeListener changeListener = context.mock(PropertyChangeListener.class); final MatchAndCopy<Service> serviceCollector = new MatchAndCopy<Service>(Service.class); final MatchAndCopy<ConnectionLifecycleListener> listenerCollector = new MatchAndCopy<ConnectionLifecycleListener>(ConnectionLifecycleListener.class); final MatchAndCopy<Runnable> runnableCollector = new MatchAndCopy<Runnable>(Runnable.class); context.checking(new Expectations() {{ exactly(1).of(registry).register(with(serviceCollector)); exactly(1).of(connectionManager).addEventListener(with(listenerCollector)); exactly(1).of(backgroundExecutor).scheduleWithFixedDelay(with(runnableCollector), with(any(Long.class)), with(any(Long.class)), with(any(TimeUnit.class))); // Ensure that the calculation actually takes place by expecting a connection info // query when the periodic service is run. exactly(1).of(connectionManager).countConnectionsWithNMessages(with(any(Integer.class))); }}); // Should start disconnected assertEquals(ConnectionStrength.DISCONNECTED, gnutellaConnectionManager.getConnectionStrength()); // Kick off the register gnutellaConnectionManager.registerService(registry, backgroundExecutor); // Test stop without initialising serviceCollector.getLastMatch().stop(); // Manually Initialise the service serviceCollector.getLastMatch().initialize(); // Make sure the service has a valid name assertNotNull(serviceCollector.getLastMatch().getServiceName()); assertNotEquals("", serviceCollector.getLastMatch().getServiceName()); // Make sure the connection listener was properly linked listenerCollector.getLastMatch().handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTED)); assertEquals(ConnectionLifecycleEventType.CONNECTED, gnutellaConnectionManager.lastStrengthRelatedEvent); listenerCollector.getLastMatch().handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.NO_INTERNET)); assertEquals(ConnectionLifecycleEventType.NO_INTERNET, gnutellaConnectionManager.lastStrengthRelatedEvent); listenerCollector.getLastMatch().handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_INITIALIZED)); assertEquals(ConnectionLifecycleEventType.CONNECTION_INITIALIZED, gnutellaConnectionManager.lastStrengthRelatedEvent); listenerCollector.getLastMatch().handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.DISCONNECTED)); assertEquals(ConnectionLifecycleEventType.CONNECTION_INITIALIZED, gnutellaConnectionManager.lastStrengthRelatedEvent); // Test start serviceCollector.getLastMatch().start(); // External listener and stop assertions context.checking(new Expectations() {{ exactly(1).of(connectionManager).removeEventListener(listenerCollector.getLastMatch()); // When service is run the new state should alternate to test listeners one(connectionManager).isConnecting(); will(returnValue(true)); one(connectionManager).isConnecting(); will(returnValue(true)); one(connectionManager).isConnecting(); will(returnValue(false)); // Should only be fired the once, the rest are either caches or when the listener is removed exactly(1).of(changeListener).propertyChange(with(any(PropertyChangeEvent.class))); // We don't care about any other calls on connection manager that could happen while // calculating the connection strength. ignoring(connectionManager); }}); // Add a listener to ensure changes are sent out gnutellaConnectionManager.addPropertyChangeListener(changeListener); // Test the service - change event runnableCollector.getLastMatch().run(); // Test the service - no change event because caching runnableCollector.getLastMatch().run(); // Remove the listener gnutellaConnectionManager.removePropertyChangeListener(changeListener); // Test the service - no change event because no listeners conencted runnableCollector.getLastMatch().run(); // Test stop serviceCollector.getLastMatch().stop(); context.assertIsSatisfied(); } /** * Test that delegate to ConnectionManager.isConnected() is correctly connected and returns * accordingly in true and false state. */ public void testIsConnected() { final ConnectionServices connectionServices = context.mock(ConnectionServices.class); GnutellaConnectionManagerImpl gnutellaConnectionManager = new GnutellaConnectionManagerImpl( connectionManager, connectionServices); context.checking(new Expectations() {{ one(connectionServices).isConnected(); will(returnValue(true)); }}); assertTrue(gnutellaConnectionManager.isConnected()); context.checking(new Expectations() {{ one(connectionServices).isConnected(); will(returnValue(false)); }}); assertFalse(gnutellaConnectionManager.isConnected()); context.assertIsSatisfied(); } /** * Test that delegate to ConnectionManager.isSupernode() is correctly connected and returns * accordingly in true and false state. */ public void testIsUltraPeer() { final ConnectionServices connectionServices = context.mock(ConnectionServices.class); GnutellaConnectionManagerImpl gnutellaConnectionManager = new GnutellaConnectionManagerImpl( connectionManager, connectionServices); context.checking(new Expectations() {{ one(connectionManager).isSupernode(); will(returnValue(true)); }}); assertTrue(gnutellaConnectionManager.isUltrapeer()); context.checking(new Expectations() {{ one(connectionManager).isSupernode(); will(returnValue(false)); }}); assertFalse(gnutellaConnectionManager.isUltrapeer()); context.assertIsSatisfied(); } /** * Ensures that the various delegate functions to ConnectionServices, LibraryManager, * and core ConnectionManager are correctly linked and pass execution on properly. */ public void testConnectionDelegates() { final ConnectionServices connectionServices = context.mock(ConnectionServices.class); final int portnum = 111111; final String hostname = "hostekepucally"; final ConnectionItem connectionItemToNotRemove = context.mock(ConnectionItem.class); final ConnectionItem connectionItemToRemove = context.mock(CoreConnectionItem.class); final ConnectionItem connectionItemToBrowse = context.mock(ConnectionItem.class); GnutellaConnectionManagerImpl gConnectionManager = new GnutellaConnectionManagerImpl( connectionManager, connectionServices); context.checking(new Expectations() {{ allowing(connectionItemToRemove); exactly(1).of(connectionServices).connect(); exactly(1).of(connectionServices).disconnect(); exactly(1).of(connectionManager).disconnect(true); exactly(1).of(connectionManager).connect(); exactly(1).of(connectionServices).removeConnection(with(any(RoutedConnection.class))); exactly(1).of(connectionServices).connectToHostAsynchronously(hostname, portnum, ConnectType.TLS); exactly(1).of(connectionServices).connectToHostAsynchronously(hostname, portnum, ConnectType.PLAIN); FriendPresence presence = context.mock(FriendPresence.class); allowing(connectionItemToBrowse).getFriendPresence(); will(returnValue(presence)); }}); gConnectionManager.connect(); gConnectionManager.disconnect(); gConnectionManager.restart(); gConnectionManager.removeConnection(connectionItemToNotRemove); gConnectionManager.removeConnection(connectionItemToRemove); gConnectionManager.tryConnection(hostname, portnum, true); gConnectionManager.tryConnection(hostname, portnum, false); context.assertIsSatisfied(); } /** * Tests the ConnectionLifecycleEvent code under various conditions. Ensure * ConnectionItems are created, added, updated, and removed according according to * the different event types. */ public void testHandleConnectionLifecycleEvent() { GnutellaConnectionManagerImpl gnutellaConnectionManager = new GnutellaConnectionManagerImpl(connectionManager, null); EventList<ConnectionItem> list = gnutellaConnectionManager.getConnectionList(); final RoutedConnection routedConnection = context.mock(RoutedConnection.class); context.checking(new Expectations() {{ // Key to ensure the RoutedConnection and ConnectionItem match allowing(routedConnection).getPort(); will(returnValue(444)); // Marker to identify with the ConnectionItem is updated allowing(routedConnection).getConnectionTime(); will(returnValue(555L)); allowing(routedConnection); }}); assertEmpty(list); // An event with no routed connection -- should be ignored gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_INITIALIZING)); assertEmpty(list); // Spurious initialised event -- should be ignored gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_INITIALIZED, routedConnection)); assertEmpty(list); // Non relevant event -- should be ignored gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_CAPABILITIES, routedConnection)); assertEmpty(list); // Initialising -- should result in a new list item gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_INITIALIZING, routedConnection)); assertEquals(1, list.size()); assertEquals(444, list.get(0).getPort()); // Duplicate initialising -- should be ignored gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_INITIALIZING, routedConnection)); assertEquals(1, list.size()); assertEquals(444, list.get(0).getPort()); // Make sure the update marker is not set before the update is made assertNotEquals(555, list.get(0).getTime()); // Initialised -- Should call update, check that time marker is updated gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_INITIALIZED, routedConnection)); assertEquals(1, list.size()); assertEquals(444, list.get(0).getPort()); // This marker will be propagated into the ConnectionItem when update is called assertEquals(555, list.get(0).getTime()); // Closed -- Should remove the item from the list gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_CLOSED, routedConnection)); assertEmpty(list); // Duplicate closed -- Should have no effect gnutellaConnectionManager.handleConnectionLifecycleEvent( new ConnectionLifecycleEvent(this, ConnectionLifecycleEventType.CONNECTION_CLOSED, routedConnection)); assertEmpty(list); context.assertIsSatisfied(); } /** * Tests the calculate function. Make sure it identifies disconnected and connecting. * Call with various input and insure all states are returned and no errors are encountered */ public void testConnectionStrength() throws Exception { // Make sure it correctly identifies disconnected and connecting assertEquals(ConnectionStrength.DISCONNECTED, testCalculate(0, false, false, 0, 0, 0, false, false, 0, null)); assertEquals(ConnectionStrength.CONNECTING, testCalculate(0, true, false, 0, 0, 0, false, false, 0, null)); // Assert NO_INTERNET state is preserved assertEquals(ConnectionStrength.NO_INTERNET, testCalculate(0, true, false, 0, 0, 0, false, false, 0, ConnectionLifecycleEventType.NO_INTERNET)); // Test waking up state assertEquals(ConnectionStrength.MEDIUM, testCalculate(0, true, false, 0, 0, 0, false, false, Long.MAX_VALUE-60*1000, null)); // Prepare a set of states that should be hit in the proceeding calculate calls Set<ConnectionStrength> states = new HashSet<ConnectionStrength>(); for ( ConnectionStrength state : ConnectionStrength.values() ) { states.add(state); } states.remove(ConnectionStrength.DISCONNECTED); states.remove(ConnectionStrength.NO_INTERNET); // Call calculates and if the result is new remove it from the list // of outstanding states states.remove(testCalculate(0, false, false, 11, 0, 0, false, false, 0, null)); states.remove(testCalculate(0, false, false, 22, 11, 0, false, false, 0, null)); states.remove(testCalculate(80, false, true, 0, 0, 0, false, false, 0, null)); states.remove(testCalculate(1, false, false, 0, 0, Integer.MAX_VALUE, false, false, 0, null)); states.remove(testCalculate(1, false, false, 0, 0, Integer.MAX_VALUE, false, false, 0, null)); states.remove(testCalculate(23452346, false, false, 0, 0, 0, false, false, 0, null)); states.remove(testCalculate(100, false, false, 0, 0, 1, false, false, 0, null)); states.remove(testCalculate(-10, false, false, 0, 0, 100, false, false, 0, null)); states.remove(testCalculate(30, false, false, 0, 0, 99, false, false, 0, null)); states.remove(testCalculate(31, false, false, 0, 0, 101, false, false, 0, null)); states.remove(testCalculate(31, false, false, 0, 0, 101, false, false, 0, null)); states.remove(testCalculate(31, false, false, 0, 0, 101, false, true, 0, null)); states.remove(testCalculate(5, false, false, 0, 0, 101, true, false, Long.MAX_VALUE-60*1000, null)); states.remove(testCalculate(5, false, false, 0, 0, 101, true, true, 0, null)); for ( int i = 1 ; i < 125 ; i+=4 ) { states.remove(testCalculate(i, false, false, 0, 0, 100, false, false, 0, null)); } // This should be enough to hit all the connection states, make sure otherwise something // strange is happening assertEmpty(states); } /** * Returns the input of the calculate function when called in environment * that corresponds to the parameters passed in. */ private ConnectionStrength testCalculate( final int countConnectionsWithNMessages, final boolean isConnecting, final boolean isConnectionIdle, final int getNumFetchingConnections, final int getNumInitializedConnections, final int getPreferredConnectionCount, final boolean isPro, final boolean isSupernode, final long lastIdleTime, final ConnectionLifecycleEventType lastStrengthRelatedEvent) throws Exception { GnutellaConnectionManagerImpl gnutellaConnectionManager = new GnutellaConnectionManagerImpl(connectionManager, null); gnutellaConnectionManager.lastIdleTime = lastIdleTime; gnutellaConnectionManager.lastStrengthRelatedEvent = lastStrengthRelatedEvent; context.checking(new Expectations() {{ allowing(connectionManager).countConnectionsWithNMessages(with(any(int.class))); will(returnValue(countConnectionsWithNMessages)); allowing(connectionManager).isConnecting(); will(returnValue(isConnecting)); allowing(connectionManager).isConnectionIdle(); will(returnValue(isConnectionIdle)); allowing(connectionManager).getNumFetchingConnections(); will(returnValue(getNumFetchingConnections)); allowing(connectionManager).getNumInitializedConnections(); will(returnValue(getNumInitializedConnections)); allowing(connectionManager).getPreferredConnectionCount(); will(returnValue(getPreferredConnectionCount)); allowing(connectionManager).isSupernode(); will(returnValue(isSupernode)); }}); // LimeWireUtils.isPro() is hardcoded, use reflection to get it PrivateAccessor isProAccessor = new PrivateAccessor(LimeWireUtils.class, null, "_isPro"); isProAccessor.setValue(isPro); // Calculate connection strength ConnectionStrength strength = gnutellaConnectionManager.calculateStrength(); // Reset pro field isProAccessor.reset(); context.assertIsSatisfied(); // Reset context setUp(); return strength; } }