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