]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Extract the transfer tests
[btrtrc.git] / client_test.go
1 package torrent
2
3 import (
4         "encoding/binary"
5         "fmt"
6         "io"
7         "io/ioutil"
8         "os"
9         "path/filepath"
10         "reflect"
11         "testing"
12         "time"
13
14         "github.com/bradfitz/iter"
15         "github.com/stretchr/testify/assert"
16         "github.com/stretchr/testify/require"
17
18         "github.com/anacrolix/dht/v2"
19         _ "github.com/anacrolix/envpprof"
20         "github.com/anacrolix/missinggo"
21         "github.com/anacrolix/missinggo/v2/filecache"
22
23         "github.com/anacrolix/torrent/bencode"
24         "github.com/anacrolix/torrent/internal/testutil"
25         "github.com/anacrolix/torrent/iplist"
26         "github.com/anacrolix/torrent/metainfo"
27         "github.com/anacrolix/torrent/storage"
28 )
29
30 func TestClientDefault(t *testing.T) {
31         cl, err := NewClient(TestingConfig())
32         require.NoError(t, err)
33         cl.Close()
34 }
35
36 func TestClientNilConfig(t *testing.T) {
37         cl, err := NewClient(nil)
38         require.NoError(t, err)
39         cl.Close()
40 }
41
42 func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) {
43         cfg := TestingConfig()
44         pc, err := storage.NewBoltPieceCompletion(cfg.DataDir)
45         require.NoError(t, err)
46         ci := storage.NewFileWithCompletion(cfg.DataDir, pc)
47         defer ci.Close()
48         cfg.DefaultStorage = ci
49         cl, err := NewClient(cfg)
50         require.NoError(t, err)
51         cl.Close()
52         // And again, https://github.com/anacrolix/torrent/issues/158
53         cl, err = NewClient(cfg)
54         require.NoError(t, err)
55         cl.Close()
56 }
57
58 func TestAddDropTorrent(t *testing.T) {
59         cl, err := NewClient(TestingConfig())
60         require.NoError(t, err)
61         defer cl.Close()
62         dir, mi := testutil.GreetingTestTorrent()
63         defer os.RemoveAll(dir)
64         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
65         require.NoError(t, err)
66         assert.True(t, new)
67         tt.SetMaxEstablishedConns(0)
68         tt.SetMaxEstablishedConns(1)
69         tt.Drop()
70 }
71
72 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
73         // TODO?
74         t.SkipNow()
75 }
76
77 func TestAddTorrentNoUsableURLs(t *testing.T) {
78         // TODO?
79         t.SkipNow()
80 }
81
82 func TestAddPeersToUnknownTorrent(t *testing.T) {
83         // TODO?
84         t.SkipNow()
85 }
86
87 func TestPieceHashSize(t *testing.T) {
88         assert.Equal(t, 20, pieceHash.Size())
89 }
90
91 func TestTorrentInitialState(t *testing.T) {
92         dir, mi := testutil.GreetingTestTorrent()
93         defer os.RemoveAll(dir)
94         cl := &Client{
95                 config: TestingConfig(),
96         }
97         cl.initLogger()
98         tor := cl.newTorrent(
99                 mi.HashInfoBytes(),
100                 storage.NewFileWithCompletion(TestingTempDir.NewSub(), storage.NewMapPieceCompletion()),
101         )
102         tor.setChunkSize(2)
103         tor.cl.lock()
104         err := tor.setInfoBytes(mi.InfoBytes)
105         tor.cl.unlock()
106         require.NoError(t, err)
107         require.Len(t, tor.pieces, 3)
108         tor.pendAllChunkSpecs(0)
109         tor.cl.lock()
110         assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
111         tor.cl.unlock()
112         assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
113 }
114
115 func TestReducedDialTimeout(t *testing.T) {
116         cfg := NewDefaultClientConfig()
117         for _, _case := range []struct {
118                 Max             time.Duration
119                 HalfOpenLimit   int
120                 PendingPeers    int
121                 ExpectedReduced time.Duration
122         }{
123                 {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
124                 {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
125                 {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
126                 {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
127                 {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
128                 {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
129         } {
130                 reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
131                 expected := _case.ExpectedReduced
132                 if expected < cfg.MinDialTimeout {
133                         expected = cfg.MinDialTimeout
134                 }
135                 if reduced != expected {
136                         t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
137                 }
138         }
139 }
140
141 func TestAddDropManyTorrents(t *testing.T) {
142         cl, err := NewClient(TestingConfig())
143         require.NoError(t, err)
144         defer cl.Close()
145         for i := range iter.N(1000) {
146                 var spec TorrentSpec
147                 binary.PutVarint(spec.InfoHash[:], int64(i))
148                 tt, new, err := cl.AddTorrentSpec(&spec)
149                 assert.NoError(t, err)
150                 assert.True(t, new)
151                 defer tt.Drop()
152         }
153 }
154
155 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
156         return storage.NewResourcePieces(fc.AsResourceProvider())
157 }
158
159 func TestMergingTrackersByAddingSpecs(t *testing.T) {
160         cl, err := NewClient(TestingConfig())
161         require.NoError(t, err)
162         defer cl.Close()
163         spec := TorrentSpec{}
164         T, new, _ := cl.AddTorrentSpec(&spec)
165         if !new {
166                 t.FailNow()
167         }
168         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
169         _, new, _ = cl.AddTorrentSpec(&spec)
170         assert.False(t, new)
171         assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.metainfo.AnnounceList)
172         // Because trackers are disabled in TestingConfig.
173         assert.EqualValues(t, 0, len(T.trackerAnnouncers))
174 }
175
176 // We read from a piece which is marked completed, but is missing data.
177 func TestCompletedPieceWrongSize(t *testing.T) {
178         cfg := TestingConfig()
179         cfg.DefaultStorage = badStorage{}
180         cl, err := NewClient(cfg)
181         require.NoError(t, err)
182         defer cl.Close()
183         info := metainfo.Info{
184                 PieceLength: 15,
185                 Pieces:      make([]byte, 20),
186                 Files: []metainfo.FileInfo{
187                         {Path: []string{"greeting"}, Length: 13},
188                 },
189         }
190         b, err := bencode.Marshal(info)
191         require.NoError(t, err)
192         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
193                 InfoBytes: b,
194                 InfoHash:  metainfo.HashBytes(b),
195         })
196         require.NoError(t, err)
197         defer tt.Drop()
198         assert.True(t, new)
199         r := tt.NewReader()
200         defer r.Close()
201         b, err = ioutil.ReadAll(r)
202         assert.Len(t, b, 13)
203         assert.NoError(t, err)
204 }
205
206 func BenchmarkAddLargeTorrent(b *testing.B) {
207         cfg := TestingConfig()
208         cfg.DisableTCP = true
209         cfg.DisableUTP = true
210         cl, err := NewClient(cfg)
211         require.NoError(b, err)
212         defer cl.Close()
213         b.ReportAllocs()
214         for range iter.N(b.N) {
215                 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
216                 if err != nil {
217                         b.Fatal(err)
218                 }
219                 t.Drop()
220         }
221 }
222
223 func TestResponsive(t *testing.T) {
224         seederDataDir, mi := testutil.GreetingTestTorrent()
225         defer os.RemoveAll(seederDataDir)
226         cfg := TestingConfig()
227         cfg.Seed = true
228         cfg.DataDir = seederDataDir
229         seeder, err := NewClient(cfg)
230         require.Nil(t, err)
231         defer seeder.Close()
232         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
233         seederTorrent.VerifyData()
234         leecherDataDir, err := ioutil.TempDir("", "")
235         require.Nil(t, err)
236         defer os.RemoveAll(leecherDataDir)
237         cfg = TestingConfig()
238         cfg.DataDir = leecherDataDir
239         leecher, err := NewClient(cfg)
240         require.Nil(t, err)
241         defer leecher.Close()
242         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
243                 ret = TorrentSpecFromMetaInfo(mi)
244                 ret.ChunkSize = 2
245                 return
246         }())
247         leecherTorrent.AddClientPeer(seeder)
248         reader := leecherTorrent.NewReader()
249         defer reader.Close()
250         reader.SetReadahead(0)
251         reader.SetResponsive()
252         b := make([]byte, 2)
253         _, err = reader.Seek(3, io.SeekStart)
254         require.NoError(t, err)
255         _, err = io.ReadFull(reader, b)
256         assert.Nil(t, err)
257         assert.EqualValues(t, "lo", string(b))
258         _, err = reader.Seek(11, io.SeekStart)
259         require.NoError(t, err)
260         n, err := io.ReadFull(reader, b)
261         assert.Nil(t, err)
262         assert.EqualValues(t, 2, n)
263         assert.EqualValues(t, "d\n", string(b))
264 }
265
266 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
267         seederDataDir, mi := testutil.GreetingTestTorrent()
268         defer os.RemoveAll(seederDataDir)
269         cfg := TestingConfig()
270         cfg.Seed = true
271         cfg.DataDir = seederDataDir
272         seeder, err := NewClient(cfg)
273         require.Nil(t, err)
274         defer seeder.Close()
275         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
276         seederTorrent.VerifyData()
277         leecherDataDir, err := ioutil.TempDir("", "")
278         require.Nil(t, err)
279         defer os.RemoveAll(leecherDataDir)
280         cfg = TestingConfig()
281         cfg.DataDir = leecherDataDir
282         leecher, err := NewClient(cfg)
283         require.Nil(t, err)
284         defer leecher.Close()
285         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
286                 ret = TorrentSpecFromMetaInfo(mi)
287                 ret.ChunkSize = 2
288                 return
289         }())
290         leecherTorrent.AddClientPeer(seeder)
291         reader := leecherTorrent.NewReader()
292         defer reader.Close()
293         reader.SetReadahead(0)
294         reader.SetResponsive()
295         b := make([]byte, 2)
296         _, err = reader.Seek(3, io.SeekStart)
297         require.NoError(t, err)
298         _, err = io.ReadFull(reader, b)
299         assert.Nil(t, err)
300         assert.EqualValues(t, "lo", string(b))
301         go leecherTorrent.Drop()
302         _, err = reader.Seek(11, io.SeekStart)
303         require.NoError(t, err)
304         n, err := reader.Read(b)
305         assert.EqualError(t, err, "torrent closed")
306         assert.EqualValues(t, 0, n)
307 }
308
309 func TestDHTInheritBlocklist(t *testing.T) {
310         ipl := iplist.New(nil)
311         require.NotNil(t, ipl)
312         cfg := TestingConfig()
313         cfg.IPBlocklist = ipl
314         cfg.NoDHT = false
315         cl, err := NewClient(cfg)
316         require.NoError(t, err)
317         defer cl.Close()
318         numServers := 0
319         cl.eachDhtServer(func(s *dht.Server) {
320                 assert.Equal(t, ipl, s.IPBlocklist())
321                 numServers++
322         })
323         assert.EqualValues(t, 2, numServers)
324 }
325
326 // Check that stuff is merged in subsequent AddTorrentSpec for the same
327 // infohash.
328 func TestAddTorrentSpecMerging(t *testing.T) {
329         cl, err := NewClient(TestingConfig())
330         require.NoError(t, err)
331         defer cl.Close()
332         dir, mi := testutil.GreetingTestTorrent()
333         defer os.RemoveAll(dir)
334         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
335                 InfoHash: mi.HashInfoBytes(),
336         })
337         require.NoError(t, err)
338         require.True(t, new)
339         require.Nil(t, tt.Info())
340         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
341         require.NoError(t, err)
342         require.False(t, new)
343         require.NotNil(t, tt.Info())
344 }
345
346 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
347         dir, mi := testutil.GreetingTestTorrent()
348         os.RemoveAll(dir)
349         cl, _ := NewClient(TestingConfig())
350         defer cl.Close()
351         tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
352                 InfoHash: mi.HashInfoBytes(),
353         })
354         tt.Drop()
355         assert.EqualValues(t, 0, len(cl.Torrents()))
356         select {
357         case <-tt.GotInfo():
358                 t.FailNow()
359         default:
360         }
361 }
362
363 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
364         for i := range iter.N(info.NumPieces()) {
365                 p := info.Piece(i)
366                 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
367         }
368 }
369
370 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
371         fileCacheDir, err := ioutil.TempDir("", "")
372         require.NoError(t, err)
373         defer os.RemoveAll(fileCacheDir)
374         fileCache, err := filecache.NewCache(fileCacheDir)
375         require.NoError(t, err)
376         greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
377         defer os.RemoveAll(greetingDataTempDir)
378         filePieceStore := csf(fileCache)
379         defer filePieceStore.Close()
380         info, err := greetingMetainfo.UnmarshalInfo()
381         require.NoError(t, err)
382         ih := greetingMetainfo.HashInfoBytes()
383         greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
384         require.NoError(t, err)
385         writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
386         // require.Equal(t, len(testutil.GreetingFileContents), written)
387         // require.NoError(t, err)
388         for i := 0; i < info.NumPieces(); i++ {
389                 p := info.Piece(i)
390                 if alreadyCompleted {
391                         require.NoError(t, greetingData.Piece(p).MarkComplete())
392                 }
393         }
394         cfg := TestingConfig()
395         // TODO: Disable network option?
396         cfg.DisableTCP = true
397         cfg.DisableUTP = true
398         cfg.DefaultStorage = filePieceStore
399         cl, err := NewClient(cfg)
400         require.NoError(t, err)
401         defer cl.Close()
402         tt, err := cl.AddTorrent(greetingMetainfo)
403         require.NoError(t, err)
404         psrs := tt.PieceStateRuns()
405         assert.Len(t, psrs, 1)
406         assert.EqualValues(t, 3, psrs[0].Length)
407         assert.Equal(t, alreadyCompleted, psrs[0].Complete)
408         if alreadyCompleted {
409                 r := tt.NewReader()
410                 b, err := ioutil.ReadAll(r)
411                 assert.NoError(t, err)
412                 assert.EqualValues(t, testutil.GreetingFileContents, b)
413         }
414 }
415
416 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
417         testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
418 }
419
420 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
421         testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
422 }
423
424 func TestAddMetainfoWithNodes(t *testing.T) {
425         cfg := TestingConfig()
426         cfg.ListenHost = func(string) string { return "" }
427         cfg.NoDHT = false
428         cfg.DhtStartingNodes = func() ([]dht.Addr, error) { return nil, nil }
429         // For now, we want to just jam the nodes into the table, without
430         // verifying them first. Also the DHT code doesn't support mixing secure
431         // and insecure nodes if security is enabled (yet).
432         // cfg.DHTConfig.NoSecurity = true
433         cl, err := NewClient(cfg)
434         require.NoError(t, err)
435         defer cl.Close()
436         sum := func() (ret int64) {
437                 cl.eachDhtServer(func(s *dht.Server) {
438                         ret += s.Stats().OutboundQueriesAttempted
439                 })
440                 return
441         }
442         assert.EqualValues(t, 0, sum())
443         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
444         require.NoError(t, err)
445         // Nodes are not added or exposed in Torrent's metainfo. We just randomly
446         // check if the announce-list is here instead. TODO: Add nodes.
447         assert.Len(t, tt.metainfo.AnnounceList, 5)
448         // There are 6 nodes in the torrent file.
449         for sum() != int64(6*len(cl.dhtServers)) {
450                 time.Sleep(time.Millisecond)
451         }
452 }
453
454 type testDownloadCancelParams struct {
455         SetLeecherStorageCapacity bool
456         LeecherStorageCapacity    int64
457         Cancel                    bool
458 }
459
460 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
461         greetingTempDir, mi := testutil.GreetingTestTorrent()
462         defer os.RemoveAll(greetingTempDir)
463         cfg := TestingConfig()
464         cfg.Seed = true
465         cfg.DataDir = greetingTempDir
466         seeder, err := NewClient(cfg)
467         require.NoError(t, err)
468         defer seeder.Close()
469         defer testutil.ExportStatusWriter(seeder, "s")()
470         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
471         seederTorrent.VerifyData()
472         leecherDataDir, err := ioutil.TempDir("", "")
473         require.NoError(t, err)
474         defer os.RemoveAll(leecherDataDir)
475         fc, err := filecache.NewCache(leecherDataDir)
476         require.NoError(t, err)
477         if ps.SetLeecherStorageCapacity {
478                 fc.SetCapacity(ps.LeecherStorageCapacity)
479         }
480         cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
481         cfg.DataDir = leecherDataDir
482         leecher, err := NewClient(cfg)
483         require.NoError(t, err)
484         defer leecher.Close()
485         defer testutil.ExportStatusWriter(leecher, "l")()
486         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
487                 ret = TorrentSpecFromMetaInfo(mi)
488                 ret.ChunkSize = 2
489                 return
490         }())
491         require.NoError(t, err)
492         assert.True(t, new)
493         psc := leecherGreeting.SubscribePieceStateChanges()
494         defer psc.Close()
495
496         leecherGreeting.cl.lock()
497         leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
498         if ps.Cancel {
499                 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
500         }
501         leecherGreeting.cl.unlock()
502         done := make(chan struct{})
503         defer close(done)
504         go leecherGreeting.AddClientPeer(seeder)
505         completes := make(map[int]bool, 3)
506         expected := func() map[int]bool {
507                 if ps.Cancel {
508                         return map[int]bool{0: false, 1: false, 2: false}
509                 } else {
510                         return map[int]bool{0: true, 1: true, 2: true}
511                 }
512         }()
513         for !reflect.DeepEqual(completes, expected) {
514                 _v := <-psc.Values
515                 v := _v.(PieceStateChange)
516                 completes[v.Index] = v.Complete
517         }
518 }
519
520 func TestTorrentDownloadAll(t *testing.T) {
521         testDownloadCancel(t, testDownloadCancelParams{})
522 }
523
524 func TestTorrentDownloadAllThenCancel(t *testing.T) {
525         testDownloadCancel(t, testDownloadCancelParams{
526                 Cancel: true,
527         })
528 }
529
530 // Ensure that it's an error for a peer to send an invalid have message.
531 func TestPeerInvalidHave(t *testing.T) {
532         cl, err := NewClient(TestingConfig())
533         require.NoError(t, err)
534         defer cl.Close()
535         info := metainfo.Info{
536                 PieceLength: 1,
537                 Pieces:      make([]byte, 20),
538                 Files:       []metainfo.FileInfo{{Length: 1}},
539         }
540         infoBytes, err := bencode.Marshal(info)
541         require.NoError(t, err)
542         tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
543                 InfoBytes: infoBytes,
544                 InfoHash:  metainfo.HashBytes(infoBytes),
545                 Storage:   badStorage{},
546         })
547         require.NoError(t, err)
548         assert.True(t, _new)
549         defer tt.Drop()
550         cn := &connection{
551                 t: tt,
552         }
553         assert.NoError(t, cn.peerSentHave(0))
554         assert.Error(t, cn.peerSentHave(1))
555 }
556
557 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
558         greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
559         defer os.RemoveAll(greetingTempDir)
560         cfg := TestingConfig()
561         cfg.DataDir = greetingTempDir
562         seeder, err := NewClient(TestingConfig())
563         require.NoError(t, err)
564         seeder.AddTorrentSpec(&TorrentSpec{
565                 InfoBytes: greetingMetainfo.InfoBytes,
566         })
567 }
568
569 // Check that when the listen port is 0, all the protocols listened on have
570 // the same port, and it isn't zero.
571 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
572         cl, err := NewClient(TestingConfig())
573         require.NoError(t, err)
574         defer cl.Close()
575         port := cl.LocalPort()
576         assert.NotEqual(t, 0, port)
577         cl.eachListener(func(s listener) bool {
578                 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
579                 return true
580         })
581 }
582
583 func TestClientDynamicListenTCPOnly(t *testing.T) {
584         cfg := TestingConfig()
585         cfg.DisableUTP = true
586         cfg.DisableTCP = false
587         cl, err := NewClient(cfg)
588         require.NoError(t, err)
589         defer cl.Close()
590         assert.NotEqual(t, 0, cl.LocalPort())
591 }
592
593 func TestClientDynamicListenUTPOnly(t *testing.T) {
594         cfg := TestingConfig()
595         cfg.DisableTCP = true
596         cfg.DisableUTP = false
597         cl, err := NewClient(cfg)
598         require.NoError(t, err)
599         defer cl.Close()
600         assert.NotEqual(t, 0, cl.LocalPort())
601 }
602
603 func totalConns(tts []*Torrent) (ret int) {
604         for _, tt := range tts {
605                 tt.cl.lock()
606                 ret += len(tt.conns)
607                 tt.cl.unlock()
608         }
609         return
610 }
611
612 func TestSetMaxEstablishedConn(t *testing.T) {
613         var tts []*Torrent
614         ih := testutil.GreetingMetaInfo().HashInfoBytes()
615         cfg := TestingConfig()
616         cfg.DisableAcceptRateLimiting = true
617         cfg.dropDuplicatePeerIds = true
618         for i := range iter.N(3) {
619                 cl, err := NewClient(cfg)
620                 require.NoError(t, err)
621                 defer cl.Close()
622                 tt, _ := cl.AddTorrentInfoHash(ih)
623                 tt.SetMaxEstablishedConns(2)
624                 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i))()
625                 tts = append(tts, tt)
626         }
627         addPeers := func() {
628                 for _, tt := range tts {
629                         for _, _tt := range tts {
630                                 // if tt != _tt {
631                                 tt.AddClientPeer(_tt.cl)
632                                 // }
633                         }
634                 }
635         }
636         waitTotalConns := func(num int) {
637                 for totalConns(tts) != num {
638                         addPeers()
639                         time.Sleep(time.Millisecond)
640                 }
641         }
642         addPeers()
643         waitTotalConns(6)
644         tts[0].SetMaxEstablishedConns(1)
645         waitTotalConns(4)
646         tts[0].SetMaxEstablishedConns(0)
647         waitTotalConns(2)
648         tts[0].SetMaxEstablishedConns(1)
649         addPeers()
650         waitTotalConns(4)
651         tts[0].SetMaxEstablishedConns(2)
652         addPeers()
653         waitTotalConns(6)
654 }
655
656 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
657 // client, and returns a magnet link.
658 func makeMagnet(t *testing.T, cl *Client, dir string, name string) string {
659         os.MkdirAll(dir, 0770)
660         file, err := os.Create(filepath.Join(dir, name))
661         require.NoError(t, err)
662         file.Write([]byte(name))
663         file.Close()
664         mi := metainfo.MetaInfo{}
665         mi.SetDefaults()
666         info := metainfo.Info{PieceLength: 256 * 1024}
667         err = info.BuildFromFilePath(filepath.Join(dir, name))
668         require.NoError(t, err)
669         mi.InfoBytes, err = bencode.Marshal(info)
670         require.NoError(t, err)
671         magnet := mi.Magnet(name, mi.HashInfoBytes()).String()
672         tr, err := cl.AddTorrent(&mi)
673         require.NoError(t, err)
674         require.True(t, tr.Seeding())
675         tr.VerifyData()
676         return magnet
677 }
678
679 // https://github.com/anacrolix/torrent/issues/114
680 func TestMultipleTorrentsWithEncryption(t *testing.T) {
681         testSeederLeecherPair(
682                 t,
683                 func(cfg *ClientConfig) {
684                         cfg.HeaderObfuscationPolicy.Preferred = true
685                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
686                 },
687                 func(cfg *ClientConfig) {
688                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
689                 },
690         )
691 }
692
693 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
694 // seeder config is done first.
695 func testSeederLeecherPair(t *testing.T, seeder func(*ClientConfig), leecher func(*ClientConfig)) {
696         cfg := TestingConfig()
697         cfg.Seed = true
698         cfg.DataDir = filepath.Join(cfg.DataDir, "server")
699         os.Mkdir(cfg.DataDir, 0755)
700         seeder(cfg)
701         server, err := NewClient(cfg)
702         require.NoError(t, err)
703         defer server.Close()
704         defer testutil.ExportStatusWriter(server, "s")()
705         magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
706         // Extra torrents are added to test the seeder having to match incoming obfuscated headers
707         // against more than one torrent. See issue #114
708         makeMagnet(t, server, cfg.DataDir, "test2")
709         for i := 0; i < 100; i++ {
710                 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
711         }
712         cfg = TestingConfig()
713         cfg.DataDir = filepath.Join(cfg.DataDir, "client")
714         leecher(cfg)
715         client, err := NewClient(cfg)
716         require.NoError(t, err)
717         defer client.Close()
718         defer testutil.ExportStatusWriter(client, "c")()
719         tr, err := client.AddMagnet(magnet1)
720         require.NoError(t, err)
721         tr.AddClientPeer(server)
722         <-tr.GotInfo()
723         tr.DownloadAll()
724         client.WaitAll()
725 }
726
727 // This appears to be the situation with the S3 BitTorrent client.
728 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
729         // Leecher prefers obfuscation, but the seeder does not allow it.
730         testSeederLeecherPair(
731                 t,
732                 func(cfg *ClientConfig) {
733                         cfg.HeaderObfuscationPolicy.Preferred = false
734                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
735                 },
736                 func(cfg *ClientConfig) {
737                         cfg.HeaderObfuscationPolicy.Preferred = true
738                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
739                 },
740         )
741 }
742
743 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
744         // Leecher prefers no obfuscation, but the seeder enforces it.
745         testSeederLeecherPair(
746                 t,
747                 func(cfg *ClientConfig) {
748                         cfg.HeaderObfuscationPolicy.Preferred = true
749                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
750                 },
751                 func(cfg *ClientConfig) {
752                         cfg.HeaderObfuscationPolicy.Preferred = false
753                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
754                 },
755         )
756 }
757
758 func TestClientAddressInUse(t *testing.T) {
759         s, _ := NewUtpSocket("udp", ":50007", nil)
760         if s != nil {
761                 defer s.Close()
762         }
763         cfg := TestingConfig().SetListenAddr(":50007")
764         cl, err := NewClient(cfg)
765         require.Error(t, err)
766         require.Nil(t, cl)
767 }
768
769 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
770         cc := TestingConfig()
771         cc.DisableUTP = true
772         cc.NoDHT = false
773         cl, err := NewClient(cc)
774         require.NoError(t, err)
775         defer cl.Close()
776         assert.NotEmpty(t, cl.DhtServers())
777 }
778
779 func TestIssue335(t *testing.T) {
780         dir, mi := testutil.GreetingTestTorrent()
781         defer os.RemoveAll(dir)
782         cfg := TestingConfig()
783         cfg.Seed = false
784         cfg.Debug = true
785         cfg.DataDir = dir
786         comp, err := storage.NewBoltPieceCompletion(dir)
787         require.NoError(t, err)
788         defer comp.Close()
789         cfg.DefaultStorage = storage.NewMMapWithCompletion(dir, comp)
790         cl, err := NewClient(cfg)
791         require.NoError(t, err)
792         defer cl.Close()
793         tor, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
794         require.NoError(t, err)
795         assert.True(t, new)
796         require.True(t, cl.WaitAll())
797         tor.Drop()
798         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
799         require.NoError(t, err)
800         assert.True(t, new)
801         require.True(t, cl.WaitAll())
802 }