github.com/uber/kraken@v0.1.4/lib/torrent/scheduler/events_test.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package scheduler 15 16 import ( 17 "testing" 18 "time" 19 20 "github.com/golang/mock/gomock" 21 "github.com/stretchr/testify/require" 22 "github.com/uber-go/tally" 23 "github.com/uber/kraken/core" 24 "github.com/uber/kraken/lib/store" 25 "github.com/uber/kraken/lib/torrent/networkevent" 26 "github.com/uber/kraken/lib/torrent/scheduler/announcequeue" 27 "github.com/uber/kraken/lib/torrent/scheduler/conn" 28 "github.com/uber/kraken/lib/torrent/scheduler/connstate" 29 "github.com/uber/kraken/lib/torrent/storage" 30 "github.com/uber/kraken/lib/torrent/storage/agentstorage" 31 mockannounceclient "github.com/uber/kraken/mocks/tracker/announceclient" 32 mockmetainfoclient "github.com/uber/kraken/mocks/tracker/metainfoclient" 33 "github.com/uber/kraken/tracker/announceclient" 34 "github.com/uber/kraken/utils/testutil" 35 ) 36 37 const _testNamespace = "noexist" 38 39 type mockEventLoop struct { 40 t *testing.T 41 c chan event 42 } 43 44 func (l *mockEventLoop) expect(e event) { 45 select { 46 case result := <-l.c: 47 require.Equal(l.t, e, result) 48 case <-time.After(5 * time.Second): 49 l.t.Fatalf("timed out waiting for %T to occur", e) 50 } 51 } 52 53 func (l *mockEventLoop) send(e event) bool { 54 l.c <- e 55 return true 56 } 57 58 // Unimplemented. 59 func (l *mockEventLoop) run(*state) {} 60 func (l *mockEventLoop) stop() {} 61 func (l *mockEventLoop) sendTimeout(e event, timeout time.Duration) error { panic("unimplemented") } 62 63 type stateMocks struct { 64 metainfoClient *mockmetainfoclient.MockClient 65 announceClient *mockannounceclient.MockClient 66 announceQueue announcequeue.Queue 67 torrentArchive storage.TorrentArchive 68 eventLoop *mockEventLoop 69 } 70 71 func newStateMocks(t *testing.T) (*stateMocks, func()) { 72 cleanup := &testutil.Cleanup{} 73 defer cleanup.Recover() 74 75 ctrl := gomock.NewController(t) 76 cleanup.Add(ctrl.Finish) 77 78 metainfoClient := mockmetainfoclient.NewMockClient(ctrl) 79 80 announceClient := mockannounceclient.NewMockClient(ctrl) 81 82 cads, c := store.CADownloadStoreFixture() 83 cleanup.Add(c) 84 85 mocks := &stateMocks{ 86 metainfoClient: metainfoClient, 87 announceClient: announceClient, 88 announceQueue: announcequeue.New(), 89 torrentArchive: agentstorage.NewTorrentArchive(tally.NoopScope, cads, metainfoClient), 90 eventLoop: &mockEventLoop{t, make(chan event)}, 91 } 92 return mocks, cleanup.Run 93 } 94 95 func (m *stateMocks) newState(config Config) *state { 96 sched, err := newScheduler( 97 config, 98 m.torrentArchive, 99 tally.NoopScope, 100 core.PeerContextFixture(), 101 m.announceClient, 102 networkevent.NewTestProducer(), 103 withEventLoop(m.eventLoop)) 104 if err != nil { 105 panic(err) 106 } 107 return newState(sched, m.announceQueue) 108 } 109 110 func (m *stateMocks) newTorrent() storage.Torrent { 111 mi := core.MetaInfoFixture() 112 113 m.metainfoClient.EXPECT(). 114 Download(_testNamespace, mi.Digest()). 115 Return(mi, nil) 116 117 t, err := m.torrentArchive.CreateTorrent(_testNamespace, mi.Digest()) 118 if err != nil { 119 panic(err) 120 } 121 return t 122 } 123 124 func TestAnnounceTickEvent(t *testing.T) { 125 require := require.New(t) 126 127 mocks, cleanup := newStateMocks(t) 128 defer cleanup() 129 130 state := mocks.newState(Config{}) 131 132 var ctrls []*torrentControl 133 for i := 0; i < 5; i++ { 134 c, err := state.addTorrent(_testNamespace, mocks.newTorrent(), true) 135 require.NoError(err) 136 ctrls = append(ctrls, c) 137 } 138 139 // First torrent should announce. 140 mocks.announceClient.EXPECT(). 141 Announce( 142 ctrls[0].dispatcher.Digest(), 143 ctrls[0].dispatcher.InfoHash(), 144 false, 145 announceclient.V1). 146 Return(nil, time.Second, nil) 147 148 announceTickEvent{}.apply(state) 149 150 mocks.eventLoop.expect(announceResultEvent{ 151 infoHash: ctrls[0].dispatcher.InfoHash(), 152 }) 153 } 154 155 func TestAnnounceTickEventSkipsFullTorrents(t *testing.T) { 156 require := require.New(t) 157 158 mocks, cleanup := newStateMocks(t) 159 defer cleanup() 160 161 state := mocks.newState(Config{ 162 ConnState: connstate.Config{ 163 MaxOpenConnectionsPerTorrent: 5, 164 }, 165 }) 166 167 full, err := state.addTorrent(_testNamespace, mocks.newTorrent(), true) 168 require.NoError(err) 169 170 info := full.dispatcher.Stat() 171 172 for i := 0; i < 5; i++ { 173 _, c, cleanup := conn.PipeFixture(conn.Config{}, info) 174 defer cleanup() 175 176 require.NoError(state.conns.AddPending(c.PeerID(), c.InfoHash(), nil)) 177 require.NoError(state.addOutgoingConn(c, info.Bitfield(), info)) 178 } 179 180 empty, err := state.addTorrent(_testNamespace, mocks.newTorrent(), true) 181 require.NoError(err) 182 183 // The first torrent is full and should be skipped, announcing the empty 184 // torrent. 185 mocks.announceClient.EXPECT(). 186 Announce( 187 empty.dispatcher.Digest(), 188 empty.dispatcher.InfoHash(), 189 false, 190 announceclient.V1). 191 Return(nil, time.Second, nil) 192 193 announceTickEvent{}.apply(state) 194 195 // Empty torrent announced. 196 mocks.eventLoop.expect(announceResultEvent{ 197 infoHash: empty.dispatcher.InfoHash(), 198 }) 199 200 // The empty torrent is pending, so keep skipping full torrent. 201 announceTickEvent{}.apply(state) 202 announceTickEvent{}.apply(state) 203 announceTickEvent{}.apply(state) 204 205 // Remove a connection -- torrent is no longer full. 206 c := state.conns.ActiveConns()[0] 207 c.Close() 208 // TODO(codyg): This is ugly. Conn fixtures aren't connected to our event 209 // loop, so we have to manually trigger the event. 210 connClosedEvent{c}.apply(state) 211 212 mocks.eventLoop.expect(peerRemovedEvent{ 213 peerID: c.PeerID(), 214 infoHash: c.InfoHash(), 215 }) 216 217 mocks.announceClient.EXPECT(). 218 Announce( 219 full.dispatcher.Digest(), 220 full.dispatcher.InfoHash(), 221 false, 222 announceclient.V1). 223 Return(nil, time.Second, nil) 224 225 announceTickEvent{}.apply(state) 226 227 // Previously full torrent announced. 228 mocks.eventLoop.expect(announceResultEvent{ 229 infoHash: full.dispatcher.InfoHash(), 230 }) 231 }