ChokerTest.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 Copyright (C) 2006-2007 by Peter Dimov.
00004 
00005 This file is part of Calitko (http://www.calitko.org).
00006 
00007 Calitko is free software; you can redistribute it and/or modify
00008 it under the terms of the GNU General Public License as published by
00009 the Free Software Foundation; either version 2 of the License, or
00010 (at your option) any later version.
00011 
00012 Calitko is distributed in the hope that it will be useful,
00013 but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Calitko; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 
00021 */
00022 
00023 #include "Qt.h"
00024 #include "../Choker.h"
00025 #include "generated/ChokerDriver.h"
00026 #include "generated/ChokerMock.h" // \todo because of Timer and RandomNumberGenerator.
00027 #include "generated/TransferSessionMock.h"
00028 #include "Imports.cpp"
00029 
00030 namespace Protocols {
00031 namespace BitTorrent {
00032 namespace Transfers {
00033 namespace Testing {
00034 
00036 
00038 class ChokerTest : public CppUnit::TestFixture
00039 {
00040     CPPUNIT_TEST_SUITE (ChokerTest);
00041     CPPUNIT_TEST (testStartingChokerStartsTheTimer);
00042     CPPUNIT_TEST (testStoppingChokerStopsTheTimer);
00043     CPPUNIT_TEST (testAddOneSessionItGetsUnchokedNextChokingTime);
00044     CPPUNIT_TEST (testAddFourSessionsAllGetUnchokedNextChokingTime);
00045     CPPUNIT_TEST (testAddFiveSessionsFiveNotInterestedGetUnchokedNextChokingTime);
00046     CPPUNIT_TEST (testAddFiveSessionsFourInterestedGetUnchokedNextChokingTime);
00047     CPPUNIT_TEST (testFifthSessionUnchokedDuringFirstOptimisticUnchoking);
00048     CPPUNIT_TEST (testForthSessionUnchokedDuringSecondUnchoking);
00049     CPPUNIT_TEST (testOptimisticUnchokeDoesNotChangeFor2ChokingTimes);
00050     CPPUNIT_TEST (testUnchokePeersWeDownloadFastestFrom);
00051     CPPUNIT_TEST (testAntiSnubbingOneSnubbedSessionTwoOptimisticChokes);
00052     CPPUNIT_TEST (testAntiSnubbingHasNoEffectWhenNoConnectionsAreChoked);
00053     CPPUNIT_TEST (testClosedSessionsAreNeitherChokedNorUnchokedAnymore);
00054     CPPUNIT_TEST (testSeedersAreAlwaysKeptChoked);
00055     CPPUNIT_TEST (testSeedersAreNeverOptimisticlyUnchoked);
00056     CPPUNIT_TEST (testOptimisticUnchokingPicksRandomlyFromMultipleCandidates);
00057     CPPUNIT_TEST (testUnchokeSessionsWithHighestUploadRateWhenInSeedingMode);
00058     CPPUNIT_TEST (testWeIngnoreBeingSnubbedWhenWeAreInSeedingMode);
00059     CPPUNIT_TEST_SUITE_END();
00060 
00061     struct SessionState
00062     {
00063         bool    isPeerChoked;
00064         bool    isPeerInterested;
00065         bool    isPeerSeeder;
00066         bool    areWeSnubbed;
00067         qint64  downloadSpeed;
00068         qint64  uploadSpeed;
00069 
00070         SessionState() : isPeerChoked (true), isPeerInterested (false),
00071                          isPeerSeeder (false), areWeSnubbed (false),
00072                          downloadSpeed (0), uploadSpeed (0) {}
00073     };
00074 
00075     typedef QList <TransferSessionMock *>               Sessions;
00076     typedef QMap <TransferSessionMock *, SessionState>  SessionsStateMap;
00077 
00078     auto_ptr <TimerMock>                    timer;
00079     auto_ptr <RandomNumberGeneratorMock>    randomNumberGenerator;
00080     auto_ptr <TransferManagerStateMock>     transferManagerState;
00081     auto_ptr <Choker>                       chokerReal;
00082     auto_ptr <ChokerDriver>                 choker;
00083     Sessions                                sessions;
00084     SessionsStateMap                        sessionsState;
00085     Sessions                                openedSessions;
00086 
00087     const Choke                             choke;
00088     const Unchoke                           unchoke;
00089     QList <int>                             randomIntsZero;
00090     QList <int>                             randomIntsZeroZero;
00091     bool                                    isTestingSeedingMode;
00092     static const int                        SessionsCount = 10;
00093 
00094 public:
00095     ChokerTest() : timer (), randomNumberGenerator(), transferManagerState(),
00096                    chokerReal(), choker(), sessions(), sessionsState(),
00097                    openedSessions(), choke(), unchoke(), randomIntsZero(),
00098                    randomIntsZeroZero(), isTestingSeedingMode (false)
00099     {
00100         // let first choked session be unchoked:
00101         randomIntsZero += 0;
00102         // let the first two choked sessions be unchoked:
00103         randomIntsZeroZero += 0;
00104         randomIntsZeroZero += 0;
00105     }
00106 
00107     void setUp()
00108     {
00109         timer.reset (new TimerMock());
00110         randomNumberGenerator.reset (new RandomNumberGeneratorMock());
00111         transferManagerState.reset (new TransferManagerStateMock());
00112         chokerReal.reset (new Choker (*timer,
00113                                       *randomNumberGenerator,
00114                                       *transferManagerState));
00115         choker.reset (new ChokerDriver (*chokerReal));
00116 
00117         for (int i = 0; i < SessionsCount; ++i) {
00118             sessions += new TransferSessionMock();
00119             sessionsState.insert (sessions.last(), SessionState());
00120         }
00121     }
00122 
00123     void tearDown()
00124     {
00125         choker.reset();
00126         chokerReal.reset();
00127         randomNumberGenerator.reset();
00128         transferManagerState.reset();
00129         timer.reset();
00130 
00131         for (int i = 0; i < 10; ++i)
00132             delete sessions [i];
00133         sessions.clear();
00134         sessionsState.clear();
00135         openedSessions.clear();
00136     }
00137 
00139     void sessionsStateSetPeerIsChoked (const Sessions &sessions, bool choked)
00140     {
00141         foreach (TransferSessionMock *session, sessions)
00142             sessionsState [session].isPeerChoked = choked;
00143     }
00144 
00146     void sessionsStateSetPeerIsInterested (const Sessions &sessions,
00147                                            bool interested)
00148     {
00149         foreach (TransferSessionMock *session, sessions)
00150             sessionsState [session].isPeerInterested = interested;
00151     }
00152 
00154     void sessionsStateSetPeerIsSeeder (const Sessions &sessions, bool seeder)
00155     {
00156         foreach (TransferSessionMock *session, sessions)
00157             sessionsState [session].isPeerSeeder = seeder;
00158     }
00159 
00161     void sessionsStateSetWeAreSnubbed (const Sessions &sessions, bool snubbed)
00162     {
00163         foreach (TransferSessionMock *session, sessions)
00164             sessionsState [session].areWeSnubbed = snubbed;
00165     }
00166 
00168     void refOpenSessions (const Sessions &sessions)
00169     {
00170         foreach (TransferSessionMock *session, sessions) {
00171             call (choker->openedSession (*session))
00172             .returns();
00173         }
00174         openedSessions += sessions;
00175     }
00176 
00178     void refCloseSessions (const Sessions &sessions)
00179     {
00180         foreach (TransferSessionMock *session, sessions) {
00181             call (choker->closedSession (*session))
00182             .returns();
00183 
00184             openedSessions.removeAll (session);
00185         }
00186     }
00187 
00189 
00200     void refChokingTimeUnchokeChokeSessions (const Sessions &unchokedSessions,
00201                                              const Sessions &chokedSessions,
00202                                              const QList <int> &randomInts
00203                                                     = QList <int>())
00204     {
00205         CallDriver <BoundFunction <Choker, void()> > foo =
00206         timer->calls (choker->chokingTime());
00207             foo.willCall (transferManagerState->isDownloading())
00208                 .numberOfTimes (1, -1)
00209             .willReturn (!isTestingSeedingMode);
00210 
00211             foreach (TransferSessionMock *session, openedSessions)
00212                 foo.willCall (session->isPeerChoked())
00213                         .numberOfTimes (0, -1)
00214                     .willReturn (sessionsState [session].isPeerChoked)
00215                     .willCall (session->isPeerInterested())
00216                         .numberOfTimes (0, -1)
00217                     .willReturn (sessionsState [session].isPeerInterested)
00218                     .willCall (session->areWeSnubbed())
00219                         .numberOfTimes (0, -1)
00220                     .willReturn (sessionsState [session].areWeSnubbed)
00221                     .willCall (session->isPeerSeeder())
00222                         .numberOfTimes (0, -1)
00223                     .willReturn (sessionsState [session].isPeerSeeder)
00224                     .willCall (session->currentDownloadSpeed())
00225                         .numberOfTimes (0, -1)
00226                     .willReturn (sessionsState [session].downloadSpeed)
00227                     .willCall (session->currentUploadSpeed())
00228                         .numberOfTimes (0, -1)
00229                     .willReturn (sessionsState [session].uploadSpeed);
00230 
00231             foreach (TransferSessionMock *session, unchokedSessions)
00232                 foo.willCall (session->sendPacket (unchoke))
00233                     .returns();
00234 
00235             foreach (TransferSessionMock *session, chokedSessions)
00236                 foo.willCall (session->sendPacket (choke))
00237                     .returns();
00238 
00239             // This sets the sequence of random numbers returned when selecting
00240             // the optimistic unchokes.
00241             foreach (int randomInt, randomInts)
00242                 foo.willCall (randomNumberGenerator->random())
00243                     .willReturn (randomInt);
00244         foo.returns();
00245 
00246         sessionsStateSetPeerIsChoked (chokedSessions, true);
00247         sessionsStateSetPeerIsChoked (unchokedSessions, false);
00248     }
00249 
00251 
00255     void testStartingChokerStartsTheTimer()
00256     {
00257         Callable <void()> chokingTimeCallable (choker->operator Choker&(),
00258                                                &Choker::chokingTime);
00259 
00260         call (choker->start())
00261             .willCall (timer->start (chokingTimeCallable, 10000))
00262             .returns()
00263         .returns();
00264     }
00265 
00267     void testStoppingChokerStopsTheTimer()
00268     {
00269         call (choker->stop())
00270             .willCall (timer->stop())
00271             .returns()
00272         .returns();
00273     }
00274 
00276     void testAddOneSessionItGetsUnchokedNextChokingTime()
00277     {
00278         refOpenSessions (sessions.mid (0, 1));
00279         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 1),
00280                                             sessions.mid (0, 0));
00281     }
00282 
00284     void testAddFourSessionsAllGetUnchokedNextChokingTime()
00285     {
00286         refOpenSessions (sessions.mid (0, 4));
00287         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 4),
00288                                             sessions.mid (0, 0));
00289     }
00290 
00292     void testAddFiveSessionsFiveNotInterestedGetUnchokedNextChokingTime()
00293     {
00294         refOpenSessions (sessions.mid (0, 5));
00295         sessionsStateSetPeerIsInterested (sessions.mid (0, 5), false);
00296         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 5),
00297                                             sessions.mid (0, 0));
00298     }
00299 
00301     void testAddFiveSessionsFourInterestedGetUnchokedNextChokingTime()
00302     {
00303         refOpenSessions (sessions.mid (0, 5));
00304         sessionsStateSetPeerIsInterested (sessions.mid (0, 5), true);
00305         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 4),
00306                                             sessions.mid (0, 0));
00307     }
00308 
00310 
00323     void testFifthSessionUnchokedDuringFirstOptimisticUnchoking()
00324     {
00325         refOpenSessions (sessions.mid (0, 5));
00326         sessionsStateSetPeerIsInterested (sessions.mid (0, 5), true);
00327         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 4),
00328                                             sessions.mid (0, 0));
00329         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 0),
00330                                             sessions.mid (0, 0));
00331         refChokingTimeUnchokeChokeSessions (sessions.mid (4, 1),
00332                                             sessions.mid (3, 1),
00333                                             randomIntsZero);
00334     }
00335 
00337 
00345     void testForthSessionUnchokedDuringSecondUnchoking()
00346     {
00347         testFifthSessionUnchokedDuringFirstOptimisticUnchoking();
00348 
00349         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00350         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00351         refChokingTimeUnchokeChokeSessions (sessions.mid (3, 1),
00352                                             sessions.mid (4, 1),
00353                                             randomIntsZero);
00354     }
00355 
00357 
00368     void testOptimisticUnchokeDoesNotChangeFor2ChokingTimes()
00369     {
00370         testFifthSessionUnchokedDuringFirstOptimisticUnchoking();
00371         sessionsState [sessions [0]].downloadSpeed = 15;
00372         sessionsState [sessions [1]].downloadSpeed = 10;
00373         sessionsState [sessions [2]].downloadSpeed = 5;
00374         sessionsState [sessions [3]].downloadSpeed = 5;
00375         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00376         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00377         refChokingTimeUnchokeChokeSessions (sessions.mid (3, 1),
00378                                             sessions.mid (4, 1),
00379                                             randomIntsZero);
00380     }
00381 
00383 
00387     void testUnchokePeersWeDownloadFastestFrom()
00388     {
00389         refOpenSessions (sessions.mid (0, SessionsCount));
00390 
00391         Sessions unchokedSessions, chokedSessions;
00392         // Low download rate, not interested => choked
00393         sessionsState [sessions [0]].isPeerInterested = false;
00394         sessionsState [sessions [0]].downloadSpeed = 1;
00395         chokedSessions += sessions [0];
00396         // Low download rate, interested => choked
00397         sessionsState [sessions [1]].isPeerInterested = true;
00398         sessionsState [sessions [1]].downloadSpeed = 0;
00399         chokedSessions += sessions [1];
00400         // Good download rate, not interested => unchoked (good download)
00401         sessionsState [sessions [2]].isPeerInterested = false;
00402         sessionsState [sessions [2]].downloadSpeed = 12;
00403         unchokedSessions += sessions [2];
00404         // Low download rate, not interested => choked
00405         sessionsState [sessions [3]].isPeerInterested = false;
00406         sessionsState [sessions [3]].downloadSpeed = 0;
00407         chokedSessions += sessions [3];
00408         // Good download rate, interested => unchcoked (interested)
00409         sessionsState [sessions [4]].isPeerInterested = true;
00410         sessionsState [sessions [4]].downloadSpeed = 12;
00411         unchokedSessions += sessions [4];
00412         // Good download rate, interested => unchoked (interested)
00413         sessionsState [sessions [5]].isPeerInterested = true;
00414         sessionsState [sessions [5]].downloadSpeed = 48;
00415         unchokedSessions += sessions [5];
00416         // Low download rate, interested => choked
00417         sessionsState [sessions [6]].isPeerInterested = true;
00418         sessionsState [sessions [6]].downloadSpeed = 0;
00419         chokedSessions += sessions [6];
00420         // Low download rate, interested => unchoked (interested)
00421         // Note: wins over 0 because the other is not interested
00422         sessionsState [sessions [7]].isPeerInterested = true;
00423         sessionsState [sessions [7]].downloadSpeed = 1;
00424         unchokedSessions += sessions [7];
00425         // Good download rate, interested => unchoked (interested)
00426         sessionsState [sessions [8]].isPeerInterested = true;
00427         sessionsState [sessions [8]].downloadSpeed = 12000000;
00428         unchokedSessions += sessions [8];
00429         // Low download rate, not interested => choked
00430         sessionsState [sessions [9]].isPeerInterested = false;
00431         sessionsState [sessions [9]].downloadSpeed = 0;
00432         chokedSessions += sessions [9];
00433 
00434         refChokingTimeUnchokeChokeSessions (unchokedSessions, Sessions());
00435     }
00436 
00438 
00447     void testAntiSnubbingOneSnubbedSessionTwoOptimisticChokes()
00448     {
00449         refOpenSessions (sessions.mid (0, 6));
00450 
00451         sessionsStateSetPeerIsChoked (sessions.mid (0, 4), false);
00452         sessionsStateSetPeerIsInterested (sessions.mid (0, 6), true);
00453         sessionsStateSetWeAreSnubbed (sessions.mid (2, 1), true);
00454 
00455         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00456         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00457         refChokingTimeUnchokeChokeSessions (sessions.mid (4, 2),
00458                                             sessions.mid (2, 2),
00459                                             randomIntsZeroZero);
00460     }
00461 
00463     void testAntiSnubbingHasNoEffectWhenNoConnectionsAreChoked()
00464     {
00465         refOpenSessions (sessions.mid (0, 4));
00466 
00467         sessionsStateSetPeerIsChoked (sessions.mid (0, 4), false);
00468         sessionsStateSetWeAreSnubbed (sessions.mid (0, 4), true);
00469 
00470         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00471         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00472         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00473     }
00474 
00476 
00481     void testClosedSessionsAreNeitherChokedNorUnchokedAnymore()
00482     {
00483         refOpenSessions (sessions.mid (0, 1));
00484         refCloseSessions (sessions.mid (0, 1));
00485         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00486     }
00487 
00489 
00494     void testSeedersAreAlwaysKeptChoked()
00495     {
00496         refOpenSessions (sessions.mid (0, 4));
00497         // 1. session 0 is seeder and must not be unchoked
00498         // 2. session 1 is seeder and is unchoked, so should be choked
00499         sessionsStateSetPeerIsSeeder (sessions.mid (0, 2), true);
00500         sessionsStateSetPeerIsChoked (sessions.mid (1, 1), false);
00501         refChokingTimeUnchokeChokeSessions (sessions.mid (2, 2),
00502                                             sessions.mid (1, 1));
00503     }
00504 
00506     void testSeedersAreNeverOptimisticlyUnchoked()
00507     {
00508         // For the first choking time there will be new seeder sessions.
00509         testSeedersAreAlwaysKeptChoked();
00510         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00511         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00512         // For the second choking time there will be no new seeder sessions.
00513         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00514         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00515         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00516     }
00517 
00519 
00533     void testOptimisticUnchokingPicksRandomlyFromMultipleCandidates()
00534     {
00535         refOpenSessions (sessions);
00536         sessionsStateSetPeerIsInterested (sessions, true);
00537         // Unchoke the first four interested sessions:
00538         refChokingTimeUnchokeChokeSessions (sessions.mid (0, 4), Sessions());
00539         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00540         // Optimisticly unchoking sessions [7]:
00541         QList <int> randomIntsTwentyEight;
00542         randomIntsTwentyEight += 28;
00543         refChokingTimeUnchokeChokeSessions (sessions.mid (7, 1),
00544                                             sessions.mid (3, 1),
00545                                             randomIntsTwentyEight);
00546     }
00547 
00548     void testUnchokeSessionsWithHighestUploadRateWhenInSeedingMode()
00549     {
00550         isTestingSeedingMode = true;
00551         refOpenSessions (sessions.mid (0, SessionsCount));
00552 
00553         Sessions unchokedSessions, chokedSessions;
00554         // Low download rate, not interested => choked
00555         sessionsState [sessions [0]].isPeerInterested = false;
00556         sessionsState [sessions [0]].uploadSpeed = 1;
00557         chokedSessions += sessions [0];
00558         // Low download rate, interested => choked
00559         sessionsState [sessions [1]].isPeerInterested = true;
00560         sessionsState [sessions [1]].uploadSpeed = 0;
00561         chokedSessions += sessions [1];
00562         // Good download rate, not interested => unchoked (good download)
00563         sessionsState [sessions [2]].isPeerInterested = false;
00564         sessionsState [sessions [2]].uploadSpeed = 12;
00565         unchokedSessions += sessions [2];
00566         // Low download rate, not interested => choked
00567         sessionsState [sessions [3]].isPeerInterested = false;
00568         sessionsState [sessions [3]].uploadSpeed = 0;
00569         chokedSessions += sessions [3];
00570         // Good download rate, interested => unchcoked (interested)
00571         sessionsState [sessions [4]].isPeerInterested = true;
00572         sessionsState [sessions [4]].uploadSpeed = 12;
00573         unchokedSessions += sessions [4];
00574         // Good download rate, interested => unchoked (interested)
00575         sessionsState [sessions [5]].isPeerInterested = true;
00576         sessionsState [sessions [5]].uploadSpeed = 48;
00577         unchokedSessions += sessions [5];
00578         // Low download rate, interested => choked
00579         sessionsState [sessions [6]].isPeerInterested = true;
00580         sessionsState [sessions [6]].uploadSpeed = 0;
00581         chokedSessions += sessions [6];
00582         // Low download rate, interested => unchoked (interested)
00583         // Note: wins over 0 because the other is not interested
00584         sessionsState [sessions [7]].isPeerInterested = true;
00585         sessionsState [sessions [7]].uploadSpeed = 1;
00586         unchokedSessions += sessions [7];
00587         // Good download rate, interested => unchoked (interested)
00588         sessionsState [sessions [8]].isPeerInterested = true;
00589         sessionsState [sessions [8]].uploadSpeed = 12000000;
00590         unchokedSessions += sessions [8];
00591         // Low download rate, not interested => choked
00592         sessionsState [sessions [9]].isPeerInterested = false;
00593         sessionsState [sessions [9]].uploadSpeed = 0;
00594         chokedSessions += sessions [9];
00595 
00596         refChokingTimeUnchokeChokeSessions (unchokedSessions, Sessions());
00597     }
00598 
00600     void testWeIngnoreBeingSnubbedWhenWeAreInSeedingMode()
00601     {
00602         isTestingSeedingMode = true;
00603         refOpenSessions (sessions.mid (0, 6));
00604 
00605         sessionsStateSetPeerIsChoked (sessions.mid (0, 4), false);
00606         sessionsStateSetPeerIsInterested (sessions.mid (0, 6), true);
00607         sessionsStateSetWeAreSnubbed (sessions.mid (2, 1), true);
00608 
00609         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00610         refChokingTimeUnchokeChokeSessions (Sessions(), Sessions());
00611         refChokingTimeUnchokeChokeSessions (sessions.mid (4, 1),
00612                                             sessions.mid (3, 1),
00613                                             randomIntsZero);
00614     }
00615 };
00616 
00617 CPPUNIT_TEST_SUITE_REGISTRATION(ChokerTest);
00618 
00619 } // namespace Testing
00620 } // namespace Transfers
00621 } // namespace BitTorrent
00622 } // namespace Protocols